#include <QDir>
#include <QLocale>
#include <QDateTime>
+#include <QDataStream>
#include "debug.h"
"site_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
"host_name TEXT UNIQUE);", Q_FUNC_INFO);
- exec("CREATE INDEX IF NOT EXISTS domain_index ON sites(host_name);", Q_FUNC_INFO);
+ exec("CREATE INDEX IF NOT EXISTS host_name_index ON sites(host_name);", Q_FUNC_INFO);
// クエリ作成。
m_find_site_id = prepare("SELECT site_id FROM sites WHERE host_name=?;", Q_FUNC_INFO);
return pointer(new SentenceDatabase(settings, site_id, name));
}
+
+// IndexDatabase --------------------------------------------------------------
+
+TM::index_data_type::index_data_type()
+ : index_id(0)
+ , word_id(0)
+ , is_stop_word(false)
+{
+}
+
+TM::index_data_type::pointer TM::index_data_type::create()
+{
+ return pointer(new index_data_type());
+}
+
+TM::IndexDatabase::IndexDatabase(Settings *settings, QString dbname)
+{
+ m_index_limit = 20 * 1024;
+
+ m_database_name =
+ settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
+ + "/" + dbname;
+ open(Q_FUNC_INFO);
+
+ // テーブル作成。
+ exec("CREATE TABLE IF NOT EXISTS indexes("
+ "index_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
+ "word_id INTEGER UNIQUE NOT NULL,"
+ "sentence_ids BLOB,"
+ "is_stop_word BOOLEAN);", Q_FUNC_INFO);
+
+ exec("CREATE INDEX IF NOT EXISTS word_id_index ON indexes(word_id);", Q_FUNC_INFO);
+
+ // クエリ作成。
+ m_find_index = prepare(
+ "SELECT * FROM indexes WHERE word_id=?;", Q_FUNC_INFO);
+ m_insert_index = prepare(
+ "INSERT INTO indexes(word_id, sentence_ids, is_stop_word) "
+ "VALUES(?, ?, ?);", Q_FUNC_INFO);
+ m_update_index = prepare(
+ "UPDATE indexes SET "
+ "word_id=?,"
+ "sentence_ids=?,"
+ "is_stop_word=? "
+ "WHERE index_id=?;", Q_FUNC_INFO);
+}
+
+TM::index_data_type::pointer TM::IndexDatabase::find_index(quint32 word_id)
+{
+ assert(word_id);
+
+ m_find_index.bindValue(0, word_id);
+ exec(m_find_index, Q_FUNC_INFO);
+
+ index_data_type::pointer result;
+
+ if(m_find_index.next())
+ {
+ result = index_data_type::create();
+ result->index_id = m_find_index.value(0).toUInt();
+ result->word_id = m_find_index.value(1).toUInt();
+ QByteArray sentence_ids = m_find_index.value(2).toByteArray();
+ QDataStream ds(sentence_ids);
+ while(!ds.atEnd())
+ {
+ quint32 sentence_id;
+ ds >> sentence_id;
+ result->sentence_ids.insert(sentence_id);
+ }
+ result->is_stop_word = m_find_index.value(3).toBool();
+ }
+
+ return result;
+}
+
+void TM::IndexDatabase::insert_index(WordDatabase::pointer word_database,
+ sentence_data_type::pointer source, quint32 target_id)
+{
+ QString const &string = source->sentence;
+ for(QPair<int,int> const & range: source->words)
+ {
+ QString word = string.mid(
+ range.first, range.second - range.first + 1);
+ if(word.size() <= 2) continue;
+
+ int word_id = 0;
+ word_id = word_database->word_id(word);
+ assert(word_id);
+
+ index_data_type::pointer index_data = find_index(word_id);
+ if(index_data)
+ {
+ index_data->sentence_ids.insert(target_id);
+ // レコードが大きくなりすぎた場合、ストップワードとする。
+ if(m_index_limit < index_data->sentence_ids.size())
+ {
+ index_data->sentence_ids.clear();
+ index_data->is_stop_word = true;
+ }
+ update_index(index_data);
+ }
+ else
+ {
+ index_data = index_data_type::create();
+ index_data->word_id = word_id;
+ index_data->sentence_ids.insert(target_id);
+ index_data->is_stop_word = false;
+ insert_index(index_data);
+ }
+ }
+}
+
+void TM::IndexDatabase::insert_index(index_data_type::pointer index_data)
+{
+ assert(index_data);
+
+ QByteArray sentence_ids;
+ QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
+ for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
+
+ m_insert_index.bindValue(0, index_data->word_id);
+ m_insert_index.bindValue(1, sentence_ids);
+ m_insert_index.bindValue(2, index_data->is_stop_word);
+
+ exec(m_insert_index, Q_FUNC_INFO);
+}
+
+void TM::IndexDatabase::update_index(index_data_type::pointer index_data)
+{
+ assert(index_data);
+
+ QByteArray sentence_ids;
+ QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
+ for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
+
+ m_update_index.bindValue(0, index_data->word_id);
+ m_update_index.bindValue(1, sentence_ids);
+ m_update_index.bindValue(2, index_data->is_stop_word);
+ m_update_index.bindValue(3, index_data->index_id);
+
+ exec(m_update_index, Q_FUNC_INFO);
+}
+
+TM::IndexDatabase::pointer TM::IndexDatabase::create(Settings *settings, QString dbname)
+{
+ return pointer(new IndexDatabase(settings, dbname));
+}
+
// Database -------------------------------------------------------------------
TM::Database::Database(Settings *settings, Service *service)
m_sentence_cache_limit = m_settings->value(
"Database/sentence-cache-limit", QVariant::fromValue(20)).toInt();
+ m_index_cache_limit = m_settings->value(
+ "Database/index-cache-limit", QVariant::fromValue(20)).toInt();
+
// データベース用のフォルダが無ければ作成する。
if(!m_settings->contains(TMDATABASE_ROOT_PATH_KEY)) qFatal("An error occured while find settings in Database().");
QString path = m_settings->value(TMDATABASE_ROOT_PATH_KEY).toString();
SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
assert(tdb);
+ // 文脈込の検索を試みる。
quint32 source_id = sdb->find_sentence_id_with_context(
source->sentence, source->previous_crc, source->next_crc);
+ if(source_id)
+ {
+ result = tdb->find_sentence_by_source_id(source_id);
+ if(result) result->quality = 101;
+ }
+ // 文脈込で発見できない場合、文脈無視で検索を試みる。
+ if(!result)
+ {
+ source_id = sdb->find_sentence_id(source->sentence);
+ if(source_id) result = tdb->find_sentence_by_source_id(source_id);
+ if(result) result->quality = 100;
+ }
- if(source_id) result = tdb->find_sentence_by_source_id(source_id);
-
+ // 検索結果を返す。
QMetaObject::invokeMethod(
m_service, "sentence_found",
Qt::QueuedConnection,
quint32 scode, sentence_data_type::pointer source,
quint32 tcode, sentence_data_type::pointer target)
{
+ // 入力の検証。
assert(site_id);
assert(scode);
assert(tcode);
assert(target->previous_crc == 0);
assert(target->next_crc == 0);
+ // 原文の更新。
SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
assert(sdb);
- SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
- assert(tdb);
+ // 原文IDの検索、無い場合登録。
+ // 現在、前後の文脈が一致する場合のみ、IDを検索できたこととしている。
+ // 文脈が一致しない場合、新たにIDを付与する。
quint32 source_id = source->sentence_id;
if(!source_id) source_id = sdb->find_sentence_id_with_context(
source->sentence, source->previous_crc, source->next_crc);
}
assert(source_id);
+ // 訳文の更新。
+ SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
+ assert(tdb);
+
target->source_id = source_id;
target->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
+ // 原文IDに対応する訳文を検索する。
+ // 現在、原文IDに対して登録できる訳文は一つのみ。
sentence_data_type::pointer target_sentence =
tdb->find_sentence_by_source_id(source_id);
- if(target_sentence)
+ if(target_sentence) // 訳文があった場合、上書きして更新する。
{
target->sentence_id = target_sentence->sentence_id;
tdb->update(target);
}
- else tdb->insert(target);
+ else
+ {
+ tdb->insert(target); // 訳文が無かった場合、新規挿入する。
+ // 挿入後、検索することで訳文IDを取得する。
+ target_sentence = tdb->find_sentence_by_source_id(source_id);
+ }
+ assert(target_sentence);
+ quint32 target_id = target_sentence->sentence_id;
+ assert(target_id); // 挿入あるいは更新しているので、IDがある。
+
+ // 単語の登録。
+ IndexDatabase::pointer index_database =
+ find_index_database(site_id, scode, tcode);
+ assert(index_database);
+ WordDatabase::pointer word_database =
+ find_word_database(scode);
+ index_database->insert_index(word_database, source, target_id);
+ assert(index_database);
}
QString TM::Database::find_language_name(int code) const
return result;
}
+TM::WordDatabase::pointer TM::Database::find_word_database(int code)
+{
+ QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
+ assert(it != m_word_databases.end());
+ return it.value();
+}
+
+/*!
+ * \brief 原文の単語IDと訳文の文IDとの照応を保持するデータベースを返します。
+ * \param site_id サイトのID。
+ * \param scode 原文の言語コード。
+ * \param tcode 訳文の言語コード。
+ * \return データベースへのポインタ。
+ *
+ * このメンバは接続をキャッシュしています。
+ */
+TM::IndexDatabase::pointer TM::Database::find_index_database(
+ quint32 site_id, int scode, int tcode)
+{
+ assert(site_id);
+ assert(scode);
+ assert(tcode);
+
+ // キャッシュが大きすぎる場合、消去する。
+ if(m_index_cache_limit < m_index_databases.size()) m_index_databases.clear();
+
+ // データベース名を作成する。
+ QString dbname, sname, tname, site_name;
+ sname = find_language_name(scode).toLower();
+ assert(!sname.isEmpty());
+ if(!sname.isEmpty())
+ {
+ tname = find_language_name(tcode).toLower();
+ assert(!tname.isEmpty());
+ }
+ if(!tname.isEmpty())
+ {
+ site_name = QString::number(site_id);
+ assert(!site_name.isEmpty());
+ }
+ if(!site_name.isEmpty())
+ {
+ dbname = QString("index-");
+ dbname += site_name + "-" + sname + "-" + tname + ".db";
+ }
+
+ IndexDatabase::pointer result;
+
+ if(!dbname.isEmpty())
+ {
+ QMap<QString, IndexDatabase::pointer>::iterator it =
+ m_index_databases.find(dbname);
+ if(it == m_index_databases.end())
+ it = m_index_databases.insert(dbname, IndexDatabase::create(m_settings, dbname));
+ assert(it != m_index_databases.end());
+
+ if(it != m_index_databases.end()) result = it.value();
+ }
+
+ return result;
+}
+
/*!
* \brief 引数として与えられたサイトと言語用のセンテンス・データベースを返します。
* \param site_id サイトを識別する番号。