From: Bill Erickson Date: Wed, 27 Oct 2021 17:21:52 +0000 (-0400) Subject: LP1844418 ES rebasing X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=473f107a39ab2b8f24f8aeaa5b520671b0f00646;p=working%2FEvergreen.git LP1844418 ES rebasing Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/share/catalog/elastic.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/elastic.service.ts index 903dd0c48a..434ebddcd4 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/elastic.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/elastic.service.ts @@ -9,10 +9,20 @@ import {RequestBodySearch, MatchQuery, MultiMatchQuery, TermsQuery, Query, Sort, PrefixQuery, NestedQuery, BoolQuery, TermQuery, WildcardQuery, RangeQuery, QueryStringQuery} from 'elastic-builder'; +const INDEX_SHORTCUTS_MAP = { + 'au:': 'author\\*:', + 'ti:': 'title\\*:', + 'su:': 'subject\\*:', + 'kw:': 'keyword\\*:', + 'se:': 'series\\*:', + 'pb:': 'identifier|publisher\\*:' +}; + @Injectable() export class ElasticService { enabled: boolean; + ebfMap: {[id: number]: IdlObject} = {}; constructor( private idl: IdlService, @@ -21,6 +31,10 @@ export class ElasticService { private pcrud: PcrudService ) {} + init(): Promise { + return Promise.resolve(); + } + // Returns true if Elastic can provide search results. canSearch(ctx: CatalogSearchContext): boolean { if (!this.enabled) { return false; } @@ -32,8 +46,9 @@ export class ElasticService { return true; } - if (ctx.identSearch.isSearchable() - && ctx.identSearch.queryType !== 'item_barcode') { + if (ctx.identSearch.isSearchable() && + ctx.identSearch.queryType !== 'item_barcode' && + ctx.identSearch.queryType !== 'identifier|tcn') { return true; } @@ -48,7 +63,7 @@ export class ElasticService { let method = ctx.termSearch.isMetarecordSearch() ? 'open-ils.search.elastic.bib_search.metabib' : - 'open-ils.search.elastic.bib_search' + 'open-ils.search.elastic.bib_search'; if (ctx.isStaff) { method += '.staff'; } @@ -107,6 +122,9 @@ export class ElasticService { // Sort by match score by default. search.sort(new Sort('_score', 'desc')); } + + // Apply a tie-breaker sort on bib ID. + search.sort(new Sort('id', 'desc')); } addFilters(ctx: CatalogSearchContext, rootNode: BoolQuery) { @@ -128,7 +146,7 @@ export class ElasticService { ts.facetFilters.forEach(f => { if (f.facetValue !== '') { rootNode.filter(new TermQuery( - `${f.facetClass}|${f.facetName}.facet`, f.facetValue)); + `${f.facetClass}|${f.facetName}|facet`, f.facetValue)); } }); @@ -186,12 +204,10 @@ export class ElasticService { const marcQuery = new BoolQuery(); const tag = ms.tags[idx]; const subfield = ms.subfields[idx]; + const matchOp = ms.matchOp[idx]; - // Full-text search on the values - const valMatch = new MultiMatchQuery(['marc.value*'], value); - valMatch.operator('and'); - valMatch.type('most_fields'); - marcQuery.must(valMatch); + this.appendMatchOp( + marcQuery, matchOp, 'marc.value.text*', 'marc.value', value); if (tag) { marcQuery.must(new TermQuery('marc.tag', tag)); @@ -257,12 +273,20 @@ export class ElasticService { } else if (fieldClass === 'keyword' && matchOp === 'contains' && value.match(/:/)) { + // Map ti: to title\*: so the shortcut searches search + // across all sub-indexes. + let valueMod = value; + Object.keys(INDEX_SHORTCUTS_MAP).forEach(sc => { + const reg = new RegExp(sc, 'gi'); + valueMod = valueMod.replace(reg, INDEX_SHORTCUTS_MAP[sc]); + }); + // A search where 'keyword' 'contains' a value with a ':' // character is assumed to be a complex / query string search. // NOTE: could handle this differently, e.g. provide an escape // character (e.g. !title:potter), a dedicated matchOp, etc. boolNode.must( - new QueryStringQuery(value) + new QueryStringQuery(valueMod) .defaultOperator('AND') .defaultField('keyword.text') ); @@ -270,7 +294,22 @@ export class ElasticService { return; } - const textIndex = `${fieldClass}.text*`; + // KCLS ident searches: Identifier indices don't have text variations + let textIndex = fieldClass.match('identifier') ? + fieldClass : `${fieldClass}.text*`; + + // Bib call numbers use different indexes depending on the matchOp + if (fieldClass === 'identifier|bibcn' && + matchOp.match(/contains|nocontains|phrase/)) { + textIndex = 'keyword|bibcn.text*'; + } + + this.appendMatchOp(boolNode, matchOp, textIndex, fieldClass, value); + } + + appendMatchOp(boolNode: BoolQuery, matchOp: string, + textIndex: string, termIndex: string, value: string) { + let query; switch (matchOp) { @@ -297,15 +336,20 @@ export class ElasticService { boolNode.mustNot(query); return; - // "exact" and "starts" searches use term searches instead - // of full-text searches. + // "containsexact", "exact", "starts" searches use term + // searches instead of full-text searches. + case 'containsexact': + query = new WildcardQuery(termIndex, `*${value}*`); + boolNode.must(query); + return; + case 'exact': - query = new TermQuery(fieldClass, value); + query = new TermQuery(termIndex, value); boolNode.must(query); return; case 'starts': - query = new PrefixQuery(fieldClass, value); + query = new PrefixQuery(termIndex, value); boolNode.must(query); return; }