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-09-28 20:47:17 +00:00
|
|
|
import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc, TxUpdateDoc, TxMixin, TxPutBag, TxRemoveDoc } from '@anticrm/core'
|
|
|
|
import core, { DOMAIN_TX, SortingOrder, TxProcessor, Hierarchy, isOperator } from '@anticrm/core'
|
2021-09-15 10:53:11 +00:00
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
2021-08-18 19:46:59 +00:00
|
|
|
|
2021-08-20 16:20:22 +00:00
|
|
|
import { MongoClient, Db, Filter, Document, Sort } from 'mongodb'
|
2021-08-18 19:46:59 +00:00
|
|
|
|
|
|
|
function translateDoc (doc: Doc): Document {
|
|
|
|
return doc as Document
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
abstract class MongoAdapterBase extends TxProcessor {
|
2021-08-18 19:46:59 +00:00
|
|
|
constructor (
|
2021-08-26 12:16:45 +00:00
|
|
|
protected readonly db: Db,
|
|
|
|
protected readonly hierarchy: Hierarchy
|
2021-08-18 19:46:59 +00:00
|
|
|
) {
|
|
|
|
super()
|
|
|
|
}
|
|
|
|
|
2021-08-25 17:55:26 +00:00
|
|
|
async init (): Promise<void> {}
|
|
|
|
|
2021-09-08 08:34:48 +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]
|
|
|
|
if (typeof value === 'object') {
|
|
|
|
const keys = Object.keys(value)
|
|
|
|
if (keys[0] === '$like') {
|
|
|
|
const pattern = value.$like as string
|
|
|
|
translated[key] = {
|
|
|
|
$regex: `^${pattern.split('%').join('.*')}$`,
|
|
|
|
$options: 'i'
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
translated[key] = value
|
|
|
|
}
|
2021-09-08 08:34:48 +00:00
|
|
|
const classes = this.hierarchy.getDescendants(clazz)
|
2021-10-01 13:33:49 +00:00
|
|
|
translated._class = { $in: classes }
|
|
|
|
// return Object.assign({}, query, { _class: { $in: classes } })
|
|
|
|
return translated
|
2021-09-08 08:34:48 +00:00
|
|
|
}
|
|
|
|
|
2021-09-03 14:34:08 +00:00
|
|
|
private async lookup<T extends Doc> (clazz: Ref<Class<T>>, query: DocumentQuery<T>, options: FindOptions<T>): Promise<FindResult<T>> {
|
|
|
|
const pipeline = []
|
2021-09-08 08:34:48 +00:00
|
|
|
pipeline.push({ $match: this.translateQuery(clazz, query) })
|
2021-09-03 14:34:08 +00:00
|
|
|
const lookups = options.lookup as any
|
|
|
|
for (const key in lookups) {
|
|
|
|
const clazz = lookups[key]
|
|
|
|
const step = {
|
|
|
|
from: this.hierarchy.getDomain(clazz),
|
|
|
|
localField: key,
|
|
|
|
foreignField: '_id',
|
|
|
|
as: key + '_lookup'
|
|
|
|
}
|
|
|
|
pipeline.push({ $lookup: step })
|
|
|
|
}
|
|
|
|
const domain = this.hierarchy.getDomain(clazz)
|
|
|
|
const cursor = this.db.collection(domain).aggregate(pipeline)
|
|
|
|
const result = await cursor.toArray() as FindResult<T>
|
|
|
|
for (const row of result) {
|
|
|
|
const object = row as any
|
|
|
|
object.$lookup = {}
|
|
|
|
for (const key in lookups) {
|
|
|
|
const arr = object[key + '_lookup']
|
|
|
|
object.$lookup[key] = arr[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-08-18 19:46:59 +00:00
|
|
|
async findAll<T extends Doc> (
|
|
|
|
_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) {
|
|
|
|
if (options.lookup !== undefined) { return await this.lookup(_class, query, options) }
|
|
|
|
}
|
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-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) {
|
|
|
|
const order = options.sort[key] === SortingOrder.Ascending ? 1 : -1
|
|
|
|
sort[key] = order
|
|
|
|
}
|
|
|
|
cursor = cursor.sort(sort)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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-09-15 17:03:34 +00:00
|
|
|
protected override async txPutBag (tx: TxPutBag<any>): Promise<void> {
|
|
|
|
const domain = this.hierarchy.getDomain(tx.objectClass)
|
|
|
|
console.log('mongo', { $set: { [tx.bag + '.' + tx.key]: tx.value } })
|
|
|
|
await this.db.collection(domain).updateOne({ _id: tx.objectId }, { $set: { [tx.bag + '.' + tx.key]: tx.value } })
|
2021-09-15 10:53:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
|
|
|
protected txMixin (tx: TxMixin<Doc, Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
protected override async txCreateDoc (tx: TxCreateDoc<Doc>): Promise<void> {
|
|
|
|
const doc = TxProcessor.createDoc2Doc(tx)
|
|
|
|
const domain = this.hierarchy.getDomain(doc._class)
|
|
|
|
await this.db.collection(domain).insertOne(translateDoc(doc))
|
|
|
|
}
|
2021-09-06 17:56:50 +00:00
|
|
|
|
|
|
|
protected override async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<void> {
|
|
|
|
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: {
|
|
|
|
$push: {
|
|
|
|
[arr]: {
|
|
|
|
$each: [desc.$value],
|
|
|
|
$position: desc.$position
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
console.log('ops', ops)
|
|
|
|
await this.db.collection(domain).bulkWrite(ops as any)
|
|
|
|
} else {
|
|
|
|
await this.db.collection(domain).updateOne({ _id: tx.objectId }, tx.operations)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
await this.db.collection(domain).updateOne({ _id: tx.objectId }, { $set: tx.operations })
|
|
|
|
}
|
2021-09-06 17:56:50 +00:00
|
|
|
}
|
2021-09-28 14:24:28 +00:00
|
|
|
|
|
|
|
override tx (tx: Tx): Promise<void> {
|
|
|
|
console.log('mongo', tx)
|
|
|
|
return super.tx(tx)
|
|
|
|
}
|
2021-08-26 12:16:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
2021-09-15 10:53:11 +00:00
|
|
|
protected txCreateDoc (tx: TxCreateDoc<Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
|
|
|
protected txPutBag (tx: TxPutBag<any>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
|
|
|
protected txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
|
|
|
protected txRemoveDoc (tx: TxRemoveDoc<Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
|
|
|
protected txMixin (tx: TxMixin<Doc, Doc>): Promise<void> {
|
|
|
|
throw new Error('Method not implemented.')
|
|
|
|
}
|
|
|
|
|
2021-08-26 12:16:45 +00:00
|
|
|
override async tx (tx: Tx): Promise<void> {
|
|
|
|
console.log('mongotx', tx)
|
|
|
|
await this.db.collection(DOMAIN_TX).insertOne(translateDoc(tx))
|
|
|
|
}
|
|
|
|
|
|
|
|
async getModel (): Promise<Tx[]> {
|
|
|
|
return await this.db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export async function createMongoAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<DbAdapter> {
|
|
|
|
const client = new MongoClient(url)
|
|
|
|
await client.connect()
|
|
|
|
const db = client.db(dbName)
|
|
|
|
return new MongoAdapter(db, hierarchy)
|
|
|
|
}
|
|
|
|
|
2021-08-18 19:46:59 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-08-26 12:16:45 +00:00
|
|
|
export async function createMongoTxAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<TxAdapter> {
|
2021-08-18 19:46:59 +00:00
|
|
|
const client = new MongoClient(url)
|
|
|
|
await client.connect()
|
|
|
|
const db = client.db(dbName)
|
2021-08-26 12:16:45 +00:00
|
|
|
return new MongoTxAdapter(db, hierarchy)
|
2021-08-18 19:46:59 +00:00
|
|
|
}
|