#include #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 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 &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 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); }