OSDN Git Service

d04737d043a0ebd0ed6f2cf3d9a3d0d566551b3b
[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
11 #include "debug.h"
12
13 // DatabaseBase ---------------------------------------------------------------
14
15 bool TM::DatabaseBase::open(char const *message)
16 {
17         assert(!m_database_name.isEmpty());
18         m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name);
19         m_database.setDatabaseName(m_database_name);
20         bool ret = m_database.open();
21         if(!ret)
22         {
23                 error(message);
24                 assert(false);
25         }
26         return ret;
27 }
28
29 QSqlQuery TM::DatabaseBase::prepare(char const *sql, char const *message)
30 {
31         QSqlQuery result(m_database);
32         bool ret = result.prepare(sql);
33         if(!ret)
34         {
35                 error(message, &result);
36                 assert(false);
37         }
38         return result;
39 }
40
41 bool TM::DatabaseBase::exec(QSqlQuery &query, char const *message)
42 {
43         bool ret = query.exec();
44         if(!ret)
45         {
46                 error(message, &query);
47                 assert(false);
48         }
49         return ret;
50 }
51
52 QSqlQuery TM::DatabaseBase::exec(char const *sql, char const *message)
53 {
54         QSqlQuery result = m_database.exec(sql);
55         if(result.lastError().isValid())
56         {
57                 error(message, &result);
58                 assert(false);
59         }
60         return result;
61 }
62
63 void TM::DatabaseBase::error(QString message, QSqlQuery *query)
64 {
65         QString s;
66         if(query) s = query->lastError().text();
67         else s = m_database.lastError().text();
68
69         qCritical() << message << s;
70 }
71
72 // SiteDatabase ---------------------------------------------------------------
73
74 TM::SiteDatabase::SiteDatabase(Settings *settings)
75 {
76         // キャッシュの最大値を設定。
77         m_cache_limit = settings->value(
78                 "SiteDatabase/cache-limit", QVariant::fromValue(1024)).toInt();
79
80         // データベースを開く。
81         m_database_name = settings->value(TMDATABASE_ROOT_PATH_KEY).toString() + "/site.db";
82         open(Q_FUNC_INFO);
83
84         // テーブル作成。
85         exec("CREATE TABLE IF NOT EXISTS sites("
86                         "site_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
87                         "host_name TEXT UNIQUE);", Q_FUNC_INFO);
88
89         exec("CREATE INDEX IF NOT EXISTS domain_index ON sites(host_name);", Q_FUNC_INFO);
90
91         // クエリ作成。
92         m_find_site_id = prepare("SELECT site_id FROM sites WHERE host_name=?;", Q_FUNC_INFO);
93         m_insert_site = prepare("INSERT INTO sites(host_name) VALUES(?);", Q_FUNC_INFO);
94 }
95
96 TM::SiteDatabase::~SiteDatabase()
97 {
98         if(m_database.isOpen()) m_database.close();
99 }
100
101 /*!
102  * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
103  */
104 int TM::SiteDatabase::site_id(QString domain)
105 {
106         // キャッシュが大きくなりすぎた場合、消去する。
107         if(m_cache_limit < m_cache.size()) m_cache.clear();
108         // キャッシュを検索。
109         QMap<QString, int>::const_iterator it = m_cache.find(domain);
110         if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
111
112         // キャッシュにない場合、データベースを検索。
113         int result = find_site_id(domain);
114         // データベースにない場合、データベースに登録。
115         if(!result)
116         {
117                 insert_site(domain);
118                 result = find_site_id(domain);
119         }
120         assert(0 < result);
121         // キャッシュに追加。
122         m_cache.insert(domain, result);
123
124         return result;
125 }
126
127 /*!
128  * \brief 引数として与えられたドメイン名に対して一意な番号を返します。
129  *
130  * 登録されていない場合、0を返します。
131  */
132 int TM::SiteDatabase::find_site_id(QString host_name)
133 {
134         m_find_site_id.bindValue(0, host_name);
135         exec(m_find_site_id, Q_FUNC_INFO);
136         if(m_find_site_id.next()) return m_find_site_id.value(0).toInt();
137
138         return 0;
139 }
140
141 void TM::SiteDatabase::insert_site(QString host_name)
142 {
143         m_insert_site.bindValue(0, host_name);
144         exec(m_insert_site, Q_FUNC_INFO);
145 }
146
147 TM::SiteDatabase::pointer TM::SiteDatabase::create(Settings *settings)
148 {
149         return pointer(new SiteDatabase(settings));
150 }
151
152 // WordDatabase ---------------------------------------------------------------
153
154 TM::WordDatabase::WordDatabase(Settings *settings, QString name)
155 {
156         // キャッシュの最大値を設定。
157         m_cache_limit = settings->value(
158                 "WordDatabase/cache-limit", QVariant::fromValue(100 * 1024)).toInt();
159
160         // データベースを開く。
161         m_database_name = settings->value(
162                 TMDATABASE_ROOT_PATH_KEY).toString() + "/word-" + name.toLower() + ".db";
163         open(Q_FUNC_INFO);
164
165         // テーブル作成。
166         exec("CREATE TABLE IF NOT EXISTS words("
167                         "word_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
168                         "word TEXT UNIQUE);", Q_FUNC_INFO);
169
170         exec("CREATE INDEX IF NOT EXISTS word_index ON words(word);", Q_FUNC_INFO);
171
172         // クエリ作成。
173         m_find_word_id = prepare(
174                 "SELECT word_id FROM words WHERE word=?;", Q_FUNC_INFO);
175
176         m_insert_word = prepare("INSERT INTO words(word) VALUES(?);", Q_FUNC_INFO);
177 }
178
179 TM::WordDatabase::~WordDatabase()
180 {
181         if(m_database.isOpen()) m_database.close();
182 }
183
184 int TM::WordDatabase::word_id(QString word)
185 {
186         // キャッシュが大きくなりすぎた場合、消去する。
187         if(m_cache_limit < m_cache.size()) m_cache.clear();
188         // キャッシュを検索。
189         QMap<QString, int>::const_iterator it = m_cache.find(word);
190         if(it != m_cache.end()) return *it; // キャッシュに存在する場合、ここで終わり。
191
192         // キャッシュにない場合、データベースを検索。
193         int result = find_word_id(word);
194         // データベースにない場合、データベースに登録。
195         if(!result)
196         {
197                 insert_word(word);
198                 result = find_word_id(word);
199         }
200         assert(0 < result);
201         // キャッシュに追加。
202         m_cache.insert(word, result);
203
204         return result;
205 }
206
207 int TM::WordDatabase::find_word_id(QString word)
208 {
209         m_find_word_id.bindValue(0, word);
210         exec(m_find_word_id, Q_FUNC_INFO);
211         if(m_find_word_id.next()) return m_find_word_id.value(0).toInt();
212
213         return 0;
214 }
215
216 void TM::WordDatabase::insert_word(QString word)
217 {
218         m_insert_word.bindValue(0, word);
219         exec(m_insert_word, Q_FUNC_INFO);
220 }
221
222 TM::WordDatabase::pointer TM::WordDatabase::create(Settings *settings, QString name)
223 {
224         return pointer(new WordDatabase(settings, name));
225 }
226
227 // SentenceDatabase -----------------------------------------------------------
228
229 TM::SentenceDatabase::SentenceDatabase(Settings *settings, int site_id, QString name)
230 {
231         // データベースを開く。
232         m_database_name =
233                 settings->value(TMDATABASE_ROOT_PATH_KEY).toString()
234                 + "/sentence-" + QString::number(site_id) + "-" + name.toLower() + ".db";
235         open(Q_FUNC_INFO);
236
237         // テーブル作成。
238         exec("CREATE TABLE IF NOT EXISTS sentences("
239                         "sentence_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
240                         "source_id INTEGER,"
241                         "sentence TEXT,"
242                         "json TEXT,"
243                         "crc INTEGER,"
244                         "previous_crc INTEGER,"
245                         "next_crc INTEGER,"
246                         "user_id INTEGER,"
247                         "time INTEGER);");
248
249         // インデックス作成。
250         exec("CREATE INDEX IF NOT EXISTS source_id_index ON sentences(source_id);", Q_FUNC_INFO);
251         exec("CREATE INDEX IF NOT EXISTS sentence_index ON sentences(sentence);", Q_FUNC_INFO);
252
253         // クエリ作成。
254         m_find_sentence_id = prepare(
255                 "SELECT sentence_id FROM sentences WHERE sentence=?;", Q_FUNC_INFO);
256
257         m_find_sentence_id_with_context = prepare(
258                 "SELECT sentence_id FROM sentences "
259                         "WHERE sentence=? "
260                         "AND previous_crc=? "
261                         "AND next_crc=?;", Q_FUNC_INFO);
262
263         m_find_sentence_by_crc = prepare("SELECT * FROM sentences WHERE crc=?;", Q_FUNC_INFO);
264         m_find_sentence_by_source_id = prepare("SELECT * FROM sentences WHERE source_id=?;", Q_FUNC_INFO);
265         m_insert_sentence = prepare(
266                 "INSERT OR REPLACE INTO sentences("
267                         "source_id, sentence, json, crc, previous_crc, next_crc, user_id, time) "
268                 "VALUES(?, ?, ?, ?, ?, ?, ?, ?);", Q_FUNC_INFO);
269         m_update_sentence = prepare(
270                 "UPDATE sentences SET "
271                         "source_id=?,"
272                         "sentence=?,"
273                         "json=?,"
274                         "crc=?,"
275                         "previous_crc=?,"
276                         "next_crc=?,"
277                         "user_id=?,"
278                         "time=? "
279                 "WHERE sentence_id=?;", Q_FUNC_INFO);
280 }
281
282 TM::SentenceDatabase::~SentenceDatabase()
283 {
284         if(m_database.isOpen()) m_database.close();
285 }
286
287 quint32 TM::SentenceDatabase::find_sentence_id(QString sentence)
288 {
289         m_find_sentence_id.bindValue(0, sentence);
290         exec(m_find_sentence_id, Q_FUNC_INFO);
291
292         if(!m_find_sentence_id.next()) return 0;
293         return m_find_sentence_id.value(0).toUInt();
294 }
295
296 quint32 TM::SentenceDatabase::find_sentence_id_with_context(
297                 QString sentence, quint32 previous_crc, quint32 next_crc)
298 {
299         m_find_sentence_id_with_context.bindValue(0, sentence);
300         m_find_sentence_id_with_context.bindValue(1, previous_crc);
301         m_find_sentence_id_with_context.bindValue(2, next_crc);
302         exec(m_find_sentence_id_with_context, Q_FUNC_INFO);
303
304         if(!m_find_sentence_id_with_context.next()) return 0;
305         return m_find_sentence_id_with_context.value(0).toUInt();
306 }
307
308 TM::sentence_data_type::pointer
309 TM::SentenceDatabase::find_sentence_by_source_id(int source_id)
310 {
311         assert(source_id);
312
313         sentence_data_type::pointer result;
314
315         m_find_sentence_by_source_id.bindValue(0, source_id);
316         exec(m_find_sentence_by_source_id, Q_FUNC_INFO);
317
318         if(!m_find_sentence_by_source_id.next()) return result;
319
320         return stuff_value(&m_find_sentence_by_source_id);
321 }
322
323 void TM::SentenceDatabase::insert(sentence_data_type::pointer sentence_data)
324 {
325         assert(m_database.isValid());
326         assert(m_database.isOpen());
327
328         m_insert_sentence.bindValue(0, sentence_data->source_id);
329         m_insert_sentence.bindValue(1, sentence_data->sentence);
330         m_insert_sentence.bindValue(2, sentence_data->json);
331         m_insert_sentence.bindValue(3, sentence_data->crc);
332         m_insert_sentence.bindValue(4, sentence_data->previous_crc);
333         m_insert_sentence.bindValue(5, sentence_data->next_crc);
334         m_insert_sentence.bindValue(6, sentence_data->user_id);
335         m_insert_sentence.bindValue(7, sentence_data->time);
336
337         exec(m_insert_sentence, Q_FUNC_INFO);
338 }
339
340 void TM::SentenceDatabase::update(sentence_data_type::pointer sentence_data)
341 {
342         assert(m_database.isValid());
343         assert(m_database.isOpen());
344
345         m_update_sentence.bindValue(0, sentence_data->source_id);
346         m_update_sentence.bindValue(1, sentence_data->sentence);
347         m_update_sentence.bindValue(2, sentence_data->json);
348         m_update_sentence.bindValue(3, sentence_data->crc);
349         m_update_sentence.bindValue(4, sentence_data->previous_crc);
350         m_update_sentence.bindValue(5, sentence_data->next_crc);
351         m_update_sentence.bindValue(6, sentence_data->user_id);
352         m_update_sentence.bindValue(7, sentence_data->time);
353         m_update_sentence.bindValue(8, sentence_data->sentence_id);
354
355         exec(m_update_sentence, Q_FUNC_INFO);
356 }
357
358 TM::sentence_data_type::pointer
359 TM::SentenceDatabase::stuff_value(QSqlQuery *query)
360 {
361         assert(query->isValid());
362         sentence_data_type::pointer result = sentence_data_type::create();
363
364         result->sentence_id = query->value(0).toUInt();
365         result->source_id = query->value(1).toUInt();
366         result->sentence = query->value(2).toString();
367         result->json = query->value(3).toByteArray();
368         result->crc = query->value(4).toUInt();
369         result->previous_crc = query->value(5).toUInt();
370         result->next_crc = query->value(6).toUInt();
371         result->user_id = query->value(7).toUInt();
372         result->time = query->value(8).toUInt();
373
374         return result;
375 }
376
377 TM::SentenceDatabase::pointer TM::SentenceDatabase::create(
378                 Settings *settings, int site_id, QString name)
379 {
380         return pointer(new SentenceDatabase(settings, site_id, name));
381 }
382
383 // Database -------------------------------------------------------------------
384
385 TM::Database::Database(Settings *settings, Service *service)
386         : QObject(0)
387         , m_settings(settings)
388         , m_service(service)
389 {
390         qRegisterMetaType<sentence_data_type::pointer>();
391 }
392
393 TM::Database::~Database()
394 {
395 }
396
397 /*!
398  * \brief データベースのセットアップを行います。
399  *
400  * データベース接続を開いたスレッド内でしか使えない制限のため、スレッドの
401  * イベントループから処理します。
402  */
403 void TM::Database::setup()
404 {
405         // キャッシュの最大値を設定。
406         m_sentence_cache_limit = m_settings->value(
407                 "Database/sentence-cache-limit", QVariant::fromValue(20)).toInt();
408
409         // データベース用のフォルダが無ければ作成する。
410         if(!m_settings->contains(TMDATABASE_ROOT_PATH_KEY)) qFatal("An error occured while find settings in Database().");
411         QString path = m_settings->value(TMDATABASE_ROOT_PATH_KEY).toString();
412         QDir dir(path);
413         if(!dir.exists()) dir.mkpath(path);
414
415         // データベースを開く。
416         m_site_database = SiteDatabase::create(m_settings);
417 }
418
419 /*!
420  * \brief 引数で指定された言語の単語データベースを開きます。
421  * \param code 言語コード。
422  * \param name 言語名。
423  *
424  * 言語プラグインを読み込んだ時に呼び出すことを想定しています。
425  * 言語プラグインは、TM::Serviceで読み込まれます。
426  */
427 void TM::Database::open_word_database(int code, QString name)
428 {
429         m_language_map.insert(code, name);
430         m_word_databases.insert(code, WordDatabase::create(m_settings, name));
431 }
432
433 quint32 TM::Database::find_site_id(QString host_name)
434 {
435         quint32 result = m_site_database->site_id(host_name);
436         return result;
437 }
438
439 void TM::Database::find_sentence(quint32 site_id, int scode, int tcode,
440                         int segment_id, int index, sentence_data_type::pointer source,
441                         SocketConnection::pointer socket)
442 {
443         sentence_data_type::pointer result;
444
445         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
446         assert(sdb);
447         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
448         assert(tdb);
449
450         quint32 source_id = sdb->find_sentence_id_with_context(
451                                 source->sentence, source->previous_crc, source->next_crc);
452
453         if(source_id) result = tdb->find_sentence_by_source_id(source_id);
454
455         QMetaObject::invokeMethod(
456                 m_service, "sentence_found",
457                 Qt::QueuedConnection,
458                 Q_ARG(qint32, segment_id),
459                 Q_ARG(qint32, index),
460                 Q_ARG(sentence_data_type::pointer, result),
461                 Q_ARG(SocketConnection::pointer, socket));
462
463 }
464
465 void TM::Database::insert_sentence(quint32 site_id,
466                 quint32 scode, sentence_data_type::pointer source,
467                 quint32 tcode, sentence_data_type::pointer target)
468 {
469         assert(site_id);
470         assert(scode);
471         assert(tcode);
472
473         assert(source->source_id == 0);
474         assert(source->json.isEmpty());
475         assert(target->previous_crc == 0);
476         assert(target->next_crc == 0);
477
478         SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode);
479         assert(sdb);
480         SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode);
481         assert(tdb);
482
483         quint32 source_id = source->sentence_id;
484         if(!source_id) source_id = sdb->find_sentence_id_with_context(
485                                 source->sentence, source->previous_crc, source->next_crc);
486         if(!source_id)
487         {
488                 source->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
489                 sdb->insert(source);
490                 source_id = sdb->find_sentence_id_with_context(
491                                         source->sentence, source->previous_crc, source->next_crc);
492         }
493         assert(source_id);
494
495         target->source_id = source_id;
496         target->time = QDateTime::currentDateTime().toMSecsSinceEpoch();
497
498         sentence_data_type::pointer target_sentence =
499                         tdb->find_sentence_by_source_id(source_id);
500         if(target_sentence)
501         {
502                 target->sentence_id = target_sentence->sentence_id;
503                 tdb->update(target);
504         }
505         else tdb->insert(target);
506 }
507
508 QString TM::Database::find_language_name(int code) const
509 {
510         QString result;
511         QMap<int, QString>::const_iterator it = m_language_map.find(code);
512         if(it != m_language_map.end()) result = it.value();
513         return result;
514 }
515
516 /*!
517  * \brief 引数として与えられた単語に対して一意な番号を返します。
518  * \param code 言語コード。
519  * \param word 単語。
520  */
521 int TM::Database::find_word_id(int code, QString word)
522 {
523         QMap<int, WordDatabase::pointer>::iterator it = m_word_databases.find(code);
524         assert(it != m_word_databases.end());
525         int result = (*it)->word_id(word);
526         assert(result);
527         return result;
528 }
529
530 /*!
531  * \brief 引数として与えられたサイトと言語用のセンテンス・データベースを返します。
532  * \param site_id サイトを識別する番号。
533  * \param code 言語コード。
534  *
535  * センテンス・データベースは、設定によって決められた数まで接続をキャッシュします。
536  * このメンバは、キャッシュ内にデータベース接続があればそれを返し、無ければ接続します。
537  */
538 TM::SentenceDatabase::pointer
539 TM::Database::find_sentence_database(quint32 site_id, int code)
540 {
541         typedef QMap<QPair<quint32, int>, SentenceDatabase::pointer>::iterator iterator;
542         assert(site_id);
543         assert(code);
544
545         // キャッシュが大きすぎる場合、消去する。
546         if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear();
547
548         // キャッシュにデータベースが無い場合、作成する。
549         QPair<quint32, int> key(site_id, code);
550         iterator it = m_sentence_databases.find(key);
551         if(it == m_sentence_databases.end())
552         {
553                 QString name = find_language_name(code);
554                 assert(!name.isEmpty());
555                 it = m_sentence_databases.insert(
556                         key, SentenceDatabase::create(m_settings, site_id, name));
557         }
558         assert(it.value());
559
560         return it.value();
561 }
562
563
564
565
566
567
568
569
570
571
572
573
574
575