DbSearcher.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. //const _ = require('lodash');
  2. const utils = require('./utils');
  3. class DbSearcher {
  4. constructor(config, db) {
  5. this.config = config;
  6. this.db = db;
  7. this.searchFlag = 0;
  8. this.timer = null;
  9. this.closed = false;
  10. this.periodicCleanCache();//no await
  11. }
  12. async selectAuthorIds(query) {
  13. const db = this.db;
  14. let authorRows;
  15. let authorIds = new Set();
  16. //сначала выберем все id авторов по фильтру
  17. //порядок id соответсвует ASC-сортировке по author
  18. if (query.author) {
  19. authorRows = await db.select({
  20. table: 'author',
  21. map: `(r) => ({id: r.id})`,
  22. });
  23. for (const row of authorRows)
  24. authorIds.add(row.id);
  25. } else {//все авторы
  26. if (!db.searchCache.authorIdsAll) {
  27. authorRows = await db.select({
  28. table: 'author',
  29. map: `(r) => ({id: r.id})`,
  30. });
  31. db.searchCache.authorIdsAll = [];
  32. for (const row of authorRows) {
  33. authorIds.add(row.id);
  34. db.searchCache.authorIdsAll.push(row.id);
  35. }
  36. } else {//оптимизация
  37. for (const id of db.searchCache.authorIdsAll) {
  38. authorIds.add(id);
  39. }
  40. }
  41. }
  42. const idsArr = [];
  43. idsArr.push(authorIds);
  44. //серии
  45. //названия
  46. //жанры
  47. //языки
  48. if (idsArr.length > 1)
  49. authorIds = utils.intersectSet(idsArr);
  50. //сортировка
  51. authorIds = Array.from(authorIds);
  52. authorIds.sort((a, b) => a > b);
  53. return authorIds;
  54. }
  55. async getAuthorIds(query) {
  56. const db = this.db;
  57. if (!db.searchCache)
  58. db.searchCache = {};
  59. let result;
  60. //сначала попробуем найти в кеше
  61. const q = query;
  62. const keyArr = [q.author, q.series, q.title, q.genre, q.lang];
  63. const keyStr = keyArr.join('');
  64. if (!keyStr) {//пустой запрос
  65. if (db.searchCache.authorIdsAll)
  66. result = db.searchCache.authorIdsAll;
  67. else
  68. result = await this.selectAuthorIds(query);
  69. } else {//непустой запрос
  70. const key = JSON.stringify(keyArr);
  71. const rows = await db.select({table: 'query_cache', where: `@@id(${db.esc(key)})`});
  72. if (rows.length) {//нашли в кеше
  73. await db.insert({
  74. table: 'query_time',
  75. replace: true,
  76. rows: [{id: key, time: Date.now()}],
  77. });
  78. result = rows[0].value;
  79. } else {//не нашли в кеше, ищем в поисковых таблицах
  80. result = await this.selectAuthorIds(query);
  81. await db.insert({
  82. table: 'query_cache',
  83. replace: true,
  84. rows: [{id: key, value: result}],
  85. });
  86. await db.insert({
  87. table: 'query_time',
  88. replace: true,
  89. rows: [{id: key, time: Date.now()}],
  90. });
  91. }
  92. }
  93. return result;
  94. }
  95. async search(query) {
  96. if (this.closed)
  97. throw new Error('DbSearcher closed');
  98. this.searchFlag++;
  99. try {
  100. const db = this.db;
  101. const authorIds = await this.getAuthorIds(query);
  102. const totalFound = authorIds.length;
  103. const limit = (query.limit ? query.limit : 1000);
  104. //выборка найденных авторов
  105. let result = await db.select({
  106. table: 'author',
  107. map: `(r) => ({id: r.id, author: r.author})`,
  108. where: `
  109. const all = @all();
  110. const ids = new Set();
  111. let n = 0;
  112. for (const id of all) {
  113. if (++n > ${db.esc(limit)})
  114. break;
  115. ids.add(id);
  116. }
  117. return ids;
  118. `
  119. });
  120. return {result, totalFound};
  121. } finally {
  122. this.searchFlag--;
  123. }
  124. }
  125. async periodicCleanCache() {
  126. this.timer = null;
  127. const cleanInterval = 5*1000;//this.config.cacheCleanInterval*60*1000;
  128. try {
  129. const db = this.db;
  130. const oldThres = Date.now() - cleanInterval;
  131. //выберем всех кандидатов удаление
  132. const rows = await db.select({
  133. table: 'query_time',
  134. where: `
  135. @@iter(@all(), (r) => (r.time < ${db.esc(oldThres)}));
  136. `
  137. });
  138. const ids = [];
  139. for (const row of rows)
  140. ids.push(row.id);
  141. //удаляем
  142. await db.delete({table: 'query_cache', where: `@@id(${db.esc(ids)})`});
  143. await db.delete({table: 'query_time', where: `@@id(${db.esc(ids)})`});
  144. console.log('Cache clean', ids);
  145. } catch(e) {
  146. console.error(e.message);
  147. } finally {
  148. if (!this.closed) {
  149. this.timer = setTimeout(() => { this.periodicCleanCache(); }, cleanInterval);
  150. }
  151. }
  152. }
  153. async close() {
  154. while (this.searchFlag > 0) {
  155. await utils.sleep(50);
  156. }
  157. if (this.timer) {
  158. clearTimeout(this.timer);
  159. this.timer = null;
  160. }
  161. this.closed = true;
  162. }
  163. }
  164. module.exports = DbSearcher;