platform/server/core/src/indexer/utils.ts

240 lines
6.4 KiB
TypeScript
Raw Normal View History

//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import core, {
AnyAttribute,
AttachedDoc,
Class,
ClassifierKind,
Collection,
Data,
Doc,
DocIndexState,
DOMAIN_BLOB,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_FULLTEXT_BLOB,
DOMAIN_MODEL,
DOMAIN_TRANSIENT,
DOMAIN_TX,
FullTextSearchContext,
generateId,
Hierarchy,
IndexStageState,
isFullTextAttribute,
Obj,
Ref,
Space,
Storage,
TxFactory
} from '@hcengineering/core'
import { deepEqual } from 'fast-equals'
import plugin from '../plugin'
/**
* @public
*/
export function getFullTextAttributes (hierarchy: Hierarchy, clazz: Ref<Class<Obj>>): AnyAttribute[] {
const allAttributes = hierarchy.getAllAttributes(clazz)
const result: AnyAttribute[] = []
for (const [, attr] of allAttributes) {
if (isFullTextAttribute(attr)) {
result.push(attr)
}
}
hierarchy
.getDescendants(clazz)
.filter((m) => hierarchy.getClass(m).kind === ClassifierKind.MIXIN)
.forEach((m) => {
for (const [, v] of hierarchy.getAllAttributes(m, clazz)) {
if (isFullTextAttribute(v)) {
result.push(v)
}
}
})
return result
}
export { docKey, docUpdKey, extractDocKey, IndexKeyOptions, isFullTextAttribute } from '@hcengineering/core'
/**
* @public
*/
export function getContent (
hierarchy: Hierarchy,
attributes: AnyAttribute[],
doc: Doc
): Record<string, { value: any, attr: AnyAttribute }> {
const attrs: Record<string, { value: any, attr: AnyAttribute }> = {}
for (const attr of attributes) {
const isMixinAttr = hierarchy.isMixin(attr.attributeOf)
if (isMixinAttr) {
const key = (attr.attributeOf as string) + '.' + attr.name
const value = (doc as any)[attr.attributeOf]?.[attr.name]?.toString() ?? ''
attrs[key] = { value, attr }
} else {
const value = (doc as any)[attr.name]?.toString() ?? ''
attrs[attr.name] = { value, attr }
}
}
return attrs
}
/**
* @public
*/
export function isClassIndexable (hierarchy: Hierarchy, c: Ref<Class<Doc>>): boolean {
const indexed = hierarchy.getClassifierProp(c, 'class_indexed')
if (indexed !== undefined) {
return indexed as boolean
}
const domain = hierarchy.findDomain(c)
if (domain === undefined) {
hierarchy.setClassifierProp(c, 'class_indexed', false)
return false
}
if (
domain === DOMAIN_DOC_INDEX_STATE ||
domain === DOMAIN_TX ||
domain === DOMAIN_MODEL ||
domain === DOMAIN_BLOB ||
domain === DOMAIN_FULLTEXT_BLOB ||
domain === DOMAIN_TRANSIENT
) {
hierarchy.setClassifierProp(c, 'class_indexed', false)
return false
}
const attrs = getFullTextAttributes(hierarchy, c)
for (const d of hierarchy.getDescendants(c)) {
if (hierarchy.isMixin(d)) {
attrs.push(...getFullTextAttributes(hierarchy, d))
}
}
let result = true
if (attrs.length === 0 && !(getFullTextContext(hierarchy, c)?.forceIndex ?? false)) {
result = false
// We need check if document has collections with indexable fields.
const attrs = hierarchy.getAllAttributes(c).values()
for (const attr of attrs) {
if (attr.type._class === core.class.Collection) {
if (isClassIndexable(hierarchy, (attr.type as Collection<AttachedDoc>).of)) {
result = true
break
}
}
}
}
hierarchy.setClassifierProp(c, 'class_indexed', result)
return result
}
/**
* @public
*/
export function createStateDoc (
id: Ref<Doc>,
objectClass: Ref<Class<Obj>>,
data: Omit<Data<DocIndexState>, 'objectClass'> & { space?: Ref<Space> }
): DocIndexState {
return {
_class: core.class.DocIndexState,
_id: id as Ref<DocIndexState>,
space: data.space ?? plugin.space.DocIndexState,
objectClass,
modifiedBy: core.account.System,
modifiedOn: Date.now(),
...data
}
}
/**
* @public
*/
export async function loadIndexStageStage (
storage: Storage,
state: IndexStageState | undefined,
stageId: string,
field: string,
newValue: any
): Promise<[boolean | string, IndexStageState]> {
if (state === undefined) {
;[state] = await storage.findAll(core.class.IndexStageState, { stageId })
}
const attributes: Record<string, any> = state?.attributes ?? {}
let result: boolean | string | undefined = attributes?.index !== undefined ? `${attributes?.index as number}` : true
if (!deepEqual(attributes[field], newValue)) {
// Not match,
const newIndex = ((attributes.index as number) ?? 0) + 1
result = `${newIndex}`
const ops = new TxFactory(core.account.System)
const data = {
stageId,
attributes: {
[field]: newValue,
index: newIndex
}
}
if (state === undefined) {
const id: Ref<IndexStageState> = generateId()
await storage.tx(ops.createTxCreateDoc(core.class.IndexStageState, plugin.space.DocIndexState, data, id))
state = {
...data,
_class: core.class.IndexStageState,
_id: id,
space: plugin.space.DocIndexState,
modifiedBy: core.account.System,
modifiedOn: Date.now()
}
} else {
await storage.tx(ops.createTxUpdateDoc(core.class.IndexStageState, plugin.space.DocIndexState, state._id, data))
state = { ...state, ...data, modifiedOn: Date.now() }
}
}
return [result, state]
}
/**
* @public
*/
export function getFullTextContext (
hierarchy: Hierarchy,
objectClass: Ref<Class<Doc>>
): Omit<FullTextSearchContext, keyof Class<Doc>> {
let objClass = hierarchy.getClass(objectClass)
while (true) {
if (hierarchy.hasMixin(objClass, core.mixin.FullTextSearchContext)) {
const ctx = hierarchy.as<Class<Doc>, FullTextSearchContext>(objClass, core.mixin.FullTextSearchContext)
if (ctx !== undefined) {
return ctx
}
}
if (objClass.extends === undefined) {
break
}
objClass = hierarchy.getClass(objClass.extends)
}
return {
fullTextSummary: false,
forceIndex: false,
propogate: []
}
}