// // 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>): 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 { const attrs: Record = {} 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>): 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).of)) { result = true break } } } } hierarchy.setClassifierProp(c, 'class_indexed', result) return result } /** * @public */ export function createStateDoc ( id: Ref, objectClass: Ref>, data: Omit, 'objectClass'> & { space?: Ref } ): DocIndexState { return { _class: core.class.DocIndexState, _id: id as Ref, 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 = 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 = 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> ): Omit> { let objClass = hierarchy.getClass(objectClass) while (true) { if (hierarchy.hasMixin(objClass, core.mixin.FullTextSearchContext)) { const ctx = hierarchy.as, 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: [] } }