Elastic fulltext search mixins support (#770)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2022-01-06 18:09:45 +07:00 committed by GitHub
parent 7ebc44b8c0
commit 4328fdee9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 54 deletions

View File

@ -52,6 +52,10 @@ class NullFullTextAdapter implements FullTextAdapter {
async search (query: any): Promise<IndexedDoc[]> { async search (query: any): Promise<IndexedDoc[]> {
return [] return []
} }
async remove (id: Ref<Doc>): Promise<void> {
}
} }
async function createNullFullTextAdapter (): Promise<FullTextAdapter> { async function createNullFullTextAdapter (): Promise<FullTextAdapter> {

View File

@ -36,6 +36,10 @@ class NullFullTextAdapter implements FullTextAdapter {
async search (query: any): Promise<IndexedDoc[]> { async search (query: any): Promise<IndexedDoc[]> {
return [] return []
} }
async remove (id: Ref<Doc>): Promise<void> {
}
} }
async function createNullFullTextAdapter (): Promise<FullTextAdapter> { async function createNullFullTextAdapter (): Promise<FullTextAdapter> {

View File

@ -14,8 +14,8 @@
// limitations under the License. // limitations under the License.
// //
import type { AttachedDoc, Class, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc } from '@anticrm/core'
import core, { import core, {
AttachedDoc, Class, ClassifierKind, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc,
AnyAttribute, AnyAttribute,
Collection, Collection,
DocumentQuery, DocumentQuery,
@ -56,12 +56,28 @@ export class FullTextIndex implements WithFind {
protected async txRemoveDoc (ctx: MeasureContext, tx: TxRemoveDoc<Doc>): Promise<TxResult> { protected async txRemoveDoc (ctx: MeasureContext, tx: TxRemoveDoc<Doc>): Promise<TxResult> {
// console.log('FullTextIndex.txRemoveDoc: Method not implemented.') // console.log('FullTextIndex.txRemoveDoc: Method not implemented.')
await this.adapter.remove(tx.objectId)
return {} return {}
} }
protected async txMixin (ctx: MeasureContext, tx: TxMixin<Doc, Doc>): Promise<TxResult> { protected async txMixin (ctx: MeasureContext, tx: TxMixin<Doc, Doc>): Promise<TxResult> {
console.log('FullTextIndex.txMixin: Method not implemented') const attributes = this.getFullTextAttributes(tx.mixin)
return {} let result = {}
if (attributes === undefined) return result
const ops: any = tx.attributes
const update: any = {}
let shouldUpdate = false
for (const attr of attributes) {
if (ops[attr.name] !== undefined) {
update[(tx.mixin as string) + '.' + attr.name] = ops[attr.name]
shouldUpdate = true
}
}
if (shouldUpdate) {
result = await this.adapter.update(tx.objectId, update)
await this.updateAttachedDocs(ctx, tx, update)
}
return result
} }
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> { async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
@ -152,7 +168,7 @@ export class FullTextIndex implements WithFind {
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> { protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
const attributes = this.getFullTextAttributes(tx.objectClass) const attributes = this.getFullTextAttributes(tx.objectClass)
const doc = TxProcessor.createDoc2Doc(tx) const doc = TxProcessor.createDoc2Doc(tx)
let parentContent: any[] = [] let parentContent: Record<string, string> = {}
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) { if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
const attachedDoc = doc as AttachedDoc const attachedDoc = doc as AttachedDoc
const parentDoc = ( const parentDoc = (
@ -163,9 +179,10 @@ export class FullTextIndex implements WithFind {
parentContent = this.getContent(parentAttributes, parentDoc) parentContent = this.getContent(parentAttributes, parentDoc)
} }
} }
if (attributes === undefined && parentContent.length === 0) return {} if (attributes === undefined && Object.keys(parentContent).length === 0) return {}
let content = this.getContent(attributes, doc) let content = this.getContent(attributes, doc)
content = content.concat(parentContent) content = { ...parentContent, ...content }
const indexedDoc: IndexedDoc = { const indexedDoc: IndexedDoc = {
id: doc._id, id: doc._id,
_class: doc._class, _class: doc._class,
@ -173,16 +190,7 @@ export class FullTextIndex implements WithFind {
modifiedOn: doc.modifiedOn, modifiedOn: doc.modifiedOn,
space: doc.space, space: doc.space,
attachedTo: (doc as AttachedDoc).attachedTo, attachedTo: (doc as AttachedDoc).attachedTo,
content0: content[0], ...content
content1: content[1],
content2: content[2],
content3: content[3],
content4: content[4],
content5: content[5],
content6: content[6],
content7: content[7],
content8: content[8],
content9: content[9]
} }
return await this.adapter.index(indexedDoc) return await this.adapter.index(indexedDoc)
} }
@ -193,14 +201,12 @@ export class FullTextIndex implements WithFind {
if (attributes === undefined) return result if (attributes === undefined) return result
const ops: any = tx.operations const ops: any = tx.operations
const update: any = {} const update: any = {}
let i = 0
let shouldUpdate = false let shouldUpdate = false
for (const attr of attributes) { for (const attr of attributes) {
if (ops[attr.name] !== undefined) { if (ops[attr.name] !== undefined) {
update[`content${i}`] = ops[attr.name] update[attr.name] = ops[attr.name]
shouldUpdate = true shouldUpdate = true
} }
i++
} }
if (tx.operations.space !== undefined) { if (tx.operations.space !== undefined) {
update.space = tx.operations.space update.space = tx.operations.space
@ -213,28 +219,37 @@ export class FullTextIndex implements WithFind {
return result return result
} }
private getContent (attributes: AnyAttribute[] | undefined, doc: Doc): any[] { private getContent (attributes: AnyAttribute[] | undefined, doc: Doc): Record<string, string> {
if (attributes === undefined) return [] const attrs: Record<string, string> = {}
return attributes.map((attr) => (doc as any)[attr.name]?.toString() ?? '')
for (const attr of attributes ?? []) {
attrs[attr.name] = (doc as any)[attr.name]?.toString() ?? ''
}
return attrs
} }
private async updateAttachedDocs (ctx: MeasureContext, tx: TxUpdateDoc<Doc>, update: any): Promise<void> { private async updateAttachedDocs (ctx: MeasureContext, tx: {objectId: Ref<Doc>, objectClass: Ref<Class<Doc>>}, update: any): Promise<void> {
const doc = (await this.dbStorage.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0] const doc = (await this.dbStorage.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
if (doc === undefined) return if (doc === undefined) return
const attributes = this.hierarchy.getAllAttributes(doc._class) const attributes = this.hierarchy.getAllAttributes(doc._class)
// Find all mixin atttibutes for document.
this.hierarchy.getDescendants(doc._class)
.filter((m) => this.hierarchy.getClass(m).kind === ClassifierKind.MIXIN && this.hierarchy.hasMixin(doc, m))
.forEach((m) => {
for (const [k, v] of this.hierarchy.getAllAttributes(m, doc._class)) {
attributes.set(k, v)
}
})
for (const attribute of attributes.values()) { for (const attribute of attributes.values()) {
if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) { if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
const collection = attribute.type as Collection<AttachedDoc> const collection = attribute.type as Collection<AttachedDoc>
const allAttached = await this.dbStorage.findAll(ctx, collection.of, { attachedTo: tx.objectId }) const allAttached = await this.dbStorage.findAll(ctx, collection.of, { attachedTo: tx.objectId })
if (allAttached.length === 0) continue if (allAttached.length === 0) continue
const attributes = this.getFullTextAttributes(tx.objectClass)
const shift = attributes?.length ?? 0
const docUpdate: any = {} const docUpdate: any = {}
for (const key in update) { for (const key in update) {
const index = Number.parseInt(key.replace('content', '')) docUpdate[key] = update[key]
if (!isNaN(index)) {
docUpdate[`content${index + shift}`] = update[key]
}
} }
for (const attached of allAttached) { for (const attached of allAttached) {
try { try {

View File

@ -45,17 +45,8 @@ export interface IndexedDoc {
modifiedOn: Timestamp modifiedOn: Timestamp
modifiedBy: Ref<Account> modifiedBy: Ref<Account>
attachedTo?: Ref<Doc> attachedTo?: Ref<Doc>
content0?: string
content1?: string [key: string]: any
content2?: string
content3?: string
content4?: string
content5?: string
content6?: string
content7?: string
content8?: string
content9?: string
data?: string
} }
/** /**
@ -64,6 +55,7 @@ export interface IndexedDoc {
export interface FullTextAdapter { export interface FullTextAdapter {
index: (doc: IndexedDoc) => Promise<TxResult> index: (doc: IndexedDoc) => Promise<TxResult>
update: (id: Ref<Doc>, update: Record<string, any>) => Promise<TxResult> update: (id: Ref<Doc>, update: Record<string, any>) => Promise<TxResult>
remove: (id: Ref<Doc>) => Promise<void>
search: (_class: Ref<Class<Doc>>, search: DocumentQuery<Doc>, size: number | undefined) => Promise<IndexedDoc[]> search: (_class: Ref<Class<Doc>>, search: DocumentQuery<Doc>, size: number | undefined) => Promise<IndexedDoc[]>
} }

View File

@ -35,20 +35,7 @@ class ElasticAdapter implements FullTextAdapter {
must: [ must: [
{ {
multi_match: { multi_match: {
query: search, query: search
fields: [
'content0',
'content1',
'content2',
'content3',
'content4',
'content5',
'content6',
'content7',
'content8',
'content9',
'attachment.content'
]
} }
} }
], ],
@ -124,6 +111,13 @@ class ElasticAdapter implements FullTextAdapter {
return {} return {}
} }
async remove (id: Ref<Doc>): Promise<void> {
await this.client.delete({
index: this.db,
id
})
}
} }
/** /**

View File

@ -78,6 +78,10 @@ class NullFullTextAdapter implements FullTextAdapter {
async search (query: any): Promise<IndexedDoc[]> { async search (query: any): Promise<IndexedDoc[]> {
return [] return []
} }
async remove (id: Ref<Doc>): Promise<void> {
}
} }
async function createNullFullTextAdapter (): Promise<FullTextAdapter> { async function createNullFullTextAdapter (): Promise<FullTextAdapter> {