OSDN Git Service

チケット #35438 訳文を元に戻せない 対応完了♪
[wordring-tm/wordring-tm.git] / proxy / tmdatabase.cpp
1 #include "settings.h"
2 #include "tmdatabase.h"
3 #include "tmservice.h"
4
5 #include <QSqlQuery>
6 #include <QSqlError>
7 #include <QDir>
8 #include <QLocale>
9 #include <QDateTime>
10 #include <QDataStream>
11
12 #include "debug.h"
13
14 // DatabaseBase ---------------------------------------------------------------
15
16 bool TM::DatabaseBase::open(char const *message)
17 {
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();
22         if(!ret)
23         {
24                 error(message);
25                 assert(false);
26         }
27         return ret;
28 }
29
30 QSqlQuery TM::DatabaseBase::prepare(char const *sql, char const *message)
31 {
32         QSqlQuery result(m_database);
33         bool ret = result.prepare(sql);
34         if(!ret)
35         {
36                 error(message, &result);
37                 assert(false);
38         }
39         return result;
40 }
41
42 bool TM::DatabaseBase::exec(QSqlQuery &query, char const *message)
43 {
44         bool ret = query.exec();
45         if(!ret)
46         {
47                 error(message, &query);
48                 assert(false);
49         }
50         return ret;
51 }
52
53 QSqlQuery TM::DatabaseBase::exec(char const *sql, char const *message)
54 {
55         QSqlQuery result = m_database.exec(sql);
56         if(result.lastError().isValid())
57         {
58                 error(message, &result);
59                 assert(false);
60         }
61         return result;
62 }
63
64 void TM::DatabaseBase::error(QString message, QSqlQuery *query)
65 {
66         QString s;
67         if(query) s = query->lastError().text();
68         else s = m_database.lastError().text();
69
70         qCritical() << message << s;
71 }
72
73 // SiteDatabase ---------------------------------------------------------------
74
75 TM::SiteDatabase::SiteDatabase(Settings *settings)
76 {
77         // キャッシュの最大値を設定。
78         m_cache_limit = settings->value(
79                 "SiteDatabase/cache-limit", QVariant::fromValue(1024)).toInt();
80
81         // データベースを開く。
82         m_database_name = settings->value(TMDATABASE_ROOT_PATH_KEY).toString() + "/site.db";
83         open(Q_FUNC_INFO);
84
85         // テーブル作成。
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);
89
90         exec("CREATE INDEX IF NOT EXISTS host_name_index ON sites(host_name);", Q_FUNC_INFO);
91
92         // クエリ作成。
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);
95 }
96
97 TM::SiteDatabase::~SiteDatabase()
98 {
99         if(m_database.isOpen()) m_database.close();
100 }
101
102 /*!
103  * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
104  */
105 quint32 TM::SiteDatabase::site_id(QString domain)
106 {
107         // キャッシュが大きくなりすぎた場合、消去する。
108         if(m_cache_limit < m_cache.size()) m_cache.clear();
109         // キャッシュを検索。
110         QMap<QString, quint32>::const_iterator it = m_cache.find(domain);
111         if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
112
113         // キャッシュにない場合、データベースを検索。
114         quint32 result = find_site_id(domain);
115         // データベースにない場合、データベースに登録。
116         if(!result)
117         {
118                 insert_site(domain);
119                 result = find_site_id(domain);
120         }
121         assert(0 < result);
122         // キャッシュに追加。
123         m_cache.insert(domain, result);
124
125         return result;
126 }
127
128 /*!
129  * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
130  *
131  * 登録されていない場合、0を返します。
132  */
133 quint32 TM::SiteDatabase::find_site_id(QString host_name)
134 {
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();
138
139         return 0;
140 }
141
142 void TM::SiteDatabase::insert_site(QString host_name)
143 {
144         m_insert_site.bindValue(0, host_name);
145         exec(m_insert_site, Q_FUNC_INFO);
146 }
147
148 TM::SiteDatabase::pointer TM::SiteDatabase::create(Settings *settings)
149 {
150         return pointer(new SiteDatabase(settings));
151 }
152
153 // WordDatabase ---------------------------------------------------------------
154
155 TM::WordDatabase::WordDatabase(Settings *settings, QString name)
156 {
157         // キャッシュの最大値を設定。
158         m_cache_limit = settings->value(
159                 "WordDatabase/cache-limit", QVariant::fromValue(100 * 1024)).toInt();
160
161         // データベースを開く。
162         m_database_name = settings->value(
163                 TMDATABASE_ROOT_PATH_KEY).toString() + "/word-" + name.toLower() + ".db";
164         open(Q_FUNC_INFO);
165
166         // テーブル作成。
167         exec("CREATE TABLE IF NOT EXISTS words("
168                         "word_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
169                         "word TEXT UNIQUE);", Q_FUNC_INFO);
170
171         exec("CREATE INDEX IF NOT EXISTS word_index ON words(word);", Q_FUNC_INFO);
172
173         // クエリ作成。
174         m_find_word_id = prepare(
175                 "SELECT word_id FROM words WHERE word=?;", Q_FUNC_INFO);
176
177         m_insert_word = prepare("INSERT INTO words(word) VALUES(?);", Q_FUNC_INFO);
178 }
179
180 TM::WordDatabase::~WordDatabase()
181 {
182         if(m_database.isOpen()) m_database.close();
183 }
184
185 int TM::WordDatabase::word_id(QString word)
186 {
187         // キャッシュが大きくなりすぎた場合、消去する。
188         if(m_cache_limit < m_cache.size()) m_cache.clear();
189         // キャッシュを検索。
190         QMap<QString, int>::const_iterator it = m_cache.find(word);
191         if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
192
193         // キャッシュにない場合、データベースを検索。
194         int result = find_word_id(word);
195         // データベースにない場合、データベースに登録。
196         if(!result)
197         {
198                 insert_word(word);
199                 result = find_word_id(word);
200         }
201         assert(0 < result);
202         // キャッシュに追加。
203         m_cache.insert(word, result);
204
205         return result;
206 }
207
208 int TM::WordDatabase::find_word_id(QString word)
209 {
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();
213
214         return 0;
215 }
216
217 void TM::WordDatabase::insert_word(QString word)
218 {
219         m_insert_word.bindValue(0, word);
220         exec(m_insert_word, Q_FUNC_INFO);
221 }
222
223 TM::WordDatabase::pointer TM::WordDatabase::create(Settings *settings, QString name)
224 {
225         return pointer(new WordDatabase(settings, name));
226 }
227
228 // SentenceDatabase -----------------------------------------------------------
229
230 TM::SentenceDatabase::SentenceDatabase(Settings *settings, int site_id, QString name)
231 {
232         // データベースを開く。
233         m_database_name =
234                 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
235                 + "/sentence-" + QString::number(site_id) + "-" + name.toLower() + ".db";
236         open(Q_FUNC_INFO);
237
238         // テーブル作成。
239         exec("CREATE TABLE IF NOT EXISTS sentences("
240                         "sentence_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
241                         "source_id INTEGER,"
242                         "sentence TEXT,"
243                         "json TEXT,"
244                         "crc INTEGER,"
245                         "previous_crc INTEGER,"
246                         "next_crc INTEGER,"
247                         "user_id INTEGER,"
248                         "time INTEGER);");
249
250         // インデックス作成。
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);
253
254         // クエリ作成。
255         m_find_sentence_id = prepare(
256                 "SELECT sentence_id FROM sentences WHERE sentence=?;", Q_FUNC_INFO);
257
258         m_find_sentence_id_with_context = prepare(
259                 "SELECT sentence_id FROM sentences "
260                         "WHERE sentence=? "
261                         "AND previous_crc=? "
262                         "AND next_crc=?;", Q_FUNC_INFO);
263
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 "
272                         "source_id=?,"
273                         "sentence=?,"
274                         "json=?,"
275                         "crc=?,"
276                         "previous_crc=?,"
277                         "next_crc=?,"
278                         "user_id=?,"
279                         "time=? "
280                 "WHERE sentence_id=?;", Q_FUNC_INFO);
281
282         m_delete_sentence = prepare("DELETE FROM sentences WHERE source_id=?;");
283 }
284
285 TM::SentenceDatabase::~SentenceDatabase()
286 {
287         if(m_database.isOpen()) m_database.close();
288 }
289
290 quint32 TM::SentenceDatabase::find_sentence_id(QString sentence)
291 {
292         m_find_sentence_id.bindValue(0, sentence);
293         exec(m_find_sentence_id, Q_FUNC_INFO);
294
295         if(!m_find_sentence_id.next()) return 0;
296         return m_find_sentence_id.value(0).toUInt();
297 }
298
299 quint32 TM::SentenceDatabase::find_sentence_id_with_context(
300                 QString sentence, quint32 previous_crc, quint32 next_crc)
301 {
302         m_find_sentence_id_with_context.bindValue(0, sentence);
303         m_find_sentence_id_with_context.bindValue(1, previous_crc);
304         m_find_sentence_id_with_context.bindValue(2, next_crc);
305         exec(m_find_sentence_id_with_context, Q_FUNC_INFO);
306
307         if(!m_find_sentence_id_with_context.next()) return 0;
308         return m_find_sentence_id_with_context.value(0).toUInt();
309 }
310
311 TM::sentence_data_type::pointer
312 TM::SentenceDatabase::find_sentence_by_source_id(int source_id)
313 {
314         assert(source_id);
315
316         sentence_data_type::pointer result;
317
318         m_find_sentence_by_source_id.bindValue(0, source_id);
319         exec(m_find_sentence_by_source_id, Q_FUNC_INFO);
320
321         if(!m_find_sentence_by_source_id.next()) return result;
322
323         return stuff_value(&m_find_sentence_by_source_id);
324 }
325
326 void TM::SentenceDatabase::insert(sentence_data_type::pointer sentence_data)
327 {
328         assert(m_database.isValid());
329         assert(m_database.isOpen());
330
331         m_insert_sentence.bindValue(0, sentence_data->source_id);
332         m_insert_sentence.bindValue(1, sentence_data->sentence);
333         m_insert_sentence.bindValue(2, sentence_data->json);
334         m_insert_sentence.bindValue(3, sentence_data->crc);
335         m_insert_sentence.bindValue(4, sentence_data->previous_crc);
336         m_insert_sentence.bindValue(5, sentence_data->next_crc);
337         m_insert_sentence.bindValue(6, sentence_data->user_id);
338         m_insert_sentence.bindValue(7, sentence_data->time);
339
340         exec(m_insert_sentence, Q_FUNC_INFO);
341 }
342
343 void TM::SentenceDatabase::update(sentence_data_type::pointer sentence_data)
344 {
345         assert(m_database.isValid());
346         assert(m_database.isOpen());
347
348         m_update_sentence.bindValue(0, sentence_data->source_id);
349         m_update_sentence.bindValue(1, sentence_data->sentence);
350         m_update_sentence.bindValue(2, sentence_data->json);
351         m_update_sentence.bindValue(3, sentence_data->crc);
352         m_update_sentence.bindValue(4, sentence_data->previous_crc);
353         m_update_sentence.bindValue(5, sentence_data->next_crc);
354         m_update_sentence.bindValue(6, sentence_data->user_id);
355         m_update_sentence.bindValue(7, sentence_data->time);
356         m_update_sentence.bindValue(8, sentence_data->sentence_id);
357
358         exec(m_update_sentence, Q_FUNC_INFO);
359 }
360
361 void TM::SentenceDatabase::remove(quint32 source_id)
362 {
363         assert(source_id);
364         assert(m_database.isValid());
365         assert(m_database.isOpen());
366
367         m_delete_sentence.bindValue(0, source_id);
368
369         exec(m_delete_sentence, Q_FUNC_INFO);
370 }
371
372 TM::sentence_data_type::pointer
373 TM::SentenceDatabase::stuff_value(QSqlQuery *query)
374 {
375         assert(query->isValid());
376         sentence_data_type::pointer result = sentence_data_type::create();
377
378         result->sentence_id = query->value(0).toUInt();
379         result->source_id = query->value(1).toUInt();
380         result->sentence = query->value(2).toString();
381         result->json = query->value(3).toByteArray();
382         result->crc = query->value(4).toUInt();
383         result->previous_crc = query->value(5).toUInt();
384         result->next_crc = query->value(6).toUInt();
385         result->user_id = query->value(7).toUInt();
386         result->time = query->value(8).toUInt();
387
388         return result;
389 }
390
391 TM::SentenceDatabase::pointer TM::SentenceDatabase::create(
392                 Settings *settings, int site_id, QString name)
393 {
394         return pointer(new SentenceDatabase(settings, site_id, name));
395 }
396
397
398 // IndexDatabase --------------------------------------------------------------
399
400 TM::index_data_type::index_data_type()
401         : index_id(0)
402         , word_id(0)
403         , is_stop_word(false)
404 {
405 }
406
407 TM::index_data_type::pointer TM::index_data_type::create()
408 {
409         return pointer(new index_data_type());
410 }
411
412 TM::IndexDatabase::IndexDatabase(Settings *settings, QString dbname)
413 {
414         m_index_limit = 20 * 1024;
415
416         m_database_name =
417                 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
418                 + "/" + dbname;
419         open(Q_FUNC_INFO);
420
421         // テーブル作成。
422         exec("CREATE TABLE IF NOT EXISTS indexes("
423                         "index_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
424                         "word_id INTEGER UNIQUE NOT NULL,"
425                         "sentence_ids BLOB,"
426                         "is_stop_word BOOLEAN);", Q_FUNC_INFO);
427
428         exec("CREATE INDEX IF NOT EXISTS word_id_index ON indexes(word_id);", Q_FUNC_INFO);
429
430         // クエリ作成。
431         m_find_index = prepare(
432                 "SELECT * FROM indexes WHERE word_id=?;", Q_FUNC_INFO);
433         m_insert_index = prepare(
434                 "INSERT INTO indexes(word_id, sentence_ids, is_stop_word) "
435                         "VALUES(?, ?, ?);", Q_FUNC_INFO);
436         m_update_index = prepare(
437                 "UPDATE indexes SET "
438                         "word_id=?,"
439                         "sentence_ids=?,"
440                         "is_stop_word=? "
441                 "WHERE index_id=?;", Q_FUNC_INFO);
442 }
443
444 TM::index_data_type::pointer TM::IndexDatabase::find_index(quint32 word_id)
445 {
446         assert(word_id);
447
448         m_find_index.bindValue(0, word_id);
449         exec(m_find_index, Q_FUNC_INFO);
450
451         index_data_type::pointer result;
452
453         if(m_find_index.next())
454         {
455                 result = index_data_type::create();
456                 result->index_id = m_find_index.value(0).toUInt();
457                 result->word_id = m_find_index.value(1).toUInt();
458                 QByteArray sentence_ids = m_find_index.value(2).toByteArray();
459                 QDataStream ds(sentence_ids);
460                 while(!ds.atEnd())
461                 {
462                         quint32 sentence_id;
463                         ds >> sentence_id;
464                         result->sentence_ids.insert(sentence_id);
465                 }
466                 result->is_stop_word = m_find_index.value(3).toBool();
467         }
468
469         return result;
470 }
471
472 void TM::IndexDatabase::insert_index(WordDatabase::pointer word_database,
473                 sentence_data_type::pointer source, quint32 target_id)
474 {
475         QString const &string = source->sentence;
476         for(QPair<int,int> const & range: source->words)
477         {
478                 QString word = string.mid(
479                                         range.first, range.second - range.first + 1);
480                 if(word.size() <= 2) continue;
481
482                 int word_id = 0;
483                 word_id = word_database->word_id(word);
484                 assert(word_id);
485
486                 index_data_type::pointer index_data = find_index(word_id);
487                 if(index_data)
488                 {
489                         index_data->sentence_ids.insert(target_id);
490                         // レコードが大きくなりすぎた場合、ストップワードとする。
491                         if(m_index_limit < index_data->sentence_ids.size())
492                         {
493                                 index_data->sentence_ids.clear();
494                                 index_data->is_stop_word = true;
495                         }
496                         update_index(index_data);
497                 }
498                 else
499                 {
500                         index_data = index_data_type::create();
501                         index_data->word_id = word_id;
502                         index_data->sentence_ids.insert(target_id);
503                         index_data->is_stop_word = false;
504                         insert_index(index_data);
505                 }
506         }
507 }
508
509 void TM::IndexDatabase::insert_index(index_data_type::pointer index_data)
510 {
511         assert(index_data);
512
513         QByteArray sentence_ids;
514         QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
515         for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
516
517         m_insert_index.bindValue(0, index_data->word_id);
518         m_insert_index.bindValue(1, sentence_ids);
519         m_insert_index.bindValue(2, index_data->is_stop_word);
520
521         exec(m_insert_index, Q_FUNC_INFO);
522 }
523
524 void TM::IndexDatabase::update_index(index_data_type::pointer index_data)
525 {
526         assert(index_data);
527
528         QByteArray sentence_ids;
529         QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
530         for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
531
532         m_update_index.bindValue(0, index_data->word_id);
533         m_update_index.bindValue(1, sentence_ids);
534         m_update_index.bindValue(2, index_data->is_stop_word);
535         m_update_index.bindValue(3, index_data->index_id);
536
537         exec(m_update_index, Q_FUNC_INFO);
538 }
539
540 TM::IndexDatabase::pointer TM::IndexDatabase::create(Settings *settings, QString dbname)
541 {
542         return pointer(new IndexDatabase(settings, dbname));
543 }
544
545 // Database -------------------------------------------------------------------
546
547 TM::Database::Database(Settings *settings, Service *service)
548         : QObject(0)
549         , m_settings(settings)
550         , m_service(service)
551 {
552         qRegisterMetaType<sentence_data_type::pointer>();
553 }
554
555 TM::Database::~Database()
556 {
557 }
558
559 /*!
560  * \brief データベースのセットアップを行います。
561  *
562  * データベース接続を開いたスレッド内でしか使えない制限のため、スレッドの
563  * イベントループから処理します。
564  */
565 void TM::Database::setup()
566 {
567         // キャッシュの最大値を設定。
568         m_sentence_cache_limit = m_settings->value(
569                 "Database/sentence-cache-limit", QVariant::fromValue(20)).toInt();
570
571         m_index_cache_limit = m_settings->value(
572                 "Database/index-cache-limit", QVariant::fromValue(20)).toInt();
573
574         // データベース用のフォルダが無ければ作成する。
575         if(!m_settings->contains(TMDATABASE_ROOT_PATH_KEY)) qFatal("An error occured while find settings in Database().");
576         QString path = m_settings->value(TMDATABASE_ROOT_PATH_KEY).toString();
577         QDir dir(path);
578         if(!dir.exists()) dir.mkpath(path);
579
580         // データベースを開く。
581         m_site_database = SiteDatabase::create(m_settings);
582 }
583
584 /*!
585  * \brief 引数で指定された言語の単語データベースを開きます。
586  * \param code 言語コード。
587  * \param name 言語名。
588  *
589  * 言語プラグインを読み込んだ時に呼び出すことを想定しています。
590  * 言語プラグインは、TM::Serviceで読み込まれます。
591  */
592 void TM::Database::open_word_database(int code, QString name)
593 {
594         m_language_map.insert(code, name);
595         m_word_databases.insert(code, WordDatabase::create(m_settings, name));
596 }
597
598 /*!
599  * \brief 引数として与えられたサイトに対応するIDを返します。
600  */
601 quint32 TM::Database::find_site_id(QString host_name)
602 {
603         quint32 result = m_site_database->site_id(host_name);
604         return result;
605 }
606
607 /*!
608  * \brief センテンスを検索して結果をサービスに返します。
609  * \param site_name
610  * \param scode
611  * \param tcode
612  * \param source
613  * \param token
614  */
615 void TM::Database::find_sentence(QString site_name, int scode, int tcode,
616                                                         sentence_data_type::pointer source, int token)
617 {
618         sentence_data_type::pointer result;
619
620         quint32 site_id = find_site_id(site_name);
621         assert(site_id);
622
623         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
624         assert(sdb);
625         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
626         assert(tdb);
627
628         // 文脈込の検索を試みる。
629         quint32 source_id = sdb->find_sentence_id_with_context(
630                                 source->sentence, source->previous_crc, source->next_crc);
631         if(source_id)
632         {
633                 result = tdb->find_sentence_by_source_id(source_id);
634                 if(result) result->quality = 101;
635         }
636         // 文脈込で発見できない場合、文脈無視で検索を試みる。
637         if(!result)
638         {
639                 source_id = sdb->find_sentence_id(source->sentence);
640                 if(source_id) result = tdb->find_sentence_by_source_id(source_id);
641                 if(result)
642                 {
643                         result->quality = 100;
644                         result->source_id = source_id;
645                 }
646         }
647
648         if(result) // 検索結果を返す。
649         {
650                 QMetaObject::invokeMethod(
651                         m_service, "sentence_found",
652                         Qt::QueuedConnection,
653                         Q_ARG(sentence_data_type::pointer, result),
654                         Q_ARG(qint32, token));
655         }
656         else // 検索できなかったことを通知し、サービスはサーバに問い合わせる。
657         {
658                 QMetaObject::invokeMethod(
659                         m_service, "sentence_not_found",
660                         Qt::QueuedConnection,
661                         Q_ARG(QString, site_name),
662                         Q_ARG(qint32, scode),
663                         Q_ARG(qint32, tcode),
664                         Q_ARG(QString, source->sentence),
665                         Q_ARG(qint32, token));
666         }
667 }
668
669 void TM::Database::insert_sentence(
670                 QString site_name, quint32 scode, quint32 tcode,
671                 sentence_data_type::pointer source,
672                 sentence_data_type::pointer target, qint32 token)
673 {
674         // 入力の検証。
675         assert(!site_name.isEmpty());
676         assert(scode);
677         assert(tcode);
678         assert(source->source_id == 0);
679         assert(source->json.isEmpty());
680         assert(target->previous_crc == 0);
681         assert(target->next_crc == 0);
682
683         quint32 site_id = find_site_id(site_name);
684         assert(site_id);
685         if(!site_id) return;
686
687         // 原文の更新。
688         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
689         assert(sdb);
690
691         // 原文IDの検索、無い場合登録。
692         // 現在、前後の文脈が一致する場合のみ、IDを検索できたこととしている。
693         // 文脈が一致しない場合、新たにIDを付与する。
694         quint32 source_id = source->sentence_id;
695         if(!source_id) source_id = sdb->find_sentence_id_with_context(
696                                 source->sentence, source->previous_crc, source->next_crc);
697         if(!source_id)
698         {
699                 source->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
700                 sdb->insert(source);
701                 source_id = sdb->find_sentence_id_with_context(
702                                         source->sentence, source->previous_crc, source->next_crc);
703         }
704         assert(source_id);
705
706         // 訳文の更新。
707         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
708         assert(tdb);
709
710         target->source_id = source_id;
711         target->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
712
713         // 原文IDに対応する訳文を検索する。
714         // 現在、原文IDに対して登録できる訳文は一つのみ。
715         sentence_data_type::pointer target_sentence =
716                         tdb->find_sentence_by_source_id(source_id);
717         if(target_sentence) // 訳文があった場合、上書きして更新する。
718         {
719                 target->sentence_id = target_sentence->sentence_id;
720                 tdb->update(target);
721         }
722         else
723         {
724                 tdb->insert(target); // 訳文が無かった場合、新規挿入する。
725                 // 挿入後、検索することで訳文IDを取得する。
726                 target_sentence = tdb->find_sentence_by_source_id(source_id);
727         }
728         assert(target_sentence);
729         quint32 target_id = target_sentence->sentence_id;
730         assert(target_id); // 挿入あるいは更新しているので、IDがある。
731
732         // 単語の登録。
733         IndexDatabase::pointer index_database =
734                         find_index_database(site_id, scode, tcode);
735         assert(index_database);
736         WordDatabase::pointer word_database = find_word_database(scode);
737         assert(word_database);
738         index_database->insert_index(word_database, source, target_id);
739
740         // サービスに原文IDを返す。
741         QMetaObject::invokeMethod(
742                 m_service, "sentence_inserted",
743                 Qt::QueuedConnection,
744                 Q_ARG(quint32, source_id),
745                 Q_ARG(quint32, target_id),
746                 Q_ARG(qint32, token));
747 }
748
749 /*!
750  * \brief データベースから引数として与えられた原文に対応する訳文を削除します。
751  * \param site_name
752  * \param scode
753  * \param tcode
754  * \param source
755  */
756 void TM::Database::remove_sentence(QString site_name, quint32 scode, quint32 tcode,
757                                                         sentence_data_type::pointer source)
758 {
759         qDebug() << "TM::Database::remove_sentence()";
760
761         // 入力の検証。
762         assert(!site_name.isEmpty());
763         assert(scode);
764         assert(tcode);
765         assert(source->source_id == 0);
766         assert(source->json.isEmpty());
767
768         quint32 site_id = find_site_id(site_name);
769         assert(site_id);
770         if(!site_id) return;
771
772         // 原文IDの検索。
773         //SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
774         //assert(sdb);
775
776         // 原文IDの検索、無い場合、訳文を引き出せないので諦めるしかない。
777         quint32 source_id = source->sentence_id;
778         //if(!source_id) source_id = sdb->find_sentence_ids(source->sentence);
779
780         if(!source_id) qDebug() << "Database::remove_sentence(): Do not find source_id";
781         if(!source_id) return;
782
783         // 訳文の消去。
784         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
785         assert(tdb);
786
787         tdb->remove(source_id);
788 }
789
790 QString TM::Database::find_language_name(int code) const
791 {
792         QString result;
793         QMap<int, QString>::const_iterator it = m_language_map.find(code);
794         if(it != m_language_map.end()) result = it.value();
795         return result;
796 }
797
798 /*!
799  * \brief 引数として与えられた単語に対して一意な番号を返します。
800  * \param code 言語コード。
801  * \param word 単語。
802  */
803 int TM::Database::find_word_id(int code, QString word)
804 {
805         QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
806         assert(it != m_word_databases.end());
807         int result = (*it)->word_id(word);
808         assert(result);
809         return result;
810 }
811
812 TM::WordDatabase::pointer TM::Database::find_word_database(int code)
813 {
814         QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
815         assert(it != m_word_databases.end());
816         return it.value();
817 }
818
819 /*!
820  * \brief 原文の単語IDと訳文の文IDとの照応を保持するデータベースを返します。
821  * \param site_id サイトのID。
822  * \param scode 原文の言語コード。
823  * \param tcode 訳文の言語コード。
824  * \return データベースへのポインタ。
825  *
826  * このメンバは接続をキャッシュしています。
827  */
828 TM::IndexDatabase::pointer TM::Database::find_index_database(
829                 quint32 site_id, int scode, int tcode)
830 {
831         assert(site_id);
832         assert(scode);
833         assert(tcode);
834
835         // キャッシュが大きすぎる場合、消去する。
836         if(m_index_cache_limit < m_index_databases.size()) m_index_databases.clear();
837
838         // データベース名を作成する。
839         QString dbname, sname, tname, site_name;
840         sname = find_language_name(scode).toLower();
841         assert(!sname.isEmpty());
842         if(!sname.isEmpty())
843         {
844                 tname = find_language_name(tcode).toLower();
845                 assert(!tname.isEmpty());
846         }
847         if(!tname.isEmpty())
848         {
849                 site_name = QString::number(site_id);
850                 assert(!site_name.isEmpty());
851         }
852         if(!site_name.isEmpty())
853         {
854                 dbname = QString("index-");
855                 dbname += site_name + "-" + sname + "-" + tname + ".db";
856         }
857
858         IndexDatabase::pointer result;
859
860         if(!dbname.isEmpty())
861         {
862                 QMap<QString, IndexDatabase::pointer>::iterator it =
863                         m_index_databases.find(dbname);
864                 if(it == m_index_databases.end())
865                         it = m_index_databases.insert(dbname, IndexDatabase::create(m_settings, dbname));
866                 assert(it != m_index_databases.end());
867
868                 if(it != m_index_databases.end()) result = it.value();
869         }
870
871         return result;
872 }
873
874 /*!
875  * \brief 引数として与えられたサイトと言語用のセンテンス・データベースを返します。
876  * \param site_id サイトを識別する番号。
877  * \param code 言語コード。
878  *
879  * センテンス・データベースは、設定によって決められた数まで接続をキャッシュします。
880  * このメンバは、キャッシュ内にデータベース接続があればそれを返し、無ければ接続します。
881  */
882 TM::SentenceDatabase::pointer
883 TM::Database::find_sentence_database(quint32 site_id, int code)
884 {
885         typedef QMap<QPair<quint32, int>, SentenceDatabase::pointer>::iterator iterator;
886         assert(site_id);
887         assert(code);
888
889         // キャッシュが大きすぎる場合、消去する。
890         if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear();
891
892         // キャッシュにデータベースが無い場合、作成する。
893         QPair<quint32, int> key(site_id, code);
894         iterator it = m_sentence_databases.find(key);
895         if(it == m_sentence_databases.end())
896         {
897                 QString name = find_language_name(code);
898                 assert(!name.isEmpty());
899                 it = m_sentence_databases.insert(
900                         key, SentenceDatabase::create(m_settings, site_id, name));
901         }
902         assert(it.value());
903
904         return it.value();
905 }
906
907
908
909
910
911
912
913
914
915
916
917
918
919