From 3fb31f17a86c5e347e7c7203a847d35bd673bdd8 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 24 Jun 2023 13:11:56 +0200 Subject: [PATCH] Adopting date pattern search to MySQL #660 --- src/backend/model/database/SearchManager.ts | 36 ++++++++++++------- .../unit/model/sql/SearchManager.spec.ts | 36 ++++++++++++------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 217ec6f6..579d0004 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -161,7 +161,7 @@ export class SearchManager { .groupBy( 'photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city' ) - .limit(Config.Search.AutoComplete.ItemsPerCategory.position ) + .limit(Config.Search.AutoComplete.ItemsPerCategory.position) .getRawMany() ) .filter((pm): boolean => !!pm) @@ -658,6 +658,9 @@ export class SearchManager { }); case SearchQueryTypes.date_pattern: { + if (directoryOnly) { + throw new Error('not supported in directoryOnly mode'); + } const tq = query as DatePatternSearch; return new Brackets((q): unknown => { @@ -719,33 +722,40 @@ export class SearchManager { const relationTop = tq.negate ? '>' : '<='; const relationBottom = tq.negate ? '<=' : '>'; + + const addWhere = (duration: string) => { + + if (Config.Database.type === DatabaseType.sqlite) { + q.where( + `CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('${duration}','now') AS INTEGER)` + ).andWhere(`CAST(strftime('${duration}',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('${duration}','now','-:diff${queryId} day') AS INTEGER)`, + textParam); + } else { + + q.where( + `CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationTop} CAST(DATE_FORMAT(CURDATE(),'${duration}') AS SIGNED)` + ).andWhere(`CAST(FROM_UNIXTIME(media.metadataCreationDate/1000, '${duration}') AS SIGNED) ${relationBottom} CAST(DATE_FORMAT((DATE_ADD(curdate(), INTERVAL -:diff${queryId} DAY)),'${duration}') AS SIGNED)`, + textParam); + } + }; switch (tq.frequency) { case DatePatternFrequency.every_year: if (tq.daysLength >= 365) { return q; } - q.where( - `CAST(strftime('%j',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%j','now') AS INTEGER)` - ).andWhere(`CAST(strftime('%j',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%j','now','-:diff${queryId} day') AS INTEGER)`, - textParam); + addWhere('%j'); break; case DatePatternFrequency.every_month: if (tq.daysLength >= 31) { return q; } - q.where( - `CAST(strftime('%d',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%d','now') AS INTEGER)` - ).andWhere(`CAST(strftime('%d',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%d','now','-:diff${queryId} day') AS INTEGER)`, - textParam); + addWhere('%d'); break; case DatePatternFrequency.every_week: if (tq.daysLength >= 7) { return q; } - q.where( - `CAST(strftime('%w',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationTop} CAST(strftime('%w','now') AS INTEGER)` - ).andWhere(`CAST(strftime('%w',media.metadataCreationDate/1000, 'unixepoch') AS INTEGER) ${relationBottom} CAST(strftime('%w','now','-:diff${queryId} day') AS INTEGER)`, - textParam); + addWhere('%w'); break; } diff --git a/test/backend/unit/model/sql/SearchManager.spec.ts b/test/backend/unit/model/sql/SearchManager.spec.ts index 4a29cf9f..61db7c3c 100644 --- a/test/backend/unit/model/sql/SearchManager.spec.ts +++ b/test/backend/unit/model/sql/SearchManager.spec.ts @@ -118,6 +118,7 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { p4.metadata.creationDate = Date.now() - 60 * 60 * 24 * 366 * 1000; const pFaceLessTmp = TestHelper.getPhotoEntry3(subDir); delete pFaceLessTmp.metadata.faces; + pFaceLessTmp.metadata.creationDate = Date.now() - 60 * 60 * 24 * 32 * 1000; dir = await DBTestHelper.persistTestDir(directory); @@ -189,19 +190,14 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { Config.Search.AutoComplete.ItemsPerCategory.maxItems = 1; expect((await sm.autocomplete('a', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([ - new AutoCompleteItem('Ajan Kloss', SearchQueryTypes.position), - new AutoCompleteItem('Tipoca City', SearchQueryTypes.position), - new AutoCompleteItem('Amber stone', SearchQueryTypes.caption), - new AutoCompleteItem('Millennium falcon', SearchQueryTypes.caption), - new AutoCompleteItem('star wars', SearchQueryTypes.keyword), - new AutoCompleteItem('Anakin Skywalker', SearchQueryTypes.person), - new AutoCompleteItem('Obivan Kenobi', SearchQueryTypes.person), - new AutoCompleteItem('Castilon', SearchQueryTypes.position), - new AutoCompleteItem('Devaron', SearchQueryTypes.position), - new AutoCompleteItem('Jedha', SearchQueryTypes.position), - new AutoCompleteItem('wars dir', SearchQueryTypes.directory), - new AutoCompleteItem('The Phantom Menace', SearchQueryTypes.directory)]); + new AutoCompleteItem('Han Solo', SearchQueryTypes.person), + new AutoCompleteItem('Han Solo\'s dice', SearchQueryTypes.caption), + new AutoCompleteItem('Research City', SearchQueryTypes.position), + new AutoCompleteItem('death star', SearchQueryTypes.keyword), + new AutoCompleteItem('wars dir', SearchQueryTypes.directory)]); Config.Search.AutoComplete.ItemsPerCategory.maxItems = 5; + Config.Search.AutoComplete.ItemsPerCategory.fileName = 5; + Config.Search.AutoComplete.ItemsPerCategory.fileName = 5; expect((await sm.autocomplete('sw', SearchQueryTypes.any_text))).to.deep.equalInAnyOrder([ new AutoCompleteItem('sw1.jpg', SearchQueryTypes.file_name), @@ -991,6 +987,22 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { resultOverflow: false } as SearchResultDTO)); + query = { + daysLength: 3, + agoNumber: 1, + frequency: DatePatternFrequency.months_ago, + type: SearchQueryTypes.date_pattern + } as DatePatternSearch; + + expect(Utils.clone(await sm.search(query))) + .to.deep.equalInAnyOrder(removeDir({ + searchQuery: query, + directories: [], + media: [pFaceLess], + metaFile: [], + resultOverflow: false + } as SearchResultDTO)); + query = { daysLength: 366, frequency: DatePatternFrequency.every_year,