2023-01-04 17:58:54 +00:00
|
|
|
//
|
|
|
|
// 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,
|
2023-01-31 05:48:40 +00:00
|
|
|
FullTextSearchContext,
|
2023-01-26 13:53:00 +00:00
|
|
|
generateId,
|
2023-01-04 17:58:54 +00:00
|
|
|
Hierarchy,
|
2023-01-26 13:53:00 +00:00
|
|
|
IndexStageState,
|
2023-01-04 17:58:54 +00:00
|
|
|
isFullTextAttribute,
|
|
|
|
Obj,
|
|
|
|
Ref,
|
2023-01-26 13:53:00 +00:00
|
|
|
Space,
|
|
|
|
Storage,
|
|
|
|
TxFactory
|
2023-01-04 17:58:54 +00:00
|
|
|
} from '@hcengineering/core'
|
2023-01-26 13:53:00 +00:00
|
|
|
import { deepEqual } from 'fast-equals'
|
2023-01-04 17:58:54 +00:00
|
|
|
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))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-31 05:48:40 +00:00
|
|
|
let result = true
|
|
|
|
|
|
|
|
if (attrs.length === 0 && !(getFullTextContext(hierarchy, c)?.forceIndex ?? false)) {
|
|
|
|
result = false
|
2023-01-04 17:58:54 +00:00
|
|
|
// 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)) {
|
2023-01-31 05:48:40 +00:00
|
|
|
result = true
|
|
|
|
break
|
2023-01-04 17:58:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-31 05:48:40 +00:00
|
|
|
hierarchy.setClassifierProp(c, 'class_indexed', result)
|
|
|
|
return result
|
2023-01-04 17:58:54 +00:00
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
}
|
|
|
|
}
|
2023-01-26 13:53:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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]
|
|
|
|
}
|
2023-01-31 05:48:40 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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: []
|
|
|
|
}
|
|
|
|
}
|