pgLab/tests/pglabtests/tst_SqlCompletionProvider.cpp

238 lines
7.2 KiB
C++
Raw Permalink Normal View History

#include <gtest/gtest.h>
#include "SqlCompletionProvider.h"
#include "PrintTo_Qt.h"
using namespace testing;
namespace {
CompletionCatalog testCatalog()
{
CompletionCatalog c;
c.schemas = { "public", "sales" };
c.relations = {
{ "public", "customers", 'r', { "id", "name", "select" } },
{ "public", "orders", 'r', { "id", "customer_id", "Total" } },
{ "public", "order_view", 'v', { "id" } },
{ "sales", "invoices", 'r', { "inv_id" } },
{ "public", "My Table", 'r', { "a" } },
};
return c;
}
std::vector<CompletionItem> complete(QString text)
{
int pos = text.indexOf('|');
text.remove(pos, 1);
SqlCompletionProvider p(testCatalog());
return p.complete(analyzeSqlContext(text, pos));
}
const CompletionItem* find(const std::vector<CompletionItem> &items, const QString &display, const QString &detail = QString())
{
for (auto &i : items)
if (i.display == display && (detail.isEmpty() || i.detail == detail))
return &i;
return nullptr;
}
} // namespace
TEST(SqlCompletionProvider, relationsAndSchemasAfterFrom)
{
auto items = complete("SELECT * FROM |");
ASSERT_TRUE(find(items, "sales"));
EXPECT_EQ(find(items, "sales")->kind, CompletionKind::Schema);
ASSERT_TRUE(find(items, "customers"));
EXPECT_EQ(find(items, "customers")->insertText, "customers");
EXPECT_FALSE(find(items, "invoices"));
}
TEST(SqlCompletionProvider, relationNeedingQuotesIsQuoted)
{
auto items = complete("SELECT * FROM my|");
// prefix match is case-insensitive
ASSERT_TRUE(find(items, "My Table"));
EXPECT_EQ(find(items, "My Table")->insertText, "\"My Table\"");
}
TEST(SqlCompletionProvider, relationsOfSchema)
{
auto items = complete("SELECT * FROM sales.|");
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].display, "invoices");
}
TEST(SqlCompletionProvider, prefixFilters)
{
auto items = complete("SELECT * FROM ord|");
EXPECT_TRUE(find(items, "orders"));
EXPECT_TRUE(find(items, "order_view"));
EXPECT_FALSE(find(items, "customers"));
}
TEST(SqlCompletionProvider, reservedWordAndMixedCaseColumnsAreQuoted)
{
auto items = complete("SELECT | FROM customers, orders o");
ASSERT_TRUE(find(items, "select"));
EXPECT_EQ(find(items, "select")->insertText, "\"select\"");
ASSERT_TRUE(find(items, "Total"));
EXPECT_EQ(find(items, "Total")->insertText, "\"Total\"");
ASSERT_TRUE(find(items, "name"));
EXPECT_EQ(find(items, "name")->insertText, "name");
}
TEST(SqlCompletionProvider, ambiguousColumnsAreQualified)
{
auto items = complete("SELECT | FROM customers, orders o");
auto c = find(items, "id", "customers");
auto o = find(items, "id", "o");
ASSERT_TRUE(c);
ASSERT_TRUE(o);
EXPECT_TRUE(c->ambiguous);
EXPECT_TRUE(o->ambiguous);
EXPECT_EQ(c->insertText, "customers.id");
EXPECT_EQ(o->insertText, "o.id");
EXPECT_FALSE(find(items, "customer_id")->ambiguous);
}
TEST(SqlCompletionProvider, qualifiedByAlias)
{
auto items = complete("SELECT o.| FROM customers c, orders o");
ASSERT_EQ(items.size(), 3u);
EXPECT_EQ(items[1].display, "customer_id");
EXPECT_EQ(items[0].insertText, "id");
}
TEST(SqlCompletionProvider, qualifiedByTableName)
{
auto items = complete("SELECT customers.na| FROM customers");
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].display, "name");
}
TEST(SqlCompletionProvider, quotedPrefixIsCaseSensitive)
{
auto items = complete("SELECT \"To| FROM orders");
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].insertText, "\"Total\"");
}
TEST(SqlCompletionProvider, unknownRelationGivesNoColumns)
{
auto items = complete("SELECT | FROM nonexistent");
EXPECT_TRUE(items.empty() || !find(items, "id"));
}
TEST(SqlCompletionProvider, qualifierWithoutMatchIsSchema)
{
auto items = complete("SELECT sales.| FROM customers");
ASSERT_EQ(items.size(), 1u);
EXPECT_EQ(items[0].display, "invoices");
}
TEST(SqlCompletionProvider, insertColumnListOnlyTargetColumns)
{
auto items = complete("INSERT INTO orders (|");
ASSERT_EQ(items.size(), 4u); // 3 columns + the relation name
EXPECT_TRUE(find(items, "customer_id"));
}
TEST(SqlCompletionProvider, updateSetColumns)
{
auto items = complete("UPDATE customers SET na| = 1");
ASSERT_TRUE(find(items, "name"));
}
TEST(SqlCompletionProvider, functionsOnlyWithPrefix)
{
CompletionCatalog c = testCatalog();
c.functions = { { "pg_catalog", "count", 'a' }, { "pg_catalog", "coalesce", 'f' }, { "other", "cosmic", 'f' }, { "pg_catalog", "count", 'f' } };
SqlCompletionProvider p(c);
auto run = [&](QString text) {
int pos = text.indexOf('|');
text.remove(pos, 1);
return p.complete(analyzeSqlContext(text, pos));
};
auto items = run("SELECT co| FROM customers");
ASSERT_TRUE(find(items, "count"));
EXPECT_EQ(find(items, "count")->insertText, "count(");
EXPECT_EQ(find(items, "count")->kind, CompletionKind::Function);
EXPECT_TRUE(find(items, "coalesce"));
EXPECT_FALSE(find(items, "cosmic"));
EXPECT_FALSE(find(run("SELECT | FROM customers"), "count"));
EXPECT_FALSE(find(run("INSERT INTO customers (co|"), "count"));
}
namespace {
std::vector<CompletionItem> completeJoin(QString text)
{
CompletionCatalog c = testCatalog();
c.relations.push_back({ "public", "order_lines", 'r', { "order_id", "line_no", "sku" } });
c.relations.push_back({ "public", "employees", 'r', { "id", "boss_id" } });
c.foreignKeys = {
{ "public", "orders", "public", "customers", { "customer_id" }, { "id" } },
{ "public", "order_lines", "public", "orders", { "order_id", "line_no" }, { "id", "line_no" } },
{ "sales", "invoices", "public", "customers", { "inv_id" }, { "id" } },
{ "public", "employees", "public", "employees", { "boss_id" }, { "id" } },
};
int pos = text.indexOf('|');
text.remove(pos, 1);
SqlCompletionProvider p(c);
return p.complete(analyzeSqlContext(text, pos));
}
} // namespace
TEST(SqlCompletionProvider, joinFromChildToParent)
{
auto items = completeJoin("SELECT * FROM orders o JOIN |");
auto *item = find(items, "customers c");
ASSERT_TRUE(item);
EXPECT_EQ(item->kind, CompletionKind::JoinTarget);
EXPECT_EQ(item->insertText, "customers c ON o.customer_id = c.id");
// plain relations are still offered
bool plain = false;
for (auto &i : items)
plain |= i.kind == CompletionKind::Relation && i.display == "customers";
EXPECT_TRUE(plain);
}
TEST(SqlCompletionProvider, joinFromParentToChildMultiColumn)
{
auto items = completeJoin("SELECT * FROM orders JOIN order_l|");
auto *item = find(items, "order_lines ol");
ASSERT_TRUE(item);
EXPECT_EQ(item->insertText, "order_lines ol ON orders.id = ol.order_id AND orders.line_no = ol.line_no");
}
TEST(SqlCompletionProvider, joinAliasAvoidsClash)
{
auto items = completeJoin("SELECT * FROM orders c JOIN |");
auto *item = find(items, "customers c2");
ASSERT_TRUE(item);
EXPECT_EQ(item->insertText, "customers c2 ON c.customer_id = c2.id");
}
TEST(SqlCompletionProvider, joinOtherSchemaQualified)
{
auto items = completeJoin("SELECT * FROM customers JOIN |");
auto *item = find(items, "invoices i");
ASSERT_TRUE(item);
EXPECT_EQ(item->insertText, "sales.invoices i ON customers.id = i.inv_id");
}
TEST(SqlCompletionProvider, joinSelfReference)
{
auto items = completeJoin("SELECT * FROM employees e JOIN |");
auto *item = find(items, "employees e2");
ASSERT_TRUE(item);
// both directions give a different condition
int count = 0;
for (auto &i : items)
if (i.kind == CompletionKind::JoinTarget && i.display == "employees e2")
++count;
EXPECT_EQ(count, 2);
}