mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-30 19:59:47 +02:00
193 lines
6.3 KiB
TypeScript
193 lines
6.3 KiB
TypeScript
import { Analytics } from '@hcengineering/analytics'
|
|
import type {
|
|
Doc,
|
|
Domain,
|
|
DomainIndexConfiguration,
|
|
FieldIndexConfig,
|
|
Hierarchy,
|
|
MeasureContext,
|
|
ModelDb,
|
|
WorkspaceUuid
|
|
} from '@hcengineering/core'
|
|
import core, { DOMAIN_BENCHMARK, DOMAIN_MODEL, IndexKind, IndexOrder } from '@hcengineering/core'
|
|
import { deepEqual } from 'fast-equals'
|
|
import type { DomainHelper, DomainHelperOperations } from './adapter'
|
|
|
|
export class DomainIndexHelperImpl implements DomainHelper {
|
|
domains = new Map<Domain, Set<FieldIndexConfig<Doc>>>()
|
|
domainConfigurations: DomainIndexConfiguration[] = []
|
|
constructor (
|
|
readonly ctx: MeasureContext,
|
|
readonly hierarchy: Hierarchy,
|
|
readonly model: ModelDb,
|
|
readonly workspaceId: WorkspaceUuid
|
|
) {
|
|
const classes = model.findAllSync(core.class.Class, {})
|
|
|
|
try {
|
|
this.domainConfigurations =
|
|
model.findAllSync<DomainIndexConfiguration>(core.class.DomainIndexConfiguration, {}) ?? []
|
|
} catch (err: any) {
|
|
this.domainConfigurations = []
|
|
Analytics.handleError(err)
|
|
ctx.error('failed to find domain index configuration', { err })
|
|
}
|
|
|
|
this.domains = new Map<Domain, Set<FieldIndexConfig<Doc>>>()
|
|
// Find all domains and indexed fields inside
|
|
for (const c of classes) {
|
|
try {
|
|
const domain = hierarchy.findDomain(c._id)
|
|
if (domain === undefined || domain === DOMAIN_MODEL || domain === DOMAIN_BENCHMARK) {
|
|
continue
|
|
}
|
|
const attrs = hierarchy.getAllAttributes(c._id)
|
|
const domainAttrs = this.domains.get(domain) ?? new Set<FieldIndexConfig<Doc>>()
|
|
for (const a of attrs.values()) {
|
|
if (a.isCustom === true) {
|
|
// Skip custom attribute indexes
|
|
continue
|
|
}
|
|
if (a.index !== undefined && a.index !== IndexKind.FullText) {
|
|
domainAttrs.add({
|
|
keys: {
|
|
[a.name]: a.index === IndexKind.Indexed ? IndexOrder.Ascending : IndexOrder.Descending
|
|
},
|
|
sparse: false // Default to non sparse indexes
|
|
})
|
|
}
|
|
}
|
|
|
|
// Handle extra configurations
|
|
if (hierarchy.hasMixin(c, core.mixin.IndexConfiguration)) {
|
|
const config = hierarchy.as(c, core.mixin.IndexConfiguration)
|
|
for (const attr of config.indexes) {
|
|
if (typeof attr === 'string') {
|
|
domainAttrs.add({ keys: { [attr]: IndexOrder.Ascending }, sparse: false })
|
|
} else {
|
|
domainAttrs.add(attr)
|
|
}
|
|
}
|
|
}
|
|
|
|
this.domains.set(domain, domainAttrs)
|
|
} catch (err: any) {
|
|
// Ignore, since we have classes without domain.
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if some indexes need to be created for domain.
|
|
*/
|
|
async checkDomain (
|
|
ctx: MeasureContext,
|
|
domain: Domain,
|
|
documents: number,
|
|
operations: DomainHelperOperations
|
|
): Promise<void> {
|
|
const domainInfo = this.domains.get(domain)
|
|
const cfg = this.domainConfigurations.find((it) => it.domain === domain)
|
|
|
|
const bb: (string | FieldIndexConfig<Doc>)[] = []
|
|
const added = new Set<string>()
|
|
|
|
try {
|
|
if (!(await operations.exists(domain))) {
|
|
return
|
|
}
|
|
const has50Documents = documents > 50
|
|
const allIndexes = (await operations.listIndexes(domain)).filter((it) => it.name !== '_id_')
|
|
ctx.info('check indexes', { domain, has50Documents, documents })
|
|
if (has50Documents) {
|
|
async function checkIndex (vv: string | FieldIndexConfig<Doc>, checkDisabled: boolean): Promise<void> {
|
|
try {
|
|
let name: string
|
|
if (typeof vv === 'string') {
|
|
name = `${vv}_sp_1`
|
|
} else {
|
|
let pfix = ''
|
|
if (vv.filter !== undefined) {
|
|
pfix += '_fi'
|
|
} else if (vv.sparse === true) {
|
|
pfix += '_sp'
|
|
}
|
|
name = Object.entries(vv.keys)
|
|
.map(([key, val]) => `${key + pfix}_${val}`)
|
|
.join('_')
|
|
}
|
|
|
|
// Check if index is disabled or not
|
|
let isDisabled =
|
|
cfg?.disabled?.some((it) => {
|
|
const _it = typeof it === 'string' ? { [it]: 1 } : it
|
|
const _vv = typeof vv === 'string' ? { [vv]: 1 } : vv.keys
|
|
return deepEqual(_it, _vv)
|
|
}) ?? false
|
|
|
|
if (!checkDisabled) {
|
|
isDisabled = false
|
|
}
|
|
if (isDisabled) {
|
|
// skip index since it is disabled
|
|
return
|
|
}
|
|
if (added.has(name)) {
|
|
// Index already added
|
|
return
|
|
}
|
|
added.add(name)
|
|
|
|
const existingOne = allIndexes.findIndex((it) => it.name === name)
|
|
if (existingOne !== -1) {
|
|
allIndexes.splice(existingOne, 1)
|
|
}
|
|
const exists = existingOne !== -1
|
|
// Check if index exists
|
|
if (!exists && !isDisabled) {
|
|
// Check if not disabled
|
|
bb.push(vv)
|
|
await operations.createIndex(domain, vv, {
|
|
name
|
|
})
|
|
}
|
|
} catch (err: any) {
|
|
Analytics.handleError(err)
|
|
ctx.error('error: failed to create index', { domain, vv, err })
|
|
}
|
|
}
|
|
|
|
for (const vv of [...(domainInfo?.values() ?? [])]) {
|
|
await checkIndex(vv, true)
|
|
}
|
|
for (const vv of [...(cfg?.indexes ?? [])]) {
|
|
await checkIndex(vv, false)
|
|
}
|
|
}
|
|
if (allIndexes.length > 0) {
|
|
for (const c of allIndexes) {
|
|
try {
|
|
if (cfg?.skip !== undefined) {
|
|
if (Array.from(cfg.skip ?? []).some((it) => c.name.includes(it))) {
|
|
continue
|
|
}
|
|
}
|
|
ctx.info('drop index', { domain, name: c.name, has50Documents })
|
|
await operations.dropIndex(domain, c.name)
|
|
} catch (err: any) {
|
|
Analytics.handleError(err)
|
|
console.error('error: failed to drop index', { c, err })
|
|
}
|
|
}
|
|
}
|
|
} catch (err: any) {
|
|
ctx.error('error during domain collections/indexes check', { domain, error: err })
|
|
Analytics.handleError(err)
|
|
}
|
|
|
|
if (bb.length > 0) {
|
|
ctx.info('created indexes', { domain, bb })
|
|
}
|
|
}
|
|
}
|