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
*/
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 objectById = new Map<Ref<Doc>, Doc>()
@ -162,7 +162,7 @@ export abstract class MemDb extends TxProcessor {
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
result = result.slice(0, options?.limit)
const tresult = this.hierarchy.clone(result) as WithLookup<T>[]
@ -194,7 +194,7 @@ export abstract class MemDb extends TxProcessor {
*
* @public
*/
export class TxDb extends MemDb implements Storage {
export class TxDb extends MemDb {
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
throw new Error('Method not implemented.')
}
@ -222,7 +222,7 @@ export class TxDb extends MemDb implements Storage {
*
* @public
*/
export class ModelDb extends MemDb implements Storage {
export class ModelDb extends MemDb {
protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
this.addDoc(TxProcessor.createDoc2Doc(tx))
return {}

View File

@ -1,9 +1,10 @@
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 { getObjectValue } from './objvalue'
import { createPredicates, isPredicate } from './predicate'
import { SortingOrder, SortingQuery } from './storage'
import { SortingOrder, SortingQuery, Storage } from './storage'
/**
* @public
@ -30,19 +31,37 @@ function isArrayValueCheck<T, P> (val: T, value: P): boolean {
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
*/
export function resultSort<T extends Doc> (
export async function resultSort<T extends Doc> (
result: T[],
sortOptions: SortingQuery<T>,
_class: Ref<Class<T>>,
hierarchy: Hierarchy
): void {
hierarchy: Hierarchy,
modelDb: Storage
): Promise<void> {
const enums = await getEnums(_class, sortOptions, hierarchy, modelDb)
const sortFunc = (a: any, b: any): number => {
for (const key in sortOptions) {
const aValue = getValue(key, a, _class, hierarchy)
const bValue = getValue(key, b, _class, hierarchy)
const _enum = enums[key]
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])
if (result !== 0) return result
}
@ -67,6 +86,28 @@ function getSortingResult (aValue: any, bValue: any, order: SortingOrder): numbe
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 {
const tkey = checkMixinKey(key, _class, hierarchy)
let value = getObjectValue(tkey, obj)

View File

@ -394,7 +394,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (currentRefresh) return {}
}
}
this.sort(q, tx)
await this.sort(q, tx)
const udoc = q.result.find((p) => p._id === tx.objectId)
await this.updatedDocCallback(udoc, q)
} else if (isMixin) {
@ -478,11 +478,11 @@ export class LiveQuery extends TxProcessor implements Client {
if (currentRefresh) return
}
}
this.sort(q, tx)
await this.sort(q, tx)
const udoc = q.result.find((p) => p._id === tx.objectId)
await this.updatedDocCallback(udoc, q)
} 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)
await this.updatedDocCallback(udoc, q)
}
@ -500,7 +500,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (needCallback) {
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)
}
@ -725,7 +725,7 @@ export class LiveQuery extends TxProcessor implements Client {
q.total++
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) {
@ -761,7 +761,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (needCallback) {
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)
}
@ -863,7 +863,7 @@ export class LiveQuery extends TxProcessor implements Client {
}
if (needCallback) {
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)
}
@ -998,13 +998,13 @@ export class LiveQuery extends TxProcessor implements Client {
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
if (sort === undefined) return
let needSort = sort.modifiedBy !== undefined || sort.modifiedOn !== undefined
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 {

View File

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

View File

@ -22,6 +22,8 @@ import core, {
Domain,
DOMAIN_MODEL,
DOMAIN_TX,
Enum,
EnumOf,
escapeLikeForRegexp,
FindOptions,
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>(
clazz: Ref<Class<T>>,
query: DocumentQuery<T>,
@ -347,14 +382,7 @@ abstract class MongoAdapterBase implements DbAdapter {
}
pipeline.push(match)
const resultPipeline: any[] = []
if (options.sort !== undefined) {
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 })
}
await this.fillSortPipeline(clazz, options, pipeline)
if (options.limit !== undefined) {
resultPipeline.push({ $limit: options.limit })
}
@ -442,6 +470,30 @@ abstract class MongoAdapterBase implements DbAdapter {
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>(
_class: Ref<Class<T>>,
query: DocumentQuery<T>,
@ -449,7 +501,7 @@ abstract class MongoAdapterBase implements DbAdapter {
): Promise<FindResult<T>> {
// TODO: rework this
if (options !== null && options !== undefined) {
if (options.lookup !== undefined) {
if (options.lookup !== undefined || this.isEnumSort(_class, options)) {
return await this.lookup(_class, query, options)
}
}