OSDN Git Service

インデックスデータベース完成記念バックアップ♪
[wordring-tm/wordring-tm.git] / proxy / tmdatabase.cpp
index d04737d..25e5a6c 100644 (file)
@@ -7,6 +7,7 @@
 #include <QDir>
 #include <QLocale>
 #include <QDateTime>
+#include <QDataStream>
 
 #include "debug.h"
 
@@ -86,7 +87,7 @@ TM::SiteDatabase::SiteDatabase(Settings *settings)
                        "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);
@@ -380,6 +381,154 @@ TM::SentenceDatabase::pointer TM::SentenceDatabase::create(
        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)
@@ -406,6 +555,9 @@ void TM::Database::setup()
        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();
@@ -447,11 +599,23 @@ void TM::Database::find_sentence(quint32 site_id, int scode, int tcode,
        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,
@@ -466,6 +630,7 @@ void TM::Database::insert_sentence(quint32 site_id,
                quint32 scode, sentence_data_type::pointer source,
                quint32 tcode, sentence_data_type::pointer target)
 {
+       // 入力の検証。
        assert(site_id);
        assert(scode);
        assert(tcode);
@@ -475,11 +640,13 @@ void TM::Database::insert_sentence(quint32 site_id,
        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);
@@ -492,17 +659,40 @@ void TM::Database::insert_sentence(quint32 site_id,
        }
        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
@@ -527,6 +717,68 @@ int TM::Database::find_word_id(int code, QString word)
        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 サイトを識別する番号。