2 #include "tmdatabase.h"
10 #include <QDataStream>
14 // DatabaseBase ---------------------------------------------------------------
16 bool TM::DatabaseBase::open(char const *message)
18 assert(!m_database_name.isEmpty());
19 m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name);
20 m_database.setDatabaseName(m_database_name);
21 bool ret = m_database.open();
30 QSqlQuery TM::DatabaseBase::prepare(char const *sql, char const *message)
32 QSqlQuery result(m_database);
33 bool ret = result.prepare(sql);
36 error(message, &result);
42 bool TM::DatabaseBase::exec(QSqlQuery &query, char const *message)
44 bool ret = query.exec();
47 error(message, &query);
53 QSqlQuery TM::DatabaseBase::exec(char const *sql, char const *message)
55 QSqlQuery result = m_database.exec(sql);
56 if(result.lastError().isValid())
58 error(message, &result);
64 void TM::DatabaseBase::error(QString message, QSqlQuery *query)
67 if(query) s = query->lastError().text();
68 else s = m_database.lastError().text();
70 qCritical() << message << s;
73 // SiteDatabase ---------------------------------------------------------------
75 TM::SiteDatabase::SiteDatabase(Settings *settings)
78 m_cache_limit = settings->value(
79 "SiteDatabase/cache-limit", QVariant::fromValue(1024)).toInt();
82 m_database_name = settings->value(TMDATABASE_ROOT_PATH_KEY).toString() + "/site.db";
86 exec("CREATE TABLE IF NOT EXISTS sites("
87 "site_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
88 "host_name TEXT UNIQUE);", Q_FUNC_INFO);
90 exec("CREATE INDEX IF NOT EXISTS host_name_index ON sites(host_name);", Q_FUNC_INFO);
93 m_find_site_id = prepare("SELECT site_id FROM sites WHERE host_name=?;", Q_FUNC_INFO);
94 m_insert_site = prepare("INSERT INTO sites(host_name) VALUES(?);", Q_FUNC_INFO);
97 TM::SiteDatabase::~SiteDatabase()
99 if(m_database.isOpen()) m_database.close();
103 * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
105 quint32 TM::SiteDatabase::site_id(QString domain)
107 // キャッシュが大きくなりすぎた場合、消去する。
108 if(m_cache_limit < m_cache.size()) m_cache.clear();
110 QMap<QString, quint32>::const_iterator it = m_cache.find(domain);
111 if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
113 // キャッシュにない場合、データベースを検索。
114 quint32 result = find_site_id(domain);
115 // データベースにない場合、データベースに登録。
119 result = find_site_id(domain);
123 m_cache.insert(domain, result);
129 * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
133 quint32 TM::SiteDatabase::find_site_id(QString host_name)
135 m_find_site_id.bindValue(0, host_name);
136 exec(m_find_site_id, Q_FUNC_INFO);
137 if(m_find_site_id.next()) return m_find_site_id.value(0).toUInt();
142 void TM::SiteDatabase::insert_site(QString host_name)
144 m_insert_site.bindValue(0, host_name);
145 exec(m_insert_site, Q_FUNC_INFO);
148 TM::SiteDatabase::pointer TM::SiteDatabase::create(Settings *settings)
150 return pointer(new SiteDatabase(settings));
153 // WordDatabase ---------------------------------------------------------------
155 TM::WordDatabase::WordDatabase(Settings *settings, QString name)
158 m_cache_limit = settings->value(
159 "WordDatabase/cache-limit", QVariant::fromValue(100 * 1024)).toInt();
162 m_database_name = settings->value(
163 TMDATABASE_ROOT_PATH_KEY).toString() + "/word-" + name.toLower() + ".db";
167 exec("CREATE TABLE IF NOT EXISTS words("
168 "word_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
169 "word TEXT UNIQUE);", Q_FUNC_INFO);
171 exec("CREATE INDEX IF NOT EXISTS word_index ON words(word);", Q_FUNC_INFO);
174 m_find_word_id = prepare(
175 "SELECT word_id FROM words WHERE word=?;", Q_FUNC_INFO);
177 m_insert_word = prepare("INSERT INTO words(word) VALUES(?);", Q_FUNC_INFO);
180 TM::WordDatabase::~WordDatabase()
182 if(m_database.isOpen()) m_database.close();
185 int TM::WordDatabase::word_id(QString word)
187 // キャッシュが大きくなりすぎた場合、消去する。
188 if(m_cache_limit < m_cache.size()) m_cache.clear();
190 QMap<QString, int>::const_iterator it = m_cache.find(word);
191 if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
193 // キャッシュにない場合、データベースを検索。
194 int result = find_word_id(word);
195 // データベースにない場合、データベースに登録。
199 result = find_word_id(word);
203 m_cache.insert(word, result);
208 int TM::WordDatabase::find_word_id(QString word)
210 m_find_word_id.bindValue(0, word);
211 exec(m_find_word_id, Q_FUNC_INFO);
212 if(m_find_word_id.next()) return m_find_word_id.value(0).toInt();
217 void TM::WordDatabase::insert_word(QString word)
219 m_insert_word.bindValue(0, word);
220 exec(m_insert_word, Q_FUNC_INFO);
223 TM::WordDatabase::pointer TM::WordDatabase::create(Settings *settings, QString name)
225 return pointer(new WordDatabase(settings, name));
228 // SentenceDatabase -----------------------------------------------------------
230 TM::SentenceDatabase::SentenceDatabase(Settings *settings, int site_id, QString name)
234 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
235 + "/sentence-" + QString::number(site_id) + "-" + name.toLower() + ".db";
239 exec("CREATE TABLE IF NOT EXISTS sentences("
240 "sentence_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
245 "previous_crc INTEGER,"
251 exec("CREATE INDEX IF NOT EXISTS source_id_index ON sentences(source_id);", Q_FUNC_INFO);
252 exec("CREATE INDEX IF NOT EXISTS sentence_index ON sentences(sentence);", Q_FUNC_INFO);
255 m_find_sentence_id = prepare(
256 "SELECT sentence_id FROM sentences WHERE sentence=?;", Q_FUNC_INFO);
258 m_find_sentence_id_with_context = prepare(
259 "SELECT sentence_id FROM sentences "
261 "AND previous_crc=? "
262 "AND next_crc=?;", Q_FUNC_INFO);
264 m_find_sentence_by_crc = prepare("SELECT * FROM sentences WHERE crc=?;", Q_FUNC_INFO);
265 m_find_sentence_by_source_id = prepare("SELECT * FROM sentences WHERE source_id=?;", Q_FUNC_INFO);
266 m_insert_sentence = prepare(
267 "INSERT OR REPLACE INTO sentences("
268 "source_id, sentence, json, crc, previous_crc, next_crc, user_id, time) "
269 "VALUES(?, ?, ?, ?, ?, ?, ?, ?);", Q_FUNC_INFO);
270 m_update_sentence = prepare(
271 "UPDATE sentences SET "
280 "WHERE sentence_id=?;", Q_FUNC_INFO);
283 TM::SentenceDatabase::~SentenceDatabase()
285 if(m_database.isOpen()) m_database.close();
288 quint32 TM::SentenceDatabase::find_sentence_id(QString sentence)
290 m_find_sentence_id.bindValue(0, sentence);
291 exec(m_find_sentence_id, Q_FUNC_INFO);
293 if(!m_find_sentence_id.next()) return 0;
294 return m_find_sentence_id.value(0).toUInt();
297 quint32 TM::SentenceDatabase::find_sentence_id_with_context(
298 QString sentence, quint32 previous_crc, quint32 next_crc)
300 m_find_sentence_id_with_context.bindValue(0, sentence);
301 m_find_sentence_id_with_context.bindValue(1, previous_crc);
302 m_find_sentence_id_with_context.bindValue(2, next_crc);
303 exec(m_find_sentence_id_with_context, Q_FUNC_INFO);
305 if(!m_find_sentence_id_with_context.next()) return 0;
306 return m_find_sentence_id_with_context.value(0).toUInt();
309 TM::sentence_data_type::pointer
310 TM::SentenceDatabase::find_sentence_by_source_id(int source_id)
314 sentence_data_type::pointer result;
316 m_find_sentence_by_source_id.bindValue(0, source_id);
317 exec(m_find_sentence_by_source_id, Q_FUNC_INFO);
319 if(!m_find_sentence_by_source_id.next()) return result;
321 return stuff_value(&m_find_sentence_by_source_id);
324 void TM::SentenceDatabase::insert(sentence_data_type::pointer sentence_data)
326 assert(m_database.isValid());
327 assert(m_database.isOpen());
329 m_insert_sentence.bindValue(0, sentence_data->source_id);
330 m_insert_sentence.bindValue(1, sentence_data->sentence);
331 m_insert_sentence.bindValue(2, sentence_data->json);
332 m_insert_sentence.bindValue(3, sentence_data->crc);
333 m_insert_sentence.bindValue(4, sentence_data->previous_crc);
334 m_insert_sentence.bindValue(5, sentence_data->next_crc);
335 m_insert_sentence.bindValue(6, sentence_data->user_id);
336 m_insert_sentence.bindValue(7, sentence_data->time);
338 exec(m_insert_sentence, Q_FUNC_INFO);
341 void TM::SentenceDatabase::update(sentence_data_type::pointer sentence_data)
343 assert(m_database.isValid());
344 assert(m_database.isOpen());
346 m_update_sentence.bindValue(0, sentence_data->source_id);
347 m_update_sentence.bindValue(1, sentence_data->sentence);
348 m_update_sentence.bindValue(2, sentence_data->json);
349 m_update_sentence.bindValue(3, sentence_data->crc);
350 m_update_sentence.bindValue(4, sentence_data->previous_crc);
351 m_update_sentence.bindValue(5, sentence_data->next_crc);
352 m_update_sentence.bindValue(6, sentence_data->user_id);
353 m_update_sentence.bindValue(7, sentence_data->time);
354 m_update_sentence.bindValue(8, sentence_data->sentence_id);
356 exec(m_update_sentence, Q_FUNC_INFO);
359 TM::sentence_data_type::pointer
360 TM::SentenceDatabase::stuff_value(QSqlQuery *query)
362 assert(query->isValid());
363 sentence_data_type::pointer result = sentence_data_type::create();
365 result->sentence_id = query->value(0).toUInt();
366 result->source_id = query->value(1).toUInt();
367 result->sentence = query->value(2).toString();
368 result->json = query->value(3).toByteArray();
369 result->crc = query->value(4).toUInt();
370 result->previous_crc = query->value(5).toUInt();
371 result->next_crc = query->value(6).toUInt();
372 result->user_id = query->value(7).toUInt();
373 result->time = query->value(8).toUInt();
378 TM::SentenceDatabase::pointer TM::SentenceDatabase::create(
379 Settings *settings, int site_id, QString name)
381 return pointer(new SentenceDatabase(settings, site_id, name));
385 // IndexDatabase --------------------------------------------------------------
387 TM::index_data_type::index_data_type()
390 , is_stop_word(false)
394 TM::index_data_type::pointer TM::index_data_type::create()
396 return pointer(new index_data_type());
399 TM::IndexDatabase::IndexDatabase(Settings *settings, QString dbname)
401 m_index_limit = 20 * 1024;
404 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
409 exec("CREATE TABLE IF NOT EXISTS indexes("
410 "index_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
411 "word_id INTEGER UNIQUE NOT NULL,"
413 "is_stop_word BOOLEAN);", Q_FUNC_INFO);
415 exec("CREATE INDEX IF NOT EXISTS word_id_index ON indexes(word_id);", Q_FUNC_INFO);
418 m_find_index = prepare(
419 "SELECT * FROM indexes WHERE word_id=?;", Q_FUNC_INFO);
420 m_insert_index = prepare(
421 "INSERT INTO indexes(word_id, sentence_ids, is_stop_word) "
422 "VALUES(?, ?, ?);", Q_FUNC_INFO);
423 m_update_index = prepare(
424 "UPDATE indexes SET "
428 "WHERE index_id=?;", Q_FUNC_INFO);
431 TM::index_data_type::pointer TM::IndexDatabase::find_index(quint32 word_id)
435 m_find_index.bindValue(0, word_id);
436 exec(m_find_index, Q_FUNC_INFO);
438 index_data_type::pointer result;
440 if(m_find_index.next())
442 result = index_data_type::create();
443 result->index_id = m_find_index.value(0).toUInt();
444 result->word_id = m_find_index.value(1).toUInt();
445 QByteArray sentence_ids = m_find_index.value(2).toByteArray();
446 QDataStream ds(sentence_ids);
451 result->sentence_ids.insert(sentence_id);
453 result->is_stop_word = m_find_index.value(3).toBool();
459 void TM::IndexDatabase::insert_index(WordDatabase::pointer word_database,
460 sentence_data_type::pointer source, quint32 target_id)
462 QString const &string = source->sentence;
463 for(QPair<int,int> const & range: source->words)
465 QString word = string.mid(
466 range.first, range.second - range.first + 1);
467 if(word.size() <= 2) continue;
470 word_id = word_database->word_id(word);
473 index_data_type::pointer index_data = find_index(word_id);
476 index_data->sentence_ids.insert(target_id);
477 // レコードが大きくなりすぎた場合、ストップワードとする。
478 if(m_index_limit < index_data->sentence_ids.size())
480 index_data->sentence_ids.clear();
481 index_data->is_stop_word = true;
483 update_index(index_data);
487 index_data = index_data_type::create();
488 index_data->word_id = word_id;
489 index_data->sentence_ids.insert(target_id);
490 index_data->is_stop_word = false;
491 insert_index(index_data);
496 void TM::IndexDatabase::insert_index(index_data_type::pointer index_data)
500 QByteArray sentence_ids;
501 QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
502 for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
504 m_insert_index.bindValue(0, index_data->word_id);
505 m_insert_index.bindValue(1, sentence_ids);
506 m_insert_index.bindValue(2, index_data->is_stop_word);
508 exec(m_insert_index, Q_FUNC_INFO);
511 void TM::IndexDatabase::update_index(index_data_type::pointer index_data)
515 QByteArray sentence_ids;
516 QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
517 for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
519 m_update_index.bindValue(0, index_data->word_id);
520 m_update_index.bindValue(1, sentence_ids);
521 m_update_index.bindValue(2, index_data->is_stop_word);
522 m_update_index.bindValue(3, index_data->index_id);
524 exec(m_update_index, Q_FUNC_INFO);
527 TM::IndexDatabase::pointer TM::IndexDatabase::create(Settings *settings, QString dbname)
529 return pointer(new IndexDatabase(settings, dbname));
532 // Database -------------------------------------------------------------------
534 TM::Database::Database(Settings *settings, Service *service)
536 , m_settings(settings)
539 qRegisterMetaType<sentence_data_type::pointer>();
542 TM::Database::~Database()
547 * \brief データベースのセットアップを行います。
549 * データベース接続を開いたスレッド内でしか使えない制限のため、スレッドの
552 void TM::Database::setup()
555 m_sentence_cache_limit = m_settings->value(
556 "Database/sentence-cache-limit", QVariant::fromValue(20)).toInt();
558 m_index_cache_limit = m_settings->value(
559 "Database/index-cache-limit", QVariant::fromValue(20)).toInt();
561 // データベース用のフォルダが無ければ作成する。
562 if(!m_settings->contains(TMDATABASE_ROOT_PATH_KEY)) qFatal("An error occured while find settings in Database().");
563 QString path = m_settings->value(TMDATABASE_ROOT_PATH_KEY).toString();
565 if(!dir.exists()) dir.mkpath(path);
568 m_site_database = SiteDatabase::create(m_settings);
572 * \brief 引数で指定された言語の単語データベースを開きます。
576 * 言語プラグインを読み込んだ時に呼び出すことを想定しています。
577 * 言語プラグインは、TM::Serviceで読み込まれます。
579 void TM::Database::open_word_database(int code, QString name)
581 m_language_map.insert(code, name);
582 m_word_databases.insert(code, WordDatabase::create(m_settings, name));
586 * \brief 引数として与えられたサイトに対応するIDを返します。
588 quint32 TM::Database::find_site_id(QString host_name)
590 quint32 result = m_site_database->site_id(host_name);
594 void TM::Database::find_sentence(QString site_name, int scode, int tcode,
595 sentence_data_type::pointer source, int token)
597 sentence_data_type::pointer result;
599 quint32 site_id = find_site_id(site_name);
602 SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
604 SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
608 quint32 source_id = sdb->find_sentence_id_with_context(
609 source->sentence, source->previous_crc, source->next_crc);
612 result = tdb->find_sentence_by_source_id(source_id);
613 if(result) result->quality = 101;
615 // 文脈込で発見できない場合、文脈無視で検索を試みる。
618 source_id = sdb->find_sentence_id(source->sentence);
619 if(source_id) result = tdb->find_sentence_by_source_id(source_id);
620 if(result) result->quality = 100;
623 if(result) // 検索結果を返す。
625 QMetaObject::invokeMethod(
626 m_service, "sentence_found",
627 Qt::QueuedConnection,
628 Q_ARG(sentence_data_type::pointer, result),
629 Q_ARG(qint32, token));
631 else // 検索できなかったことを通知する。
633 QMetaObject::invokeMethod(
634 m_service, "sentence_not_found",
635 Qt::QueuedConnection,
636 Q_ARG(QString, site_name),
637 Q_ARG(qint32, scode),
638 Q_ARG(qint32, tcode),
639 Q_ARG(QString, source->sentence),
640 Q_ARG(qint32, token));
644 void TM::Database::insert_sentence(
645 QString site_name, quint32 scode, quint32 tcode,
646 sentence_data_type::pointer source,
647 sentence_data_type::pointer target)
650 assert(!site_name.isEmpty());
653 assert(source->source_id == 0);
654 assert(source->json.isEmpty());
655 assert(target->previous_crc == 0);
656 assert(target->next_crc == 0);
658 quint32 site_id = find_site_id(site_name);
663 SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
667 // 現在、前後の文脈が一致する場合のみ、IDを検索できたこととしている。
668 // 文脈が一致しない場合、新たにIDを付与する。
669 quint32 source_id = source->sentence_id;
670 if(!source_id) source_id = sdb->find_sentence_id_with_context(
671 source->sentence, source->previous_crc, source->next_crc);
674 source->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
676 source_id = sdb->find_sentence_id_with_context(
677 source->sentence, source->previous_crc, source->next_crc);
682 SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
685 target->source_id = source_id;
686 target->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
689 // 現在、原文IDに対して登録できる訳文は一つのみ。
690 sentence_data_type::pointer target_sentence =
691 tdb->find_sentence_by_source_id(source_id);
692 if(target_sentence) // 訳文があった場合、上書きして更新する。
694 target->sentence_id = target_sentence->sentence_id;
699 tdb->insert(target); // 訳文が無かった場合、新規挿入する。
700 // 挿入後、検索することで訳文IDを取得する。
701 target_sentence = tdb->find_sentence_by_source_id(source_id);
703 assert(target_sentence);
704 quint32 target_id = target_sentence->sentence_id;
705 assert(target_id); // 挿入あるいは更新しているので、IDがある。
708 IndexDatabase::pointer index_database =
709 find_index_database(site_id, scode, tcode);
710 assert(index_database);
711 WordDatabase::pointer word_database = find_word_database(scode);
712 assert(word_database);
713 index_database->insert_index(word_database, source, target_id);
716 QString TM::Database::find_language_name(int code) const
719 QMap<int, QString>::const_iterator it = m_language_map.find(code);
720 if(it != m_language_map.end()) result = it.value();
725 * \brief 引数として与えられた単語に対して一意な番号を返します。
729 int TM::Database::find_word_id(int code, QString word)
731 QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
732 assert(it != m_word_databases.end());
733 int result = (*it)->word_id(word);
738 TM::WordDatabase::pointer TM::Database::find_word_database(int code)
740 QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
741 assert(it != m_word_databases.end());
746 * \brief 原文の単語IDと訳文の文IDとの照応を保持するデータベースを返します。
747 * \param site_id サイトのID。
748 * \param scode 原文の言語コード。
749 * \param tcode 訳文の言語コード。
750 * \return データベースへのポインタ。
752 * このメンバは接続をキャッシュしています。
754 TM::IndexDatabase::pointer TM::Database::find_index_database(
755 quint32 site_id, int scode, int tcode)
761 // キャッシュが大きすぎる場合、消去する。
762 if(m_index_cache_limit < m_index_databases.size()) m_index_databases.clear();
765 QString dbname, sname, tname, site_name;
766 sname = find_language_name(scode).toLower();
767 assert(!sname.isEmpty());
770 tname = find_language_name(tcode).toLower();
771 assert(!tname.isEmpty());
775 site_name = QString::number(site_id);
776 assert(!site_name.isEmpty());
778 if(!site_name.isEmpty())
780 dbname = QString("index-");
781 dbname += site_name + "-" + sname + "-" + tname + ".db";
784 IndexDatabase::pointer result;
786 if(!dbname.isEmpty())
788 QMap<QString, IndexDatabase::pointer>::iterator it =
789 m_index_databases.find(dbname);
790 if(it == m_index_databases.end())
791 it = m_index_databases.insert(dbname, IndexDatabase::create(m_settings, dbname));
792 assert(it != m_index_databases.end());
794 if(it != m_index_databases.end()) result = it.value();
801 * \brief 引数として与えられたサイトと言語用のセンテンス・データベースを返します。
802 * \param site_id サイトを識別する番号。
805 * センテンス・データベースは、設定によって決められた数まで接続をキャッシュします。
806 * このメンバは、キャッシュ内にデータベース接続があればそれを返し、無ければ接続します。
808 TM::SentenceDatabase::pointer
809 TM::Database::find_sentence_database(quint32 site_id, int code)
811 typedef QMap<QPair<quint32, int>, SentenceDatabase::pointer>::iterator iterator;
815 // キャッシュが大きすぎる場合、消去する。
816 if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear();
818 // キャッシュにデータベースが無い場合、作成する。
819 QPair<quint32, int> key(site_id, code);
820 iterator it = m_sentence_databases.find(key);
821 if(it == m_sentence_databases.end())
823 QString name = find_language_name(code);
824 assert(!name.isEmpty());
825 it = m_sentence_databases.insert(
826 key, SentenceDatabase::create(m_settings, site_id, name));