Elastic priority by matching space & class (#730)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2021-12-24 15:07:53 +06:00 committed by GitHub
parent 0049dad2d8
commit 816823f9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 64 additions and 30 deletions

View File

@ -117,16 +117,15 @@ export class FullTextIndex implements WithFind {
console.log('search', query)
const { _id, $search, ...mainQuery } = query
if ($search === undefined) return []
const docs = await this.adapter.search($search)
const docs = await this.adapter.search(_class, query, options?.limit)
console.log(docs)
const ids: Ref<Doc>[] = []
const ids: Set<Ref<Doc>> = new Set<Ref<Doc>>(docs.map(p => p.id))
for (const doc of docs) {
ids.push(doc.id)
if (doc.attachedTo !== undefined) {
ids.push(doc.attachedTo)
ids.add(doc.attachedTo)
}
}
return await this.dbStorage.findAll(ctx, _class, { _id: { $in: ids as any }, ...mainQuery }, options) // TODO: remove `as any`
return await this.dbStorage.findAll(ctx, _class, { _id: { $in: Array.from(ids) as any }, ...mainQuery }, options) // TODO: remove `as any`
}
private getFullTextAttributes (clazz: Ref<Class<Obj>>): AnyAttribute[] | undefined {
@ -203,6 +202,10 @@ export class FullTextIndex implements WithFind {
}
i++
}
if (tx.operations.space !== undefined) {
update.space = tx.operations.space
shouldUpdate = true
}
if (shouldUpdate) {
result = await this.adapter.update(tx.objectId, update)
await this.updateAttachedDocs(ctx, tx, update)
@ -229,7 +232,9 @@ export class FullTextIndex implements WithFind {
const docUpdate: any = {}
for (const key in update) {
const index = Number.parseInt(key.replace('content', ''))
docUpdate[`content${index + shift}`] = update[key]
if (!isNaN(index)) {
docUpdate[`content${index + shift}`] = update[key]
}
}
for (const attached of allAttached) {
await this.adapter.update(attached._id, docUpdate)

View File

@ -64,7 +64,7 @@ export interface IndexedDoc {
export interface FullTextAdapter {
index: (doc: IndexedDoc) => Promise<TxResult>
update: (id: Ref<Doc>, update: Record<string, any>) => Promise<TxResult>
search: (search: string) => Promise<IndexedDoc[]>
search: (_class: Ref<Class<Doc>>, search: DocumentQuery<Doc>, size: number | undefined) => Promise<IndexedDoc[]>
}
/**

View File

@ -14,7 +14,7 @@
// limitations under the License.
//
import type { Ref, Doc, Class, Obj, Account, Space } from '@anticrm/core'
import type { Ref, Doc, Class, Account, Space } from '@anticrm/core'
import { createElasticAdapter } from '../adapter'
import type { IndexedDoc } from '@anticrm/server-core'
@ -23,14 +23,14 @@ describe('client', () => {
const adapter = await createElasticAdapter('http://localhost:9200/', 'ws1')
const doc: IndexedDoc = {
id: 'doc1' as Ref<Doc>,
_class: 'class1' as Ref<Class<Obj>>,
_class: 'class1' as Ref<Class<Doc>>,
modifiedBy: 'andrey' as Ref<Account>,
modifiedOn: 0,
space: 'space1' as Ref<Space>,
content0: 'hey there!'
}
await adapter.index(doc)
const hits = await adapter.search('')
const hits = await adapter.search('class1' as Ref<Class<Doc>>, {}, 1)
console.log(hits)
})

View File

@ -14,33 +14,28 @@
// limitations under the License.
//
import type { Doc, Ref, TxResult } from '@anticrm/core'
import type { Class, Doc, DocumentQuery, Ref, TxResult } from '@anticrm/core'
import type { FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
import { Client } from '@elastic/elasticsearch'
class ElasticAdapter implements FullTextAdapter {
constructor (
private readonly client: Client,
private readonly db: string
) {
}
constructor (private readonly client: Client, private readonly db: string) {}
async close (): Promise<void> {
await this.client.close()
}
async search (
search: string
): Promise<IndexedDoc[]> {
const query = search.replace(/[\\/+\-=&><!()|{}^"~*&:[\]]/g, '\\$&')
try {
const result = await this.client.search({
index: this.db,
body: {
query: {
async search (_class: Ref<Class<Doc>>, query: DocumentQuery<Doc>, size: number | undefined): Promise<IndexedDoc[]> {
if (query.$search === undefined) return []
const search = query.$search.replace(/[\\/+\-=&><!()|{}^"~*&:[\]]/g, '\\$&')
const request: any = {
bool: {
must: [
{
multi_match: {
query: query,
query: search,
fields: [
'content0',
'content1',
@ -55,12 +50,43 @@ class ElasticAdapter implements FullTextAdapter {
'attachment.content'
]
}
},
size: 200
}
],
should: [
{
term: {
_class: {
value: _class,
case_insensitive: true
}
}
}
]
}
}
if (query.space != null) {
request.bool.should.push({
term: {
space: {
value: query.space,
boost: 2.0,
case_insensitive: true
}
}
})
}
try {
const result = await this.client.search({
index: this.db,
body: {
query: request,
size: size ?? 200
}
})
const hits = result.body.hits.hits as any[]
return hits.map(hit => hit._source)
return hits.map((hit) => hit._source)
} catch (err) {
console.error(JSON.stringify(err, null, 2))
return []
@ -115,7 +141,10 @@ class ElasticAdapter implements FullTextAdapter {
/**
* @public
*/
export async function createElasticAdapter (url: string, dbName: string): Promise<FullTextAdapter & {close: () => Promise<void>}> {
export async function createElasticAdapter (
url: string,
dbName: string
): Promise<FullTextAdapter & { close: () => Promise<void> }> {
const client = new Client({
node: url
})