2021-08-18 19:46:59 +00:00
|
|
|
//
|
|
|
|
// Copyright © 2020 Anticrm Platform Contributors.
|
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
|
2021-12-22 09:02:51 +00:00
|
|
|
import core, {
|
2021-12-02 09:07:25 +00:00
|
|
|
Class,
|
|
|
|
Doc,
|
2022-02-16 09:02:31 +00:00
|
|
|
DocumentQuery, DOMAIN_MODEL, DOMAIN_TX, escapeLikeForRegexp, FindOptions, FindResult, Hierarchy, isOperator, Lookup, Mixin, ModelDb, Ref, ReverseLookups, SortingOrder, Tx,
|
2021-12-02 09:07:25 +00:00
|
|
|
TxCreateDoc,
|
2021-12-22 09:02:51 +00:00
|
|
|
TxMixin, TxProcessor, TxPutBag,
|
2021-12-02 09:07:25 +00:00
|
|
|
TxRemoveDoc,
|
|
|
|
TxResult,
|
|
|
|
TxUpdateDoc
|
|
|
|
} from '@anticrm/core'
|
2021-08-26 12:16:45 +00:00
|
|
|
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
2022-01-12 09:06:04 +00:00
|
|
|
import { Collection, Db, Document, Filter, MongoClient, Sort } from 'mongodb'
|
2021-12-02 09:07:25 +00:00
|
|
|
import { getMongoClient } from './utils'
|
2021-08-18 19:46:59 +00:00
|
|
|
|
|
|
|
function translateDoc (doc: Doc): Document {
|
|
|
|
return doc as Document
|
|
|
|
}
|
|
|
|
|
2022-01-31 09:06:30 +00:00
|
|
|
interface LookupStep {
|
|
|
|
from: string
|
|
|
|
localField: string
|
|
|
|
foreignField: string
|
|
|
|
as: string
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
abstract class MongoAdapterBase extends TxProcessor {
|
2022-01-12 09:06:04 +00:00
|
|
|
constructor (protected readonly db: Db, protected readonly hierarchy: Hierarchy, protected readonly modelDb: ModelDb, protected readonly client: MongoClient) {
|
2021-08-18 19:46:59 +00:00
|
|
|
super()
|
|
|
|
}
|
|
|
|
|
2021-08-25 17:55:26 +00:00
|
|
|
async init (): Promise<void> {}
|
|
|
|
|
2022-01-12 09:06:04 +00:00
|
|
|
async close (): Promise<void> {
|
|
|
|
await this.client.close()
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:07:25 +00:00
|
|
|
private translateQuery<T extends Doc>(clazz: Ref<Class<T>>, query: DocumentQuery<T>): Filter<Document> {
|
2021-10-01 13:33:49 +00:00
|
|
|
const translated: any = {}
|
|
|
|
for (const key in query) {
|
|
|
|
const value = (query as any)[key]
|
2022-03-24 09:04:29 +00:00
|
|
|
|
|
|
|
const tkey = this.checkMixinKey(key, clazz)
|
2021-10-10 13:20:20 +00:00
|
|
|
if (value !== null && typeof value === 'object') {
|
2021-10-01 13:33:49 +00:00
|
|
|
const keys = Object.keys(value)
|
|
|
|
if (keys[0] === '$like') {
|
|
|
|
const pattern = value.$like as string
|
2022-03-24 09:04:29 +00:00
|
|
|
translated[tkey] = {
|
2022-02-16 09:02:31 +00:00
|
|
|
$regex: `^${pattern.split('%').map(it => escapeLikeForRegexp(it)).join('.*')}$`,
|
2021-10-01 13:33:49 +00:00
|
|
|
$options: 'i'
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2022-03-24 09:04:29 +00:00
|
|
|
translated[tkey] = value
|
2021-10-01 13:33:49 +00:00
|
|
|
}
|
2021-12-30 09:04:32 +00:00
|
|
|
const baseClass = this.hierarchy.getBaseClass(clazz)
|
|
|
|
const classes = this.hierarchy.getDescendants(baseClass)
|
2021-11-19 07:41:20 +00:00
|
|
|
|
|
|
|
// Only replace if not specified
|
|
|
|
if (translated._class?.$in === undefined) {
|
|
|
|
translated._class = { $in: classes }
|
|
|
|
}
|
2021-12-30 09:04:32 +00:00
|
|
|
|
|
|
|
if (baseClass !== clazz) {
|
|
|
|
// Add an mixin to be exists flag
|
|
|
|
translated[clazz] = { $exists: true }
|
|
|
|
}
|
2021-10-01 13:33:49 +00:00
|
|
|
// return Object.assign({}, query, { _class: { $in: classes } })
|
|
|
|
return translated
|
2021-09-08 08:34:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-31 09:06:30 +00:00
|
|
|
private async getLookupValue<T extends Doc> (lookup: Lookup<T>, result: LookupStep[], parent?: string): Promise<void> {
|
|
|
|
for (const key in lookup) {
|
|
|
|
if (key === '_id') {
|
|
|
|
await this.getReverseLookupValue(lookup, result, parent)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const value = (lookup as any)[key]
|
|
|
|
const fullKey = parent !== undefined ? parent + '.' + key : key
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
const [_class, nested] = value
|
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
|
|
|
if (domain !== DOMAIN_MODEL) {
|
|
|
|
result.push({
|
|
|
|
from: domain,
|
|
|
|
localField: fullKey,
|
|
|
|
foreignField: '_id',
|
|
|
|
as: fullKey.split('.').join('') + '_lookup'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
await this.getLookupValue(nested, result, fullKey + '_lookup')
|
|
|
|
} else {
|
|
|
|
const _class = value as Ref<Class<Doc>>
|
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
|
|
|
if (domain !== DOMAIN_MODEL) {
|
|
|
|
result.push({
|
|
|
|
from: domain,
|
|
|
|
localField: fullKey,
|
|
|
|
foreignField: '_id',
|
|
|
|
as: fullKey.split('.').join('') + '_lookup'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getReverseLookupValue (lookup: ReverseLookups, result: LookupStep[], parent?: string): Promise<any | undefined> {
|
|
|
|
const fullKey = parent !== undefined ? parent + '.' + '_id' : '_id'
|
|
|
|
for (const key in lookup._id) {
|
|
|
|
const as = parent !== undefined ? parent + key : key
|
|
|
|
const value = lookup._id[key]
|
2022-03-04 09:01:46 +00:00
|
|
|
|
|
|
|
let _class: Ref<Class<Doc>>
|
|
|
|
let attr = 'attachedTo'
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
_class = value[0]
|
|
|
|
attr = value[1]
|
|
|
|
} else {
|
|
|
|
_class = value
|
|
|
|
}
|
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
2022-01-31 09:06:30 +00:00
|
|
|
if (domain !== DOMAIN_MODEL) {
|
|
|
|
const step = {
|
|
|
|
from: domain,
|
|
|
|
localField: fullKey,
|
2022-03-04 09:01:46 +00:00
|
|
|
foreignField: attr,
|
2022-01-31 09:06:30 +00:00
|
|
|
as: as.split('.').join('') + '_lookup'
|
|
|
|
}
|
|
|
|
result.push(step)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getLookups<T extends Doc> (lookup: Lookup<T> | undefined, parent?: string): Promise<LookupStep[]> {
|
|
|
|
if (lookup === undefined) return []
|
|
|
|
const result: [] = []
|
|
|
|
await this.getLookupValue(lookup, result, parent)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
private async fillLookup<T extends Doc> (_class: Ref<Class<T>>, object: any, key: string, fullKey: string, targetObject: any): Promise<void> {
|
|
|
|
if (targetObject.$lookup === undefined) {
|
|
|
|
targetObject.$lookup = {}
|
|
|
|
}
|
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
|
|
|
if (domain !== DOMAIN_MODEL) {
|
|
|
|
const arr = object[fullKey]
|
2022-03-14 09:05:02 +00:00
|
|
|
if (arr !== undefined && Array.isArray(arr)) {
|
|
|
|
if (arr.length === 1) {
|
|
|
|
targetObject.$lookup[key] = arr[0]
|
|
|
|
} else if (arr.length > 1) {
|
|
|
|
targetObject.$lookup[key] = arr
|
|
|
|
}
|
|
|
|
}
|
2022-01-31 09:06:30 +00:00
|
|
|
} else {
|
|
|
|
targetObject.$lookup[key] = this.modelDb.getObject(targetObject[key])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async fillLookupValue<T extends Doc> (lookup: Lookup<T> | undefined, object: any, parent?: string, parentObject?: any): Promise<void> {
|
|
|
|
if (lookup === undefined) return
|
|
|
|
for (const key in lookup) {
|
|
|
|
if (key === '_id') {
|
|
|
|
await this.fillReverseLookup(lookup, object, parent, parentObject)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const value = (lookup as any)[key]
|
|
|
|
const fullKey = parent !== undefined ? parent + key + '_lookup' : key + '_lookup'
|
|
|
|
const targetObject = parentObject ?? object
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
const [_class, nested] = value
|
|
|
|
await this.fillLookup(_class, object, key, fullKey, targetObject)
|
|
|
|
await this.fillLookupValue(nested, object, fullKey, targetObject.$lookup[key])
|
|
|
|
} else {
|
|
|
|
await this.fillLookup(value, object, key, fullKey, targetObject)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async fillReverseLookup (lookup: ReverseLookups, object: any, parent?: string, parentObject?: any): Promise<void> {
|
|
|
|
const targetObject = parentObject ?? object
|
|
|
|
if (targetObject.$lookup === undefined) {
|
|
|
|
targetObject.$lookup = {}
|
|
|
|
}
|
|
|
|
for (const key in lookup._id) {
|
|
|
|
const value = lookup._id[key]
|
2022-03-04 09:01:46 +00:00
|
|
|
let _class: Ref<Class<Doc>>
|
|
|
|
let attr = 'attachedTo'
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
_class = value[0]
|
|
|
|
attr = value[1]
|
|
|
|
} else {
|
|
|
|
_class = value
|
|
|
|
}
|
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
2022-01-31 09:06:30 +00:00
|
|
|
const fullKey = parent !== undefined ? parent + key + '_lookup' : key + '_lookup'
|
|
|
|
if (domain !== DOMAIN_MODEL) {
|
|
|
|
const arr = object[fullKey]
|
|
|
|
targetObject.$lookup[key] = arr
|
|
|
|
} else {
|
2022-03-04 09:01:46 +00:00
|
|
|
const arr = await this.modelDb.findAll(_class, { [attr]: targetObject._id })
|
2022-01-31 09:06:30 +00:00
|
|
|
targetObject.$lookup[key] = arr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:07:25 +00:00
|
|
|
private async lookup<T extends Doc>(
|
|
|
|
clazz: Ref<Class<T>>,
|
|
|
|
query: DocumentQuery<T>,
|
|
|
|
options: FindOptions<T>
|
|
|
|
): Promise<FindResult<T>> {
|
2021-09-03 14:34:08 +00:00
|
|
|
const pipeline = []
|
2021-09-08 08:34:48 +00:00
|
|
|
pipeline.push({ $match: this.translateQuery(clazz, query) })
|
2022-01-31 09:06:30 +00:00
|
|
|
const steps = await this.getLookups(options.lookup)
|
|
|
|
for (const step of steps) {
|
|
|
|
pipeline.push({ $lookup: step })
|
2021-09-03 14:34:08 +00:00
|
|
|
}
|
2021-12-08 09:14:55 +00:00
|
|
|
if (options.sort !== undefined) {
|
|
|
|
const sort = {} as any
|
|
|
|
for (const _key in options.sort) {
|
|
|
|
let key = _key as string
|
2022-01-31 09:06:30 +00:00
|
|
|
const arr = key.split('.').filter((p) => p)
|
|
|
|
key = ''
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
|
|
const element = arr[i]
|
|
|
|
if (element === '$lookup') {
|
|
|
|
key += arr[++i] + '_lookup'
|
|
|
|
} else {
|
|
|
|
if (!key.endsWith('.') && i > 0) {
|
|
|
|
key += '.'
|
|
|
|
}
|
|
|
|
key += arr[i]
|
|
|
|
if (i !== arr.length - 1) {
|
|
|
|
key += '.'
|
|
|
|
}
|
|
|
|
}
|
2022-02-02 09:04:13 +00:00
|
|
|
// Check if key is belong to mixin class, we need to add prefix.
|
|
|
|
key = this.checkMixinKey<T>(key, clazz)
|
2021-12-08 09:14:55 +00:00
|
|
|
}
|
|
|
|
sort[key] = options.sort[_key] === SortingOrder.Ascending ? 1 : -1
|
|
|
|
}
|
|
|
|
pipeline.push({ $sort: sort })
|
|
|
|
}
|
|
|
|
if (options.limit !== undefined) {
|
|
|
|
pipeline.push({ $limit: options.limit })
|
|
|
|
}
|
2021-09-03 14:34:08 +00:00
|
|
|
const domain = this.hierarchy.getDomain(clazz)
|
2022-03-04 09:01:46 +00:00
|
|
|
let cursor = this.db.collection(domain).aggregate(pipeline)
|
|
|
|
if (options?.projection !== undefined) {
|
|
|
|
cursor = cursor.project(options.projection)
|
|
|
|
}
|
2021-12-02 09:07:25 +00:00
|
|
|
const result = (await cursor.toArray()) as FindResult<T>
|
2021-09-03 14:34:08 +00:00
|
|
|
for (const row of result) {
|
2022-01-31 09:06:30 +00:00
|
|
|
row.$lookup = {}
|
|
|
|
await this.fillLookupValue(options.lookup, row)
|
2022-02-25 09:05:55 +00:00
|
|
|
this.clearExtraLookups(row)
|
2021-09-03 14:34:08 +00:00
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-02-25 09:05:55 +00:00
|
|
|
private clearExtraLookups (row: any): void {
|
|
|
|
for (const key in row) {
|
|
|
|
if (key.endsWith('_lookup')) {
|
|
|
|
// eslint-disable-next-line
|
|
|
|
delete row[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-02 09:04:13 +00:00
|
|
|
private checkMixinKey<T extends Doc>(key: string, clazz: Ref<Class<T>>): string {
|
|
|
|
if (!key.includes('.')) {
|
|
|
|
try {
|
|
|
|
const attr = this.hierarchy.getAttribute(clazz, key)
|
|
|
|
if (this.hierarchy.isMixin(attr.attributeOf)) {
|
|
|
|
// It is mixin
|
|
|
|
key = attr.attributeOf + '.' + key
|
|
|
|
}
|
|
|
|
} catch (err: any) {
|
|
|
|
// ignore, if
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return key
|
|
|
|
}
|
|
|
|
|
2021-12-02 09:07:25 +00:00
|
|
|
async findAll<T extends Doc>(
|
2021-08-18 19:46:59 +00:00
|
|
|
_class: Ref<Class<T>>,
|
|
|
|
query: DocumentQuery<T>,
|
|
|
|
options?: FindOptions<T>
|
|
|
|
): Promise<FindResult<T>> {
|
2021-09-03 14:34:08 +00:00
|
|
|
// TODO: rework this
|
|
|
|
if (options !== null && options !== undefined) {
|
2021-12-02 09:07:25 +00:00
|
|
|
if (options.lookup !== undefined) {
|
|
|
|
return await this.lookup(_class, query, options)
|
|
|
|
}
|
2021-09-03 14:34:08 +00:00
|
|
|
}
|
2021-08-18 19:46:59 +00:00
|
|
|
const domain = this.hierarchy.getDomain(_class)
|
2021-09-08 08:34:48 +00:00
|
|
|
let cursor = this.db.collection(domain).find<T>(this.translateQuery(_class, query))
|
2021-12-08 09:14:55 +00:00
|
|
|
|
2022-03-04 09:01:46 +00:00
|
|
|
if (options?.projection !== undefined) {
|
|
|
|
cursor = cursor.project(options.projection)
|
|
|
|
}
|
|
|
|
|
2021-08-20 16:20:22 +00:00
|
|
|
if (options !== null && options !== undefined) {
|
|
|
|
if (options.sort !== undefined) {
|
|
|
|
const sort: Sort = {}
|
|
|
|
for (const key in options.sort) {
|
2022-02-02 09:04:13 +00:00
|
|
|
const ckey = this.checkMixinKey<T>(key, _class)
|
2021-08-20 16:20:22 +00:00
|
|
|
const order = options.sort[key] === SortingOrder.Ascending ? 1 : -1
|
2022-02-02 09:04:13 +00:00
|
|
|
sort[ckey] = order
|
2021-08-20 16:20:22 +00:00
|
|
|
}
|
|
|
|
cursor = cursor.sort(sort)
|
|
|
|
}
|
2021-12-08 09:14:55 +00:00
|
|
|
if (options.limit !== undefined) {
|
|
|
|
cursor = cursor.limit(options.limit)
|
|
|
|
}
|
2021-08-20 16:20:22 +00:00
|
|
|
}
|
|
|
|
return await cursor.toArray()
|
2021-08-18 19:46:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
class MongoAdapter extends MongoAdapterBase {
|
2021-10-13 16:46:48 +00:00
|
|
|
protected override async txPutBag (tx: TxPutBag<any>): Promise<TxResult> {
|
2021-09-15 17:03:34 +00:00
|
|
|
const domain = this.hierarchy.getDomain(tx.objectClass)
|
|
|
|
await this.db.collection(domain).updateOne({ _id: tx.objectId }, { $set: { [tx.bag + '.' + tx.key]: tx.value } })
|
2021-10-13 16:46:48 +00:00
|
|
|
return {}
|
2021-09-15 10:53:11 +00:00
|
|
|
}
|
|
|
|
|
2021-10-26 17:51:49 +00:00
|
|
|
protected override async txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
|
|
|
const domain = this.hierarchy.getDomain(tx.objectClass)
|
|
|
|
await this.db.collection(domain).deleteOne({ _id: tx.objectId })
|
|
|
|
return {}
|
2021-09-15 10:53:11 +00:00
|
|
|
}
|
|
|
|
|
2021-12-30 09:04:32 +00:00
|
|
|
protected async txMixin (tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
|
|
|
const domain = this.hierarchy.getDomain(tx.objectClass)
|
|
|
|
|
|
|
|
if (isOperator(tx.attributes)) {
|
|
|
|
const operator = Object.keys(tx.attributes)[0]
|
|
|
|
if (operator === '$move') {
|
|
|
|
const keyval = (tx.attributes as any).$move
|
|
|
|
const arr = tx.mixin + '.' + Object.keys(keyval)[0]
|
|
|
|
const desc = keyval[arr]
|
|
|
|
const ops = [
|
|
|
|
{
|
|
|
|
updateOne: {
|
|
|
|
filter: { _id: tx.objectId },
|
|
|
|
update: {
|
|
|
|
$pull: {
|
|
|
|
[arr]: desc.$value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
updateOne: {
|
|
|
|
filter: { _id: tx.objectId },
|
|
|
|
update: {
|
|
|
|
$set: {
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
},
|
|
|
|
$push: {
|
|
|
|
[arr]: {
|
|
|
|
$each: [desc.$value],
|
|
|
|
$position: desc.$position
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
return await this.db.collection(domain).bulkWrite(ops as any)
|
|
|
|
} else {
|
|
|
|
return await this.db.collection(domain).updateOne(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{
|
|
|
|
...this.translateMixinAttrs(tx.mixin, tx.attributes),
|
|
|
|
$set: {
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return await this.db
|
|
|
|
.collection(domain)
|
|
|
|
.updateOne(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{
|
|
|
|
$set: {
|
|
|
|
...this.translateMixinAttrs(tx.mixin, tx.attributes),
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private translateMixinAttrs (mixin: Ref<Mixin<Doc>>, attributes: Record<string, any>): Record<string, any> {
|
|
|
|
const attrs: Record<string, any> = {}
|
|
|
|
let count = 0
|
|
|
|
for (const [k, v] of Object.entries(attributes)) {
|
|
|
|
if (k.startsWith('$')) {
|
|
|
|
attrs[k] = this.translateMixinAttrs(mixin, v)
|
|
|
|
} else {
|
|
|
|
attrs[mixin + '.' + k] = v
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
|
|
|
|
if (count === 0) {
|
|
|
|
// We need at least one attribute, to be inside for first time,
|
|
|
|
// for mongo to create embedded object, if we don't want to get object first.
|
|
|
|
attrs[mixin + '.' + '__mixin'] = 'true'
|
|
|
|
}
|
|
|
|
return attrs
|
2021-09-15 10:53:11 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
2021-08-26 12:16:45 +00:00
|
|
|
const doc = TxProcessor.createDoc2Doc(tx)
|
|
|
|
const domain = this.hierarchy.getDomain(doc._class)
|
|
|
|
await this.db.collection(domain).insertOne(translateDoc(doc))
|
2021-10-13 16:46:48 +00:00
|
|
|
return {}
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
2021-09-06 17:56:50 +00:00
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected override async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
2021-09-06 17:56:50 +00:00
|
|
|
const domain = this.hierarchy.getDomain(tx.objectClass)
|
2021-10-09 08:54:08 +00:00
|
|
|
if (isOperator(tx.operations)) {
|
|
|
|
const operator = Object.keys(tx.operations)[0]
|
|
|
|
if (operator === '$move') {
|
|
|
|
const keyval = (tx.operations as any).$move
|
|
|
|
const arr = Object.keys(keyval)[0]
|
|
|
|
const desc = keyval[arr]
|
|
|
|
const ops = [
|
|
|
|
{
|
|
|
|
updateOne: {
|
|
|
|
filter: { _id: tx.objectId },
|
|
|
|
update: {
|
|
|
|
$pull: {
|
|
|
|
[arr]: desc.$value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
updateOne: {
|
|
|
|
filter: { _id: tx.objectId },
|
|
|
|
update: {
|
2021-11-26 11:06:21 +00:00
|
|
|
$set: {
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
},
|
2021-10-09 08:54:08 +00:00
|
|
|
$push: {
|
|
|
|
[arr]: {
|
|
|
|
$each: [desc.$value],
|
|
|
|
$position: desc.$position
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
2021-10-13 18:58:14 +00:00
|
|
|
return await this.db.collection(domain).bulkWrite(ops as any)
|
2021-10-09 08:54:08 +00:00
|
|
|
} else {
|
2021-10-13 19:54:11 +00:00
|
|
|
if (tx.retrieve === true) {
|
2021-12-02 09:07:25 +00:00
|
|
|
const result = await this.db.collection(domain).findOneAndUpdate(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{
|
|
|
|
...tx.operations,
|
|
|
|
$set: {
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ returnDocument: 'after' }
|
|
|
|
)
|
2021-10-13 19:54:11 +00:00
|
|
|
return { object: result.value }
|
|
|
|
} else {
|
2021-12-02 09:07:25 +00:00
|
|
|
return await this.db.collection(domain).updateOne(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{
|
|
|
|
...tx.operations,
|
|
|
|
$set: {
|
|
|
|
modifiedBy: tx.modifiedBy,
|
|
|
|
modifiedOn: tx.modifiedOn
|
|
|
|
}
|
2021-11-29 11:07:49 +00:00
|
|
|
}
|
2021-12-02 09:07:25 +00:00
|
|
|
)
|
2021-10-13 19:54:11 +00:00
|
|
|
}
|
2021-10-09 08:54:08 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-13 19:54:11 +00:00
|
|
|
if (tx.retrieve === true) {
|
2021-12-02 09:07:25 +00:00
|
|
|
const result = await this.db
|
|
|
|
.collection(domain)
|
|
|
|
.findOneAndUpdate(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{ $set: { ...tx.operations, modifiedBy: tx.modifiedBy, modifiedOn: tx.modifiedOn } },
|
|
|
|
{ returnDocument: 'after' }
|
|
|
|
)
|
2021-10-13 19:54:11 +00:00
|
|
|
return { object: result.value }
|
|
|
|
} else {
|
2021-12-02 09:07:25 +00:00
|
|
|
return await this.db
|
|
|
|
.collection(domain)
|
|
|
|
.updateOne(
|
|
|
|
{ _id: tx.objectId },
|
|
|
|
{ $set: { ...tx.operations, modifiedBy: tx.modifiedBy, modifiedOn: tx.modifiedOn } }
|
|
|
|
)
|
2021-10-13 19:54:11 +00:00
|
|
|
}
|
2021-10-09 08:54:08 +00:00
|
|
|
}
|
2021-09-06 17:56:50 +00:00
|
|
|
}
|
2021-09-28 14:24:28 +00:00
|
|
|
|
2021-12-22 09:02:51 +00:00
|
|
|
override async tx (tx: Tx): Promise<TxResult> {
|
|
|
|
return await super.tx(tx)
|
2021-09-28 14:24:28 +00:00
|
|
|
}
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
2021-12-22 09:02:51 +00:00
|
|
|
txColl: Collection | undefined
|
2021-10-13 16:46:48 +00:00
|
|
|
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
2021-09-15 10:53:11 +00:00
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected txPutBag (tx: TxPutBag<any>): Promise<TxResult> {
|
2021-09-15 10:53:11 +00:00
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
2021-09-15 10:53:11 +00:00
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
2021-09-15 10:53:11 +00:00
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
protected txMixin (tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
2021-09-15 10:53:11 +00:00
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-10-13 16:46:48 +00:00
|
|
|
override async tx (tx: Tx): Promise<TxResult> {
|
2021-12-22 09:02:51 +00:00
|
|
|
await this.txCollection().insertOne(translateDoc(tx))
|
2021-10-13 16:46:48 +00:00
|
|
|
return {}
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 09:02:51 +00:00
|
|
|
private txCollection (): Collection {
|
|
|
|
if (this.txColl !== undefined) {
|
|
|
|
return this.txColl
|
|
|
|
}
|
|
|
|
this.txColl = this.db.collection(DOMAIN_TX)
|
|
|
|
return this.txColl
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
async getModel (): Promise<Tx[]> {
|
2022-02-03 09:02:54 +00:00
|
|
|
const model = await this.db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
|
|
|
|
// We need to put all core.account.System transactions first
|
|
|
|
const systemTr: Tx[] = []
|
|
|
|
const userTx: Tx[] = []
|
|
|
|
|
|
|
|
model.forEach(tx => ((tx.modifiedBy === core.account.System) ? systemTr : userTx).push(tx))
|
|
|
|
|
|
|
|
return systemTr.concat(userTx)
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-02 09:07:25 +00:00
|
|
|
export async function createMongoAdapter (
|
|
|
|
hierarchy: Hierarchy,
|
|
|
|
url: string,
|
|
|
|
dbName: string,
|
|
|
|
modelDb: ModelDb
|
|
|
|
): Promise<DbAdapter> {
|
|
|
|
const client = await getMongoClient(url)
|
2021-08-26 12:16:45 +00:00
|
|
|
const db = client.db(dbName)
|
2022-01-12 09:06:04 +00:00
|
|
|
return new MongoAdapter(db, hierarchy, modelDb, client)
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
|
|
|
|
2021-08-18 19:46:59 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-02 09:07:25 +00:00
|
|
|
export async function createMongoTxAdapter (
|
|
|
|
hierarchy: Hierarchy,
|
|
|
|
url: string,
|
|
|
|
dbName: string,
|
|
|
|
modelDb: ModelDb
|
|
|
|
): Promise<TxAdapter> {
|
|
|
|
const client = await getMongoClient(url)
|
2021-08-18 19:46:59 +00:00
|
|
|
const db = client.db(dbName)
|
2022-01-12 09:06:04 +00:00
|
|
|
return new MongoTxAdapter(db, hierarchy, modelDb, client)
|
2021-08-18 19:46:59 +00:00
|
|
|
}
|