#include "../contracts/assets/assets.h" #include "symbol_query.h" #include "local_database.h" #include "../core/data_path.h" #include #include #include namespace metasequoia::local_modes { namespace { struct StatementCloser { void operator()(sqlite3_stmt *statement) const { sqlite3_finalize(statement); } }; // 编码只有字母;统一转小写,非字母返回空串(查不到任何符号)。 std::string normalize_code(const std::string &code) { std::string lower; lower.reserve(code.size()); for (const unsigned char character : code) { if (character >= 'a' && character <= 'z') lower.push_back(static_cast(character)); else if (character >= 'A' && character <= 'Z') lower.push_back(static_cast(character - 'A' + 'a')); else return {}; } return lower; } LocalQueryResult query_failure(const char *diagnostic) { return {{}, std::string(diagnostic)}; } // ?1 = 编码,?2 = 前缀上界,?3 = limit。返回 symbol 一列,同一符号只取排位最好的那行。 LocalQueryResult run_query(const std::string &code, const std::filesystem::path &database_path, const char *sql, int limit) { const std::string lower = normalize_code(code); if (lower.empty() || limit <= 0) return {}; if (database_path.empty()) return query_failure("Symbol database is unavailable."); const std::shared_ptr database = open_local_database(database_path); if (!database) return query_failure("Symbol database is unavailable."); sqlite3_stmt *raw_statement = nullptr; if (sqlite3_prepare_v2(database.get(), sql, -1, &raw_statement, nullptr) != SQLITE_OK) return query_failure("Symbol database could not be queried."); const std::unique_ptr statement(raw_statement); std::string upper_bound = lower; upper_bound.push_back(static_cast(0x7f)); // 前缀查询的同一符号可能挂在好几个编码下(a 与 ao、codex 的 alpha 与 Alpha),多取一些再去重。 const int fetch_limit = limit <= (1 << 20) ? limit * 4 : limit; if (sqlite3_bind_text(statement.get(), 1, lower.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement.get(), 2, upper_bound.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_int(statement.get(), 3, fetch_limit) != SQLITE_OK) return query_failure("Symbol database could not be queried."); LocalQueryResult result; std::unordered_set seen; int step_result = SQLITE_ROW; while ((step_result = sqlite3_step(statement.get())) == SQLITE_ROW && result.candidates.size() < static_cast(limit)) { const auto *symbol = reinterpret_cast(sqlite3_column_text(statement.get(), 0)); if (symbol == nullptr || !seen.insert(symbol).second) continue; result.candidates.emplace_back(lower, symbol, 0, CandidateSource::Symbol); } if (step_result != SQLITE_ROW && step_result != SQLITE_DONE) return query_failure("Symbol database could not be queried."); const std::size_t count = result.candidates.size(); for (std::size_t index = 0; index < count; ++index) result.candidates[index].weight = static_cast(count - index); return result; } } // namespace LocalQueryResult query_symbols(const std::string &code, int limit) { return query_symbols(code, data_file_path(metasequoia::assets::other_dictionary), limit); } LocalQueryResult query_symbols(const std::string &code, const std::filesystem::path &database_path, int limit) { // 整串命中的编码排最前;其余前缀命中按编码长短,短的通常是这一类的总表(x 先出 xl 再出 xlc)。 constexpr const char *kSql = "SELECT symbol FROM symbol_code WHERE code>=?1 AND code