Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CBL-6371: Unnest Query run after delete index produces obtuse error m… #2178

Draft
wants to merge 1 commit into
base: release/3.2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 20 additions & 13 deletions C/tests/c4ArrayIndexTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -358,26 +358,30 @@ TEST_CASE_METHOD(ArrayIndexTest, "CRUD Array Index Shared Path", "[C][ArrayIndex
bool deleted = c4coll_deleteIndex(coll, "phones"_sl, ERROR_INFO());
REQUIRE(deleted);

// We must re-create the Query objects because we deleted the index - which means the unnest tables may be missing.
cityQuery = c4query_new2(
db, kC4N1QLQuery,
R"(SELECT p.pid, c.address.city, c.address.state FROM profiles AS p UNNEST p.contacts AS c WHERE c.address.state = "CA")"_sl,
nullptr, ERROR_INFO());
REQUIRE(cityQuery);
// cityQuery is not affected by the deletion of index "phones"
queryenum = REQUIRED(c4query_run(cityQuery, nullslice, nullptr));
validateQuery(queryenum, {
R"(["p-0001", "San Pedro", "CA"])",
R"(["p-0001", "San Pedro", "CA"])",
});

// phoneQuery is affected.
// Following error will be logged,
// 2024-10-29T21:14:28.226339 DB ERROR SQLite error (code 1): no such table: 152b9815998e188eb99eb1612aafbb3ee6031535 in "SELECT fl_result(fl_value(prof.body, 'pid')), fl_result(fl_unnested_value(c.body, 'address.city')), fl_result(fl_unnested_value(c.body, 'address.state')), fl_result(fl_unnested_value(p.body, 'type')), fl_result(fl_unnested_value(p.body, 'numbers')) FROM "kv_.profiles" AS prof JOIN bc89db8a20fe759bf161b84adf2294d9bfe0c88d AS c ON c.docid=prof.rowid JOIN "152b9815998e188eb99eb1612aafbb3ee6031535" AS p ON p.docid=c.rowid WHERE fl_unnested_value(p.body, 'type') = 'mobile'". This table is referenced by an array index, which may have been deleted.
C4Error error;
queryenum = c4query_run(phoneQuery, nullslice, &error);
CHECK(!queryenum); // This query relies on the index that has been deleted.
CHECK((error.domain == SQLiteDomain && error.code == 1));

// Recompile the query
phoneQuery = c4query_new2(
db, kC4N1QLQuery,
R"(SELECT prof.pid, c.address.city, c.address.state, p.type, p.numbers FROM profiles AS prof UNNEST prof.contacts AS c UNNEST c.phones AS p WHERE p.type = "mobile")"_sl,
nullptr, ERROR_INFO());
REQUIRE(phoneQuery);
queryenum = c4query_run(phoneQuery, nullslice, &error);
CHECK(queryenum);


queryenum = REQUIRED(c4query_run(cityQuery, nullslice, nullptr));
validateQuery(queryenum, {
R"(["p-0001", "San Pedro", "CA"])",
R"(["p-0001", "San Pedro", "CA"])",
});
queryenum = REQUIRED(c4query_run(phoneQuery, nullslice, nullptr));
validateQuery(queryenum, {
R"(["p-0001", "San Pedro", "CA", "mobile", ["310-9601308"]])",
R"(["p-0001", "San Pedro", "CA", "mobile", ["310-4833623"]])",
Expand Down Expand Up @@ -654,6 +658,9 @@ TEST_CASE_METHOD(ArrayIndexTest, "Unnest Without Alias", "[C][Unnest]") {

// 7. TestUnnestArrayLiteralNotSupport
TEST_CASE_METHOD(ArrayIndexTest, "Unnest Array Literal Not Supported", "[C][Unnest]") {
C4Collection* coll = createCollection(db, {"profiles"_sl, "_default"_sl});
importTestData(coll);

C4Error err{};
c4::ref query = c4query_new2(
db, kC4N1QLQuery,
Expand Down
9 changes: 9 additions & 0 deletions C/tests/c4QueryTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1487,6 +1487,15 @@ TEST_CASE_METHOD(CollectionTest, "C4Query FTS Multiple collections", "[Query][C]

CHECK(run().size() == 50);
CHECK(runFTS().size() == 50);

auto deleted = c4coll_deleteIndex(names, C4STR("byStreet"), nullptr);
CHECK(deleted);
// The query won't run after the index is deleted. We should see following error in the log,
// 2024-10-29T20:57:00.896439 DB ERROR SQLite error (code 1): no such table: kv_.namedscope.names::by\Street in "SELECT "namedscope.names".rowid, offsets(fts1."kv_.namedscope.names::by\Street"), offsets(fts2."kv_.wiki::by\Text"), fl_result("namedscope.names".key), fl_result(wiki.key) FROM "kv_.namedscope.names" AS "namedscope.names" INNER JOIN "kv_.wiki" AS wiki ON (fl_value("namedscope.names".body, 'birthday') != fl_value(wiki.body, 'title')) JOIN "kv_.namedscope.names::by\Street" AS fts1 ON fts1.docid = "namedscope.names".rowid JOIN "kv_.wiki::by\Text" AS fts2 ON fts2.docid = wiki.rowid WHERE fts1."kv_.namedscope.names::by\Street" MATCH 'Hwy' AND fts2. This table is referenced by an FTS index, which may have been deleted.
C4Error error;
auto qenum = c4query_run(query, c4str(nullptr), &error);
CHECK(!qenum);
CHECK((error.domain == SQLiteDomain && error.code == 1));
}

#pragma mark - OBSERVERS:
Expand Down
29 changes: 28 additions & 1 deletion LiteCore/Storage/SQLiteDataFile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <sqlite3.h>
#include <sstream>
#include <mutex>
#include <regex>
#include <thread>
#include <cinttypes>
#ifdef _WIN32
Expand Down Expand Up @@ -114,6 +115,28 @@ namespace litecore {

void LogStatement(const SQLite::Statement& st) { LogTo(SQL, "... %s", st.getQuery().c_str()); }

static std::pair<bool, std::string> enhanceSQLiteErrorLog(int errCode, const char* msg) {
std::regex noTableRegx{"no such table: (\\S+) in "};
std::cmatch match;
const char* extra = nullptr;
if ( std::regex_search(msg, match, noTableRegx) ) {
if ( std::regex_search(match.suffix().str(), std::regex{"fl_unnested_value"})
&& std::regex_match(match[1].str(), std::regex{"[0-9a-z]{40}"}) ) {
extra = "This table is referenced by an array index, which may have been deleted.";
} else if ( std::regex_match(match[1].str(), std::regex{"kv_\\..+::.+"}) ) {
// example target: match[1].str() = "kv_.namedscope.names::by\\Street"
// where "::" = KeyStore::kIndexSeparator
extra = "This table is referenced by an FTS index, which may have been deleted.";
}
}
std::string enhanced;
if ( extra ) {
enhanced = msg;
enhanced += ". "s + extra;
}
return std::make_pair(extra, enhanced);
}

static void sqlite3_log_callback(C4UNUSED void* pArg, int errCode, const char* msg) {
switch ( errCode & 0xFF ) {
case SQLITE_OK:
Expand All @@ -132,7 +155,11 @@ namespace litecore {
LogWarn(DBLog, "SQLite warning: %s", msg);
break;
default:
LogError(DBLog, "SQLite error (code %d): %s", errCode, msg);
{
auto [enhanced, enhancedMsg] = enhanceSQLiteErrorLog(errCode, msg);
if ( enhanced ) msg = enhancedMsg.c_str();
LogError(DBLog, "SQLite error (code %d): %s", errCode, msg);
}
break;
}
}
Expand Down
Loading