Enum sorting (#2808)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-03-22 21:40:47 +06:00 committed by GitHub
parent 746f2a809f
commit 27a3264f9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 30 deletions

View File

@ -27,7 +27,7 @@ import { toFindResult } from './utils'
/** /**
* @public * @public
*/ */
export abstract class MemDb extends TxProcessor { export abstract class MemDb extends TxProcessor implements Storage {
private readonly objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>() private readonly objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>()
private readonly objectById = new Map<Ref<Doc>, Doc>() private readonly objectById = new Map<Ref<Doc>, Doc>()
@ -162,7 +162,7 @@ export abstract class MemDb extends TxProcessor {
result = matchQuery(result, query, _class, this.hierarchy) result = matchQuery(result, query, _class, this.hierarchy)
} }
if (options?.sort !== undefined) resultSort(result, options?.sort, _class, this.hierarchy) if (options?.sort !== undefined) await resultSort(result, options?.sort, _class, this.hierarchy, this)
const total = result.length const total = result.length
result = result.slice(0, options?.limit) result = result.slice(0, options?.limit)
const tresult = this.hierarchy.clone(result) as WithLookup<T>[] const tresult = this.hierarchy.clone(result) as WithLookup<T>[]
@ -194,7 +194,7 @@ export abstract class MemDb extends TxProcessor {
* *
* @public * @public
*/ */
export class TxDb extends MemDb implements Storage { export class TxDb extends MemDb {
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> { protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
throw new Error('Method not implemented.') throw new Error('Method not implemented.')
} }
@ -222,7 +222,7 @@ export class TxDb extends MemDb implements Storage {
* *
* @public * @public
*/ */
export class ModelDb extends MemDb implements Storage { export class ModelDb extends MemDb {
protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> { protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
this.addDoc(TxProcessor.createDoc2Doc(tx)) this.addDoc(TxProcessor.createDoc2Doc(tx))
return {} return {}

View File

@ -1,9 +1,10 @@
import { DocumentQuery } from '.' import { DocumentQuery } from '.'
import { Class, Doc, Ref } from './classes' import { Class, Doc, Enum, EnumOf, Ref } from './classes'
import core from './component'
import { Hierarchy } from './hierarchy' import { Hierarchy } from './hierarchy'
import { getObjectValue } from './objvalue' import { getObjectValue } from './objvalue'
import { createPredicates, isPredicate } from './predicate' import { createPredicates, isPredicate } from './predicate'
import { SortingOrder, SortingQuery } from './storage' import { SortingOrder, SortingQuery, Storage } from './storage'
/** /**
* @public * @public
@ -30,19 +31,37 @@ function isArrayValueCheck<T, P> (val: T, value: P): boolean {
return Array.isArray(val) && !Array.isArray(value) && val.includes(value) return Array.isArray(val) && !Array.isArray(value) && val.includes(value)
} }
function getEnumValue<T extends Doc> (
key: string,
_class: Ref<Class<T>>,
hierarchy: Hierarchy,
obj: any,
_enum: Enum
): number {
const tkey = checkMixinKey(key, _class, hierarchy)
const value = getObjectValue(tkey, obj)
const index = _enum.enumValues.findIndex((p) => p === value)
return index === -1 ? _enum.enumValues.length : index
}
/** /**
* @public * @public
*/ */
export function resultSort<T extends Doc> ( export async function resultSort<T extends Doc> (
result: T[], result: T[],
sortOptions: SortingQuery<T>, sortOptions: SortingQuery<T>,
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
hierarchy: Hierarchy hierarchy: Hierarchy,
): void { modelDb: Storage
): Promise<void> {
const enums = await getEnums(_class, sortOptions, hierarchy, modelDb)
const sortFunc = (a: any, b: any): number => { const sortFunc = (a: any, b: any): number => {
for (const key in sortOptions) { for (const key in sortOptions) {
const aValue = getValue(key, a, _class, hierarchy) const _enum = enums[key]
const bValue = getValue(key, b, _class, hierarchy) const aValue =
_enum !== undefined ? getEnumValue(key, _class, hierarchy, a, _enum) : getValue(key, a, _class, hierarchy)
const bValue =
_enum !== undefined ? getEnumValue(key, _class, hierarchy, b, _enum) : getValue(key, b, _class, hierarchy)
const result = getSortingResult(aValue, bValue, sortOptions[key]) const result = getSortingResult(aValue, bValue, sortOptions[key])
if (result !== 0) return result if (result !== 0) return result
} }
@ -67,6 +86,28 @@ function getSortingResult (aValue: any, bValue: any, order: SortingOrder): numbe
return res * order return res * order
} }
async function getEnums<T extends Doc> (
_class: Ref<Class<T>>,
sortOptions: SortingQuery<T>,
hierarchy: Hierarchy,
modelDb: Storage
): Promise<Record<string, Enum>> {
const res: Record<string, Enum> = {}
for (const key in sortOptions) {
const attr = hierarchy.findAttribute(_class, key)
if (attr !== undefined) {
if (attr !== undefined) {
if (attr.type._class === core.class.EnumOf) {
const ref = (attr.type as EnumOf).of
const enu = await modelDb.findAll(core.class.Enum, { _id: ref })
res[key] = enu[0]
}
}
}
}
return res
}
function getValue<T extends Doc> (key: string, obj: any, _class: Ref<Class<T>>, hierarchy: Hierarchy): any { function getValue<T extends Doc> (key: string, obj: any, _class: Ref<Class<T>>, hierarchy: Hierarchy): any {
const tkey = checkMixinKey(key, _class, hierarchy) const tkey = checkMixinKey(key, _class, hierarchy)
let value = getObjectValue(tkey, obj) let value = getObjectValue(tkey, obj)

View File

@ -394,7 +394,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (currentRefresh) return {} if (currentRefresh) return {}
} }
} }
this.sort(q, tx) await this.sort(q, tx)
const udoc = q.result.find((p) => p._id === tx.objectId) const udoc = q.result.find((p) => p._id === tx.objectId)
await this.updatedDocCallback(udoc, q) await this.updatedDocCallback(udoc, q)
} else if (isMixin) { } else if (isMixin) {
@ -478,11 +478,11 @@ export class LiveQuery extends TxProcessor implements Client {
if (currentRefresh) return if (currentRefresh) return
} }
} }
this.sort(q, tx) await this.sort(q, tx)
const udoc = q.result.find((p) => p._id === tx.objectId) const udoc = q.result.find((p) => p._id === tx.objectId)
await this.updatedDocCallback(udoc, q) await this.updatedDocCallback(udoc, q)
} else if (await this.matchQuery(q, tx)) { } else if (await this.matchQuery(q, tx)) {
this.sort(q, tx) await this.sort(q, tx)
const udoc = q.result.find((p) => p._id === tx.objectId) const udoc = q.result.find((p) => p._id === tx.objectId)
await this.updatedDocCallback(udoc, q) await this.updatedDocCallback(udoc, q)
} }
@ -500,7 +500,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (needCallback) { if (needCallback) {
if (q.options?.sort !== undefined) { if (q.options?.sort !== undefined) {
resultSort(q.result, q.options?.sort, q._class, this.getHierarchy()) await resultSort(q.result, q.options?.sort, q._class, this.getHierarchy(), this.client.getModel())
} }
await this.callback(q) await this.callback(q)
} }
@ -725,7 +725,7 @@ export class LiveQuery extends TxProcessor implements Client {
q.total++ q.total++
if (q.options?.sort !== undefined) { if (q.options?.sort !== undefined) {
resultSort(q.result, q.options?.sort, q._class, this.getHierarchy()) await resultSort(q.result, q.options?.sort, q._class, this.getHierarchy(), this.client.getModel())
} }
if (q.options?.limit !== undefined && q.result.length > q.options.limit) { if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
@ -761,7 +761,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (needCallback) { if (needCallback) {
if (q.options?.sort !== undefined) { if (q.options?.sort !== undefined) {
resultSort(q.result, q.options?.sort, q._class, this.getHierarchy()) await resultSort(q.result, q.options?.sort, q._class, this.getHierarchy(), this.getModel())
} }
await this.callback(q) await this.callback(q)
} }
@ -863,7 +863,7 @@ export class LiveQuery extends TxProcessor implements Client {
} }
if (needCallback) { if (needCallback) {
if (q.options?.sort !== undefined) { if (q.options?.sort !== undefined) {
resultSort(q.result, q.options?.sort, q._class, this.getHierarchy()) await resultSort(q.result, q.options?.sort, q._class, this.getHierarchy(), this.getModel())
} }
await this.callback(q) await this.callback(q)
} }
@ -998,13 +998,13 @@ export class LiveQuery extends TxProcessor implements Client {
await this.__updateLookup(q, updatedDoc, ops) await this.__updateLookup(q, updatedDoc, ops)
} }
private sort (q: Query, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): void { private async sort (q: Query, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): Promise<void> {
const sort = q.options?.sort const sort = q.options?.sort
if (sort === undefined) return if (sort === undefined) return
let needSort = sort.modifiedBy !== undefined || sort.modifiedOn !== undefined let needSort = sort.modifiedBy !== undefined || sort.modifiedOn !== undefined
if (!needSort) needSort = this.checkNeedSort(sort, tx) if (!needSort) needSort = this.checkNeedSort(sort, tx)
if (needSort) resultSort(q.result as Doc[], sort, q._class, this.getHierarchy()) if (needSort) await resultSort(q.result as Doc[], sort, q._class, this.getHierarchy(), this.client.getModel())
} }
private checkNeedSort (sort: SortingQuery<Doc>, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): boolean { private checkNeedSort (sort: SortingQuery<Doc>, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): boolean {

View File

@ -41,7 +41,7 @@
let applications: Map<Ref<Vacancy>, ApplicationInfo> = new Map<Ref<Vacancy>, ApplicationInfo>() let applications: Map<Ref<Vacancy>, ApplicationInfo> = new Map<Ref<Vacancy>, ApplicationInfo>()
const applicantQuery = createQuery() const applicantQuery = createQuery()
$: applicantQuery.query( applicantQuery.query(
recruit.class.Applicant, recruit.class.Applicant,
{}, {},
(res) => { (res) => {

View File

@ -22,6 +22,8 @@ import core, {
Domain, Domain,
DOMAIN_MODEL, DOMAIN_MODEL,
DOMAIN_TX, DOMAIN_TX,
Enum,
EnumOf,
escapeLikeForRegexp, escapeLikeForRegexp,
FindOptions, FindOptions,
FindResult, FindResult,
@ -331,6 +333,39 @@ abstract class MongoAdapterBase implements DbAdapter {
} }
} }
private async fillSortPipeline<T extends Doc>(
clazz: Ref<Class<T>>,
options: FindOptions<T>,
pipeline: any[]
): Promise<void> {
if (options.sort !== undefined) {
const sort = {} as any
for (const _key in options.sort) {
const key = this.translateKey(_key, clazz)
const enumOf = await this.isEnumSortKey(clazz, _key)
if (enumOf === undefined) {
sort[key] = options.sort[_key] === SortingOrder.Ascending ? 1 : -1
} else {
const branches = enumOf.enumValues.map((value, index) => {
return { case: { $eq: [`$${key}`, value] }, then: index }
})
pipeline.push({
$addFields: {
[`sort_${key}`]: {
$switch: {
branches,
default: enumOf.enumValues.length
}
}
}
})
sort[`sort_${key}`] = options.sort[_key] === SortingOrder.Ascending ? 1 : -1
}
}
pipeline.push({ $sort: sort })
}
}
private async lookup<T extends Doc>( private async lookup<T extends Doc>(
clazz: Ref<Class<T>>, clazz: Ref<Class<T>>,
query: DocumentQuery<T>, query: DocumentQuery<T>,
@ -347,14 +382,7 @@ abstract class MongoAdapterBase implements DbAdapter {
} }
pipeline.push(match) pipeline.push(match)
const resultPipeline: any[] = [] const resultPipeline: any[] = []
if (options.sort !== undefined) { await this.fillSortPipeline(clazz, options, pipeline)
const sort = {} as any
for (const _key in options.sort) {
const key: string = this.translateKey(_key, clazz)
sort[key] = options.sort[_key] === SortingOrder.Ascending ? 1 : -1
}
pipeline.push({ $sort: sort })
}
if (options.limit !== undefined) { if (options.limit !== undefined) {
resultPipeline.push({ $limit: options.limit }) resultPipeline.push({ $limit: options.limit })
} }
@ -442,6 +470,30 @@ abstract class MongoAdapterBase implements DbAdapter {
return key return key
} }
private async isEnumSortKey<T extends Doc>(_class: Ref<Class<T>>, key: string): Promise<Enum | undefined> {
const attr = this.hierarchy.findAttribute(_class, key)
if (attr !== undefined) {
if (attr.type._class === core.class.EnumOf) {
const ref = (attr.type as EnumOf).of
const res = await this.modelDb.findAll(core.class.Enum, { _id: ref })
return res[0]
}
}
}
private isEnumSort<T extends Doc>(_class: Ref<Class<T>>, options?: FindOptions<T>): boolean {
if (options?.sort === undefined) return false
for (const key in options.sort) {
const attr = this.hierarchy.findAttribute(_class, key)
if (attr !== undefined) {
if (attr.type._class === core.class.EnumOf) {
return true
}
}
}
return false
}
async findAll<T extends Doc>( async findAll<T extends Doc>(
_class: Ref<Class<T>>, _class: Ref<Class<T>>,
query: DocumentQuery<T>, query: DocumentQuery<T>,
@ -449,7 +501,7 @@ abstract class MongoAdapterBase implements DbAdapter {
): Promise<FindResult<T>> { ): Promise<FindResult<T>> {
// TODO: rework this // TODO: rework this
if (options !== null && options !== undefined) { if (options !== null && options !== undefined) {
if (options.lookup !== undefined) { if (options.lookup !== undefined || this.isEnumSort(_class, options)) {
return await this.lookup(_class, query, options) return await this.lookup(_class, query, options)
} }
} }