DbSearcher.js 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. //const _ = require('lodash');
  2. const utils = require('./utils');
  3. const maxMemCacheSize = 100;
  4. const maxLimit = 1000;
  5. const emptyFieldValue = '?';
  6. const maxUtf8Char = String.fromCodePoint(0xFFFFF);
  7. const ruAlphabet = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя';
  8. const enAlphabet = 'abcdefghijklmnopqrstuvwxyz';
  9. const enruArr = (ruAlphabet + enAlphabet).split('');
  10. class DbSearcher {
  11. constructor(config, db) {
  12. this.config = config;
  13. this.db = db;
  14. this.searchFlag = 0;
  15. this.timer = null;
  16. this.closed = false;
  17. this.memCache = new Map();
  18. this.periodicCleanCache();//no await
  19. }
  20. queryKey(q) {
  21. return JSON.stringify([q.author, q.series, q.title, q.genre, q.lang, q.del, q.date, q.librate]);
  22. }
  23. getWhere(a) {
  24. const db = this.db;
  25. a = a.toLowerCase();
  26. let where;
  27. //особая обработка префиксов
  28. if (a[0] == '=') {
  29. a = a.substring(1);
  30. where = `@dirtyIndexLR('value', ${db.esc(a)}, ${db.esc(a)})`;
  31. } else if (a[0] == '*') {
  32. a = a.substring(1);
  33. where = `@indexIter('value', (v) => (v !== ${db.esc(emptyFieldValue)} && v.indexOf(${db.esc(a)}) >= 0) )`;
  34. } else if (a[0] == '#') {
  35. a = a.substring(1);
  36. where = `@indexIter('value', (v) => {
  37. const enru = new Set(${db.esc(enruArr)});
  38. return !v || (v !== ${db.esc(emptyFieldValue)} && !enru.has(v[0]) && v.indexOf(${db.esc(a)}) >= 0);
  39. })`;
  40. } else {
  41. where = `@dirtyIndexLR('value', ${db.esc(a)}, ${db.esc(a + maxUtf8Char)})`;
  42. }
  43. return where;
  44. }
  45. async selectAuthorIds(query) {
  46. const db = this.db;
  47. const authorKеy = `author-ids-author-${query.author}`;
  48. let authorIds = await this.getCached(authorKеy);
  49. //сначала выберем все id авторов по фильтру
  50. //порядок id соответствует ASC-сортировке по author
  51. if (authorIds === null) {
  52. if (query.author && query.author !== '*') {
  53. const where = this.getWhere(query.author);
  54. const authorRows = await db.select({
  55. table: 'author',
  56. rawResult: true,
  57. where: `return Array.from(${where})`,
  58. });
  59. authorIds = authorRows[0].rawResult;
  60. } else {//все авторы
  61. const authorRows = await db.select({
  62. table: 'author',
  63. rawResult: true,
  64. where: `return Array.from(@all())`,
  65. });
  66. authorIds = authorRows[0].rawResult;
  67. }
  68. await this.putCached(authorKеy, authorIds);
  69. }
  70. const idsArr = [];
  71. //серии
  72. if (query.series && query.series !== '*') {
  73. const seriesKеy = `author-ids-series-${query.series}`;
  74. let seriesIds = await this.getCached(seriesKеy);
  75. if (seriesIds === null) {
  76. const where = this.getWhere(query.series);
  77. const seriesRows = await db.select({
  78. table: 'series',
  79. rawResult: true,
  80. where: `
  81. const ids = ${where};
  82. const result = new Set();
  83. for (const id of ids) {
  84. const row = @unsafeRow(id);
  85. for (const authorId of row.authorId)
  86. result.add(authorId);
  87. }
  88. return Array.from(result);
  89. `
  90. });
  91. seriesIds = seriesRows[0].rawResult;
  92. await this.putCached(seriesKеy, seriesIds);
  93. }
  94. idsArr.push(seriesIds);
  95. }
  96. //названия
  97. if (query.title && query.title !== '*') {
  98. const titleKey = `author-ids-title-${query.title}`;
  99. let titleIds = await this.getCached(titleKey);
  100. if (titleIds === null) {
  101. const where = this.getWhere(query.title);
  102. let titleRows = await db.select({
  103. table: 'title',
  104. rawResult: true,
  105. where: `
  106. const ids = ${where};
  107. const result = new Set();
  108. for (const id of ids) {
  109. const row = @unsafeRow(id);
  110. for (const authorId of row.authorId)
  111. result.add(authorId);
  112. }
  113. return Array.from(result);
  114. `
  115. });
  116. titleIds = titleRows[0].rawResult;
  117. await this.putCached(titleKey, titleIds);
  118. }
  119. idsArr.push(titleIds);
  120. //чистки памяти при тяжелых запросах
  121. if (this.config.lowMemoryMode && query.title[0] == '*') {
  122. utils.freeMemory();
  123. await db.freeMemory();
  124. }
  125. }
  126. //жанры
  127. if (query.genre) {
  128. const genreKey = `author-ids-genre-${query.genre}`;
  129. let genreIds = await this.getCached(genreKey);
  130. if (genreIds === null) {
  131. const genreRows = await db.select({
  132. table: 'genre',
  133. rawResult: true,
  134. where: `
  135. const genres = ${db.esc(query.genre.split(','))};
  136. const ids = new Set();
  137. for (const g of genres) {
  138. for (const id of @indexLR('value', g, g))
  139. ids.add(id);
  140. }
  141. const result = new Set();
  142. for (const id of ids) {
  143. const row = @unsafeRow(id);
  144. for (const authorId of row.authorId)
  145. result.add(authorId);
  146. }
  147. return Array.from(result);
  148. `
  149. });
  150. genreIds = genreRows[0].rawResult;
  151. await this.putCached(genreKey, genreIds);
  152. }
  153. idsArr.push(genreIds);
  154. }
  155. //языки
  156. if (query.lang) {
  157. const langKey = `author-ids-lang-${query.lang}`;
  158. let langIds = await this.getCached(langKey);
  159. if (langIds === null) {
  160. const langRows = await db.select({
  161. table: 'lang',
  162. rawResult: true,
  163. where: `
  164. const langs = ${db.esc(query.lang.split(','))};
  165. const ids = new Set();
  166. for (const l of langs) {
  167. for (const id of @indexLR('value', l, l))
  168. ids.add(id);
  169. }
  170. const result = new Set();
  171. for (const id of ids) {
  172. const row = @unsafeRow(id);
  173. for (const authorId of row.authorId)
  174. result.add(authorId);
  175. }
  176. return Array.from(result);
  177. `
  178. });
  179. langIds = langRows[0].rawResult;
  180. await this.putCached(langKey, langIds);
  181. }
  182. idsArr.push(langIds);
  183. }
  184. //удаленные
  185. if (query.del !== undefined) {
  186. const delKey = `author-ids-del-${query.del}`;
  187. let delIds = await this.getCached(delKey);
  188. if (delIds === null) {
  189. const delRows = await db.select({
  190. table: 'del',
  191. rawResult: true,
  192. where: `
  193. const ids = @indexLR('value', ${db.esc(query.del)}, ${db.esc(query.del)});
  194. const result = new Set();
  195. for (const id of ids) {
  196. const row = @unsafeRow(id);
  197. for (const authorId of row.authorId)
  198. result.add(authorId);
  199. }
  200. return Array.from(result);
  201. `
  202. });
  203. delIds = delRows[0].rawResult;
  204. await this.putCached(delKey, delIds);
  205. }
  206. idsArr.push(delIds);
  207. }
  208. //дата поступления
  209. if (query.date) {
  210. const dateKey = `author-ids-date-${query.date}`;
  211. let dateIds = await this.getCached(dateKey);
  212. if (dateIds === null) {
  213. let [from = '', to = ''] = query.date.split(',');
  214. const dateRows = await db.select({
  215. table: 'date',
  216. rawResult: true,
  217. where: `
  218. const ids = @indexLR('value', ${db.esc(from)} || undefined, ${db.esc(to)} || undefined);
  219. const result = new Set();
  220. for (const id of ids) {
  221. const row = @unsafeRow(id);
  222. for (const authorId of row.authorId)
  223. result.add(authorId);
  224. }
  225. return Array.from(result);
  226. `
  227. });
  228. dateIds = dateRows[0].rawResult;
  229. await this.putCached(dateKey, dateIds);
  230. }
  231. idsArr.push(dateIds);
  232. }
  233. //оценка
  234. if (query.librate) {
  235. const librateKey = `author-ids-librate-${query.librate}`;
  236. let librateIds = await this.getCached(librateKey);
  237. if (librateIds === null) {
  238. const dateRows = await db.select({
  239. table: 'librate',
  240. rawResult: true,
  241. where: `
  242. const rates = ${db.esc(query.librate.split(',').map(n => parseInt(n, 10)).filter(n => !isNaN(n)))};
  243. const ids = new Set();
  244. for (const rate of rates) {
  245. for (const id of @indexLR('value', rate, rate))
  246. ids.add(id);
  247. }
  248. const result = new Set();
  249. for (const id of ids) {
  250. const row = @unsafeRow(id);
  251. for (const authorId of row.authorId)
  252. result.add(authorId);
  253. }
  254. return Array.from(result);
  255. `
  256. });
  257. librateIds = dateRows[0].rawResult;
  258. await this.putCached(librateKey, librateIds);
  259. }
  260. idsArr.push(librateIds);
  261. }
  262. /*
  263. //ищем пересечение множеств
  264. idsArr.push(authorIds);
  265. if (idsArr.length > 1) {
  266. const idsSetArr = idsArr.map(ids => new Set(ids));
  267. authorIds = Array.from(utils.intersectSet(idsSetArr));
  268. }
  269. //сортировка
  270. authorIds.sort((a, b) => a - b);
  271. */
  272. //ищем пересечение множеств, работает быстрее предыдущего
  273. if (idsArr.length) {
  274. idsArr.push(authorIds);
  275. let proc = 0;
  276. let nextProc = 0;
  277. let inter = new Set(idsArr[0]);
  278. for (let i = 1; i < idsArr.length; i++) {
  279. const newInter = new Set();
  280. for (const id of idsArr[i]) {
  281. if (inter.has(id))
  282. newInter.add(id);
  283. //прерываемся иногда, чтобы не блокировать Event Loop
  284. proc++;
  285. if (proc >= nextProc) {
  286. nextProc += 10000;
  287. await utils.processLoop();
  288. }
  289. }
  290. inter = newInter;
  291. }
  292. authorIds = Array.from(inter);
  293. }
  294. //сортировка
  295. authorIds.sort((a, b) => a - b);
  296. return authorIds;
  297. }
  298. getWhere2(query, ids, exclude = '') {
  299. const db = this.db;
  300. const filterBySearch = (searchValue) => {
  301. searchValue = searchValue.toLowerCase();
  302. //особая обработка префиксов
  303. if (searchValue[0] == '=') {
  304. searchValue = searchValue.substring(1);
  305. return `bookValue.localeCompare(${db.esc(searchValue)}) == 0`;
  306. } else if (searchValue[0] == '*') {
  307. searchValue = searchValue.substring(1);
  308. return `bookValue !== ${db.esc(emptyFieldValue)} && bookValue.indexOf(${db.esc(searchValue)}) >= 0`;
  309. } else if (searchValue[0] == '#') {
  310. searchValue = searchValue.substring(1);
  311. return `!bookValue || (bookValue !== ${db.esc(emptyFieldValue)} && !enru.has(bookValue[0]) && bookValue.indexOf(${db.esc(searchValue)}) >= 0)`;
  312. } else {
  313. return `bookValue.localeCompare(${db.esc(searchValue)}) >= 0 && bookValue.localeCompare(${db.esc(searchValue + maxUtf8Char)}) <= 0`;
  314. }
  315. };
  316. //подготовка фильтра
  317. let filter = '';
  318. let closures = '';
  319. //порядок важен, более простые проверки вперед
  320. //удаленные
  321. if (query.del !== undefined) {
  322. filter += `
  323. if (book.del !== ${db.esc(query.del)})
  324. return false;
  325. `;
  326. }
  327. //дата поступления
  328. if (query.date) {
  329. let [from = '0000-00-00', to = '9999-99-99'] = query.date.split(',');
  330. filter += `
  331. if (!(book.date >= ${db.esc(from)} && book.date <= ${db.esc(to)}))
  332. return false;
  333. `;
  334. }
  335. //оценка
  336. if (query.librate) {
  337. closures += `
  338. const searchLibrate = new Set(${db.esc(query.librate.split(',').map(n => parseInt(n, 10)).filter(n => !isNaN(n)))});
  339. `;
  340. filter += `
  341. if (!searchLibrate.has(book.librate))
  342. return false;
  343. `;
  344. }
  345. //серии
  346. if (exclude !== 'series' && query.series && query.series !== '*') {
  347. closures += `
  348. const checkSeries = (bookValue) => {
  349. if (!bookValue)
  350. bookValue = ${db.esc(emptyFieldValue)};
  351. bookValue = bookValue.toLowerCase();
  352. return ${filterBySearch(query.series)};
  353. };
  354. `;
  355. filter += `
  356. if (!checkSeries(book.series))
  357. return false;
  358. `;
  359. }
  360. //названия
  361. if (exclude !== 'title' && query.title && query.title !== '*') {
  362. closures += `
  363. const checkTitle = (bookValue) => {
  364. if (!bookValue)
  365. bookValue = ${db.esc(emptyFieldValue)};
  366. bookValue = bookValue.toLowerCase();
  367. return ${filterBySearch(query.title)};
  368. };
  369. `;
  370. filter += `
  371. if (!checkTitle(book.title))
  372. return false;
  373. `;
  374. }
  375. //языки
  376. if (exclude !== 'lang' && query.lang) {
  377. const queryLangs = query.lang.split(',');
  378. closures += `
  379. const queryLangs = new Set(${db.esc(queryLangs)});
  380. const checkLang = (bookValue) => {
  381. if (!bookValue)
  382. bookValue = ${db.esc(emptyFieldValue)};
  383. return queryLangs.has(bookValue);
  384. };
  385. `;
  386. filter += `
  387. if (!checkLang(book.lang))
  388. return false;
  389. `;
  390. }
  391. //жанры
  392. if (exclude !== 'genre' && query.genre) {
  393. const queryGenres = query.genre.split(',');
  394. closures += `
  395. const queryGenres = new Set(${db.esc(queryGenres)});
  396. const checkGenre = (bookValue) => {
  397. if (!bookValue)
  398. bookValue = ${db.esc(emptyFieldValue)};
  399. return queryGenres.has(bookValue);
  400. };
  401. `;
  402. filter += `
  403. const genres = book.genre.split(',');
  404. found = false;
  405. for (const g of genres) {
  406. if (checkGenre(g)) {
  407. found = true;
  408. break;
  409. }
  410. }
  411. if (!found)
  412. return false;
  413. `;
  414. }
  415. //авторы
  416. if (exclude !== 'author' && query.author && query.author !== '*') {
  417. closures += `
  418. const splitAuthor = (author) => {
  419. if (!author)
  420. author = ${db.esc(emptyFieldValue)};
  421. const result = author.split(',');
  422. if (result.length > 1)
  423. result.push(author);
  424. return result;
  425. };
  426. const checkAuthor = (bookValue) => {
  427. if (!bookValue)
  428. bookValue = ${db.esc(emptyFieldValue)};
  429. bookValue = bookValue.toLowerCase();
  430. return ${filterBySearch(query.author)};
  431. };
  432. `;
  433. filter += `
  434. const author = splitAuthor(book.author);
  435. found = false;
  436. for (const a of author) {
  437. if (checkAuthor(a)) {
  438. found = true;
  439. break;
  440. }
  441. }
  442. if (!found)
  443. return false;
  444. `;
  445. }
  446. //формируем where
  447. let where = '';
  448. if (filter) {
  449. where = `
  450. const enru = new Set(${db.esc(enruArr)});
  451. ${closures}
  452. const filterBook = (book) => {
  453. let found = false;
  454. ${filter}
  455. return true;
  456. };
  457. let ids;
  458. if (${!ids}) {
  459. ids = @all();
  460. } else {
  461. ids = ${db.esc(ids)};
  462. }
  463. const result = new Set();
  464. for (const id of ids) {
  465. const row = @unsafeRow(id);
  466. if (row) {
  467. for (const book of row.books) {
  468. if (filterBook(book)) {
  469. result.add(id);
  470. break;
  471. }
  472. }
  473. }
  474. }
  475. return Array.from(result);
  476. `;
  477. }
  478. return where;
  479. }
  480. async selectSeriesIds(query) {
  481. const db = this.db;
  482. let seriesIds = false;
  483. let isAll = !(query.series && query.series !== '*');
  484. //серии
  485. const seriesKеy = `series-ids-series-${query.series}`;
  486. seriesIds = await this.getCached(seriesKеy);
  487. if (seriesIds === null) {
  488. if (query.series && query.series !== '*') {
  489. const where = this.getWhere(query.series);
  490. const seriesRows = await db.select({
  491. table: 'series',
  492. rawResult: true,
  493. where: `return Array.from(${where})`,
  494. });
  495. seriesIds = seriesRows[0].rawResult;
  496. } else {
  497. const seriesRows = await db.select({
  498. table: 'series',
  499. rawResult: true,
  500. where: `return Array.from(@all())`,
  501. });
  502. seriesIds = seriesRows[0].rawResult;
  503. }
  504. seriesIds.sort((a, b) => a - b);
  505. await this.putCached(seriesKеy, seriesIds);
  506. }
  507. const where = this.getWhere2(query, (isAll ? false : seriesIds), 'series');
  508. if (where) {
  509. //тяжелый запрос перебором в series_book
  510. const rows = await db.select({
  511. table: 'series_book',
  512. rawResult: true,
  513. where,
  514. });
  515. seriesIds = rows[0].rawResult;
  516. }
  517. return seriesIds;
  518. }
  519. async selectTitleIds(query) {
  520. const db = this.db;
  521. let titleIds = false;
  522. let isAll = !(query.title && query.title !== '*');
  523. //серии
  524. const titleKеy = `title-ids-title-${query.title}`;
  525. titleIds = await this.getCached(titleKеy);
  526. if (titleIds === null) {
  527. if (query.title && query.title !== '*') {
  528. const where = this.getWhere(query.title);
  529. const titleRows = await db.select({
  530. table: 'title',
  531. rawResult: true,
  532. where: `return Array.from(${where})`,
  533. });
  534. titleIds = titleRows[0].rawResult;
  535. } else {
  536. const titleRows = await db.select({
  537. table: 'title',
  538. rawResult: true,
  539. where: `return Array.from(@all())`,
  540. });
  541. titleIds = titleRows[0].rawResult;
  542. }
  543. titleIds.sort((a, b) => a - b);
  544. await this.putCached(titleKеy, titleIds);
  545. }
  546. const where = this.getWhere2(query, (isAll ? false : titleIds), 'title');
  547. if (where) {
  548. //тяжелый запрос перебором в title_book
  549. const rows = await db.select({
  550. table: 'title_book',
  551. rawResult: true,
  552. where,
  553. });
  554. titleIds = rows[0].rawResult;
  555. }
  556. return titleIds;
  557. }
  558. async getCached(key) {
  559. if (!this.config.queryCacheEnabled)
  560. return null;
  561. let result = null;
  562. const db = this.db;
  563. const memCache = this.memCache;
  564. if (memCache.has(key)) {//есть в недавних
  565. result = memCache.get(key);
  566. //изменим порядок ключей, для последующей правильной чистки старых
  567. memCache.delete(key);
  568. memCache.set(key, result);
  569. } else {//смотрим в таблице
  570. const rows = await db.select({table: 'query_cache', where: `@@id(${db.esc(key)})`});
  571. if (rows.length) {//нашли в кеше
  572. await db.insert({
  573. table: 'query_time',
  574. replace: true,
  575. rows: [{id: key, time: Date.now()}],
  576. });
  577. result = rows[0].value;
  578. memCache.set(key, result);
  579. if (memCache.size > maxMemCacheSize) {
  580. //удаляем самый старый ключ-значение
  581. for (const k of memCache.keys()) {
  582. memCache.delete(k);
  583. break;
  584. }
  585. }
  586. }
  587. }
  588. return result;
  589. }
  590. async putCached(key, value) {
  591. if (!this.config.queryCacheEnabled)
  592. return;
  593. const db = this.db;
  594. const memCache = this.memCache;
  595. memCache.set(key, value);
  596. if (memCache.size > maxMemCacheSize) {
  597. //удаляем самый старый ключ-значение
  598. for (const k of memCache.keys()) {
  599. memCache.delete(k);
  600. break;
  601. }
  602. }
  603. //кладем в таблицу
  604. await db.insert({
  605. table: 'query_cache',
  606. replace: true,
  607. rows: [{id: key, value}],
  608. });
  609. await db.insert({
  610. table: 'query_time',
  611. replace: true,
  612. rows: [{id: key, time: Date.now()}],
  613. });
  614. }
  615. async authorSearch(query) {
  616. if (this.closed)
  617. throw new Error('DbSearcher closed');
  618. this.searchFlag++;
  619. try {
  620. const db = this.db;
  621. const key = `author-ids-${this.queryKey(query)}`;
  622. //сначала попробуем найти в кеше
  623. let authorIds = await this.getCached(key);
  624. if (authorIds === null) {//не нашли в кеше, ищем в поисковых таблицах
  625. authorIds = await this.selectAuthorIds(query);
  626. await this.putCached(key, authorIds);
  627. }
  628. const totalFound = authorIds.length;
  629. let limit = (query.limit ? query.limit : 100);
  630. limit = (limit > maxLimit ? maxLimit : limit);
  631. const offset = (query.offset ? query.offset : 0);
  632. //выборка найденных авторов
  633. const result = await db.select({
  634. table: 'author',
  635. map: `(r) => ({id: r.id, author: r.author, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
  636. where: `@@id(${db.esc(authorIds.slice(offset, offset + limit))})`
  637. });
  638. return {result, totalFound};
  639. } finally {
  640. this.searchFlag--;
  641. }
  642. }
  643. async seriesSearch(query) {
  644. if (this.closed)
  645. throw new Error('DbSearcher closed');
  646. this.searchFlag++;
  647. try {
  648. const db = this.db;
  649. const key = `series-ids-${this.queryKey(query)}`;
  650. //сначала попробуем найти в кеше
  651. let seriesIds = await this.getCached(key);
  652. if (seriesIds === null) {//не нашли в кеше, ищем в поисковых таблицах
  653. seriesIds = await this.selectSeriesIds(query);
  654. await this.putCached(key, seriesIds);
  655. }
  656. const totalFound = seriesIds.length;
  657. let limit = (query.limit ? query.limit : 100);
  658. limit = (limit > maxLimit ? maxLimit : limit);
  659. const offset = (query.offset ? query.offset : 0);
  660. //выборка найденных авторов
  661. const result = await db.select({
  662. table: 'series_book',
  663. map: `(r) => ({id: r.id, series: r.series, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
  664. where: `@@id(${db.esc(seriesIds.slice(offset, offset + limit))})`
  665. });
  666. return {result, totalFound};
  667. } finally {
  668. this.searchFlag--;
  669. }
  670. }
  671. async titleSearch(query) {
  672. if (this.closed)
  673. throw new Error('DbSearcher closed');
  674. this.searchFlag++;
  675. try {
  676. const db = this.db;
  677. const key = `title-ids-${this.queryKey(query)}`;
  678. //сначала попробуем найти в кеше
  679. let titleIds = await this.getCached(key);
  680. if (titleIds === null) {//не нашли в кеше, ищем в поисковых таблицах
  681. titleIds = await this.selectTitleIds(query);
  682. await this.putCached(key, titleIds);
  683. }
  684. const totalFound = titleIds.length;
  685. let limit = (query.limit ? query.limit : 100);
  686. limit = (limit > maxLimit ? maxLimit : limit);
  687. const offset = (query.offset ? query.offset : 0);
  688. //выборка найденных авторов
  689. const result = await db.select({
  690. table: 'title_book',
  691. map: `(r) => ({id: r.id, title: r.title, books: r.books, bookCount: r.bookCount, bookDelCount: r.bookDelCount})`,
  692. where: `@@id(${db.esc(titleIds.slice(offset, offset + limit))})`
  693. });
  694. return {result, totalFound};
  695. } finally {
  696. this.searchFlag--;
  697. }
  698. }
  699. async getAuthorBookList(authorId) {
  700. if (this.closed)
  701. throw new Error('DbSearcher closed');
  702. if (!authorId)
  703. return {author: '', books: ''};
  704. this.searchFlag++;
  705. try {
  706. const db = this.db;
  707. //выборка книг автора по authorId
  708. const rows = await db.select({
  709. table: 'author_book',
  710. where: `@@id(${db.esc(authorId)})`
  711. });
  712. let author = '';
  713. let books = '';
  714. if (rows.length) {
  715. author = rows[0].author;
  716. books = rows[0].books;
  717. }
  718. return {author, books};
  719. } finally {
  720. this.searchFlag--;
  721. }
  722. }
  723. async getSeriesBookList(series) {
  724. if (this.closed)
  725. throw new Error('DbSearcher closed');
  726. if (!series)
  727. return {books: ''};
  728. this.searchFlag++;
  729. try {
  730. const db = this.db;
  731. series = series.toLowerCase();
  732. //выборка серии по названию серии
  733. let rows = await db.select({
  734. table: 'series',
  735. rawResult: true,
  736. where: `return Array.from(@dirtyIndexLR('value', ${db.esc(series)}, ${db.esc(series)}))`
  737. });
  738. let books;
  739. if (rows.length && rows[0].rawResult.length) {
  740. //выборка книг серии
  741. rows = await db.select({
  742. table: 'series_book',
  743. where: `@@id(${rows[0].rawResult[0]})`
  744. });
  745. if (rows.length)
  746. books = rows[0].books;
  747. }
  748. return {books: (books && books.length ? JSON.stringify(books) : '')};
  749. } finally {
  750. this.searchFlag--;
  751. }
  752. }
  753. async periodicCleanCache() {
  754. this.timer = null;
  755. const cleanInterval = this.config.cacheCleanInterval*60*1000;
  756. if (!cleanInterval)
  757. return;
  758. try {
  759. const db = this.db;
  760. const oldThres = Date.now() - cleanInterval;
  761. //выберем всех кандидатов на удаление
  762. const rows = await db.select({
  763. table: 'query_time',
  764. where: `
  765. @@iter(@all(), (r) => (r.time < ${db.esc(oldThres)}));
  766. `
  767. });
  768. const ids = [];
  769. for (const row of rows)
  770. ids.push(row.id);
  771. //удаляем
  772. await db.delete({table: 'query_cache', where: `@@id(${db.esc(ids)})`});
  773. await db.delete({table: 'query_time', where: `@@id(${db.esc(ids)})`});
  774. //console.log('Cache clean', ids);
  775. } catch(e) {
  776. console.error(e.message);
  777. } finally {
  778. if (!this.closed) {
  779. this.timer = setTimeout(() => { this.periodicCleanCache(); }, cleanInterval);
  780. }
  781. }
  782. }
  783. async close() {
  784. while (this.searchFlag > 0) {
  785. await utils.sleep(50);
  786. }
  787. this.searchCache = null;
  788. if (this.timer) {
  789. clearTimeout(this.timer);
  790. this.timer = null;
  791. }
  792. this.closed = true;
  793. }
  794. }
  795. module.exports = DbSearcher;