OSDN Git Service

サーバ - データベース - エディタ - ブラウザの同期実装♪
[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
283 TM::SentenceDatabase::~SentenceDatabase()
284 {
285         if(m_database.isOpen()) m_database.close();
286 }
287
288 quint32 TM::SentenceDatabase::find_sentence_id(QString sentence)
289 {
290         m_find_sentence_id.bindValue(0, sentence);
291         exec(m_find_sentence_id, Q_FUNC_INFO);
292
293         if(!m_find_sentence_id.next()) return 0;
294         return m_find_sentence_id.value(0).toUInt();
295 }
296
297 quint32 TM::SentenceDatabase::find_sentence_id_with_context(
298                 QString sentence, quint32 previous_crc, quint32 next_crc)
299 {
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);
304
305         if(!m_find_sentence_id_with_context.next()) return 0;
306         return m_find_sentence_id_with_context.value(0).toUInt();
307 }
308
309 TM::sentence_data_type::pointer
310 TM::SentenceDatabase::find_sentence_by_source_id(int source_id)
311 {
312         assert(source_id);
313
314         sentence_data_type::pointer result;
315
316         m_find_sentence_by_source_id.bindValue(0, source_id);
317         exec(m_find_sentence_by_source_id, Q_FUNC_INFO);
318
319         if(!m_find_sentence_by_source_id.next()) return result;
320
321         return stuff_value(&m_find_sentence_by_source_id);
322 }
323
324 void TM::SentenceDatabase::insert(sentence_data_type::pointer sentence_data)
325 {
326         assert(m_database.isValid());
327         assert(m_database.isOpen());
328
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);
337
338         exec(m_insert_sentence, Q_FUNC_INFO);
339 }
340
341 void TM::SentenceDatabase::update(sentence_data_type::pointer sentence_data)
342 {
343         assert(m_database.isValid());
344         assert(m_database.isOpen());
345
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);
355
356         exec(m_update_sentence, Q_FUNC_INFO);
357 }
358
359 TM::sentence_data_type::pointer
360 TM::SentenceDatabase::stuff_value(QSqlQuery *query)
361 {
362         assert(query->isValid());
363         sentence_data_type::pointer result = sentence_data_type::create();
364
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();
374
375         return result;
376 }
377
378 TM::SentenceDatabase::pointer TM::SentenceDatabase::create(
379                 Settings *settings, int site_id, QString name)
380 {
381         return pointer(new SentenceDatabase(settings, site_id, name));
382 }
383
384
385 // IndexDatabase --------------------------------------------------------------
386
387 TM::index_data_type::index_data_type()
388         : index_id(0)
389         , word_id(0)
390         , is_stop_word(false)
391 {
392 }
393
394 TM::index_data_type::pointer TM::index_data_type::create()
395 {
396         return pointer(new index_data_type());
397 }
398
399 TM::IndexDatabase::IndexDatabase(Settings *settings, QString dbname)
400 {
401         m_index_limit = 20 * 1024;
402
403         m_database_name =
404                 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
405                 + "/" + dbname;
406         open(Q_FUNC_INFO);
407
408         // テーブル作成。
409         exec("CREATE TABLE IF NOT EXISTS indexes("
410                         "index_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
411                         "word_id INTEGER UNIQUE NOT NULL,"
412                         "sentence_ids BLOB,"
413                         "is_stop_word BOOLEAN);", Q_FUNC_INFO);
414
415         exec("CREATE INDEX IF NOT EXISTS word_id_index ON indexes(word_id);", Q_FUNC_INFO);
416
417         // クエリ作成。
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 "
425                         "word_id=?,"
426                         "sentence_ids=?,"
427                         "is_stop_word=? "
428                 "WHERE index_id=?;", Q_FUNC_INFO);
429 }
430
431 TM::index_data_type::pointer TM::IndexDatabase::find_index(quint32 word_id)
432 {
433         assert(word_id);
434
435         m_find_index.bindValue(0, word_id);
436         exec(m_find_index, Q_FUNC_INFO);
437
438         index_data_type::pointer result;
439
440         if(m_find_index.next())
441         {
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);
447                 while(!ds.atEnd())
448                 {
449                         quint32 sentence_id;
450                         ds >> sentence_id;
451                         result->sentence_ids.insert(sentence_id);
452                 }
453                 result->is_stop_word = m_find_index.value(3).toBool();
454         }
455
456         return result;
457 }
458
459 void TM::IndexDatabase::insert_index(WordDatabase::pointer word_database,
460                 sentence_data_type::pointer source, quint32 target_id)
461 {
462         QString const &string = source->sentence;
463         for(QPair<int,int> const & range: source->words)
464         {
465                 QString word = string.mid(
466                                         range.first, range.second - range.first + 1);
467                 if(word.size() <= 2) continue;
468
469                 int word_id = 0;
470                 word_id = word_database->word_id(word);
471                 assert(word_id);
472
473                 index_data_type::pointer index_data = find_index(word_id);
474                 if(index_data)
475                 {
476                         index_data->sentence_ids.insert(target_id);
477                         // レコードが大きくなりすぎた場合、ストップワードとする。
478                         if(m_index_limit < index_data->sentence_ids.size())
479                         {
480                                 index_data->sentence_ids.clear();
481                                 index_data->is_stop_word = true;
482                         }
483                         update_index(index_data);
484                 }
485                 else
486                 {
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);
492                 }
493         }
494 }
495
496 void TM::IndexDatabase::insert_index(index_data_type::pointer index_data)
497 {
498         assert(index_data);
499
500         QByteArray sentence_ids;
501         QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
502         for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
503
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);
507
508         exec(m_insert_index, Q_FUNC_INFO);
509 }
510
511 void TM::IndexDatabase::update_index(index_data_type::pointer index_data)
512 {
513         assert(index_data);
514
515         QByteArray sentence_ids;
516         QDataStream ds(&sentence_ids, QIODevice::WriteOnly);
517         for(quint32 sentence_id : index_data->sentence_ids) ds << sentence_id;
518
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);
523
524         exec(m_update_index, Q_FUNC_INFO);
525 }
526
527 TM::IndexDatabase::pointer TM::IndexDatabase::create(Settings *settings, QString dbname)
528 {
529         return pointer(new IndexDatabase(settings, dbname));
530 }
531
532 // Database -------------------------------------------------------------------
533
534 TM::Database::Database(Settings *settings, Service *service)
535         : QObject(0)
536         , m_settings(settings)
537         , m_service(service)
538 {
539         qRegisterMetaType<sentence_data_type::pointer>();
540 }
541
542 TM::Database::~Database()
543 {
544 }
545
546 /*!
547  * \brief データベースのセットアップを行います。
548  *
549  * データベース接続を開いたスレッド内でしか使えない制限のため、スレッドの
550  * イベントループから処理します。
551  */
552 void TM::Database::setup()
553 {
554         // キャッシュの最大値を設定。
555         m_sentence_cache_limit = m_settings->value(
556                 "Database/sentence-cache-limit", QVariant::fromValue(20)).toInt();
557
558         m_index_cache_limit = m_settings->value(
559                 "Database/index-cache-limit", QVariant::fromValue(20)).toInt();
560
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();
564         QDir dir(path);
565         if(!dir.exists()) dir.mkpath(path);
566
567         // データベースを開く。
568         m_site_database = SiteDatabase::create(m_settings);
569 }
570
571 /*!
572  * \brief 引数で指定された言語の単語データベースを開きます。
573  * \param code 言語コード。
574  * \param name 言語名。
575  *
576  * 言語プラグインを読み込んだ時に呼び出すことを想定しています。
577  * 言語プラグインは、TM::Serviceで読み込まれます。
578  */
579 void TM::Database::open_word_database(int code, QString name)
580 {
581         m_language_map.insert(code, name);
582         m_word_databases.insert(code, WordDatabase::create(m_settings, name));
583 }
584
585 /*!
586  * \brief 引数として与えられたサイトに対応するIDを返します。
587  */
588 quint32 TM::Database::find_site_id(QString host_name)
589 {
590         quint32 result = m_site_database->site_id(host_name);
591         return result;
592 }
593
594 void TM::Database::find_sentence(QString site_name, int scode, int tcode,
595                                                         sentence_data_type::pointer source, int token)
596 {
597         sentence_data_type::pointer result;
598
599         quint32 site_id = find_site_id(site_name);
600         assert(site_id);
601
602         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
603         assert(sdb);
604         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
605         assert(tdb);
606
607         // 文脈込の検索を試みる。
608         quint32 source_id = sdb->find_sentence_id_with_context(
609                                 source->sentence, source->previous_crc, source->next_crc);
610         if(source_id)
611         {
612                 result = tdb->find_sentence_by_source_id(source_id);
613                 if(result) result->quality = 101;
614         }
615         // 文脈込で発見できない場合、文脈無視で検索を試みる。
616         if(!result)
617         {
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;
621         }
622
623         if(result) // 検索結果を返す。
624         {
625                 QMetaObject::invokeMethod(
626                         m_service, "sentence_found",
627                         Qt::QueuedConnection,
628                         Q_ARG(sentence_data_type::pointer, result),
629                         Q_ARG(qint32, token));
630         }
631         else // 検索できなかったことを通知する。
632         {
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));
641         }
642 }
643
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)
648 {
649         // 入力の検証。
650         assert(!site_name.isEmpty());
651         assert(scode);
652         assert(tcode);
653         assert(source->source_id == 0);
654         assert(source->json.isEmpty());
655         assert(target->previous_crc == 0);
656         assert(target->next_crc == 0);
657
658         quint32 site_id = find_site_id(site_name);
659         assert(site_id);
660         if(!site_id) return;
661
662         // 原文の更新。
663         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
664         assert(sdb);
665
666         // 原文IDの検索、無い場合登録。
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);
672         if(!source_id)
673         {
674                 source->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
675                 sdb->insert(source);
676                 source_id = sdb->find_sentence_id_with_context(
677                                         source->sentence, source->previous_crc, source->next_crc);
678         }
679         assert(source_id);
680
681         // 訳文の更新。
682         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
683         assert(tdb);
684
685         target->source_id = source_id;
686         target->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
687
688         // 原文IDに対応する訳文を検索する。
689         // 現在、原文IDに対して登録できる訳文は一つのみ。
690         sentence_data_type::pointer target_sentence =
691                         tdb->find_sentence_by_source_id(source_id);
692         if(target_sentence) // 訳文があった場合、上書きして更新する。
693         {
694                 target->sentence_id = target_sentence->sentence_id;
695                 tdb->update(target);
696         }
697         else
698         {
699                 tdb->insert(target); // 訳文が無かった場合、新規挿入する。
700                 // 挿入後、検索することで訳文IDを取得する。
701                 target_sentence = tdb->find_sentence_by_source_id(source_id);
702         }
703         assert(target_sentence);
704         quint32 target_id = target_sentence->sentence_id;
705         assert(target_id); // 挿入あるいは更新しているので、IDがある。
706
707         // 単語の登録。
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);
714 }
715
716 QString TM::Database::find_language_name(int code) const
717 {
718         QString result;
719         QMap<int, QString>::const_iterator it = m_language_map.find(code);
720         if(it != m_language_map.end()) result = it.value();
721         return result;
722 }
723
724 /*!
725  * \brief 引数として与えられた単語に対して一意な番号を返します。
726  * \param code 言語コード。
727  * \param word 単語。
728  */
729 int TM::Database::find_word_id(int code, QString word)
730 {
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);
734         assert(result);
735         return result;
736 }
737
738 TM::WordDatabase::pointer TM::Database::find_word_database(int code)
739 {
740         QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
741         assert(it != m_word_databases.end());
742         return it.value();
743 }
744
745 /*!
746  * \brief 原文の単語IDと訳文の文IDとの照応を保持するデータベースを返します。
747  * \param site_id サイトのID。
748  * \param scode 原文の言語コード。
749  * \param tcode 訳文の言語コード。
750  * \return データベースへのポインタ。
751  *
752  * このメンバは接続をキャッシュしています。
753  */
754 TM::IndexDatabase::pointer TM::Database::find_index_database(
755                 quint32 site_id, int scode, int tcode)
756 {
757         assert(site_id);
758         assert(scode);
759         assert(tcode);
760
761         // キャッシュが大きすぎる場合、消去する。
762         if(m_index_cache_limit < m_index_databases.size()) m_index_databases.clear();
763
764         // データベース名を作成する。
765         QString dbname, sname, tname, site_name;
766         sname = find_language_name(scode).toLower();
767         assert(!sname.isEmpty());
768         if(!sname.isEmpty())
769         {
770                 tname = find_language_name(tcode).toLower();
771                 assert(!tname.isEmpty());
772         }
773         if(!tname.isEmpty())
774         {
775                 site_name = QString::number(site_id);
776                 assert(!site_name.isEmpty());
777         }
778         if(!site_name.isEmpty())
779         {
780                 dbname = QString("index-");
781                 dbname += site_name + "-" + sname + "-" + tname + ".db";
782         }
783
784         IndexDatabase::pointer result;
785
786         if(!dbname.isEmpty())
787         {
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());
793
794                 if(it != m_index_databases.end()) result = it.value();
795         }
796
797         return result;
798 }
799
800 /*!
801  * \brief 引数として与えられたサイトと言語用のセンテンス・データベースを返します。
802  * \param site_id サイトを識別する番号。
803  * \param code 言語コード。
804  *
805  * センテンス・データベースは、設定によって決められた数まで接続をキャッシュします。
806  * このメンバは、キャッシュ内にデータベース接続があればそれを返し、無ければ接続します。
807  */
808 TM::SentenceDatabase::pointer
809 TM::Database::find_sentence_database(quint32 site_id, int code)
810 {
811         typedef QMap<QPair<quint32, int>, SentenceDatabase::pointer>::iterator iterator;
812         assert(site_id);
813         assert(code);
814
815         // キャッシュが大きすぎる場合、消去する。
816         if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear();
817
818         // キャッシュにデータベースが無い場合、作成する。
819         QPair<quint32, int> key(site_id, code);
820         iterator it = m_sentence_databases.find(key);
821         if(it == m_sentence_databases.end())
822         {
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));
827         }
828         assert(it.value());
829
830         return it.value();
831 }
832
833
834
835
836
837
838
839
840
841
842
843
844
845