mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
removed old odm sources
This commit is contained in:
parent
976d6fd10a
commit
bdfc2eb05f
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Cascade options used to set cascade operations over document relations.
|
||||
*/
|
||||
export interface CascadeOption {
|
||||
|
||||
insert?: boolean;
|
||||
update?: boolean;
|
||||
remove?: boolean;
|
||||
field: string|any;
|
||||
cascades?: CascadeOption[]|((object: any) => CascadeOption[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cascade options can be set using multiple forms:
|
||||
*
|
||||
* Form1: nested fields
|
||||
*
|
||||
* let cascades = {
|
||||
* answers: {
|
||||
* insert: true,
|
||||
* update: true,
|
||||
* remove: true,
|
||||
* cascades: {
|
||||
* name: {
|
||||
* insert: true,
|
||||
* update: true,
|
||||
* remove: true
|
||||
* }
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* // ...
|
||||
* };
|
||||
*
|
||||
* Form2: flat fields
|
||||
*
|
||||
* let cascades = {
|
||||
* 'answers': { insert: true, update: true, remove: true },
|
||||
* 'answers.name': { insert: true, update: true, remove: true },
|
||||
* // ...
|
||||
* };
|
||||
*
|
||||
* Form3: field in CascadeOption object
|
||||
*
|
||||
* let cascades2 = {
|
||||
* 'answers': { insert: true, update: true, remove: true },
|
||||
* 'answers.name': { insert: true, update: true, remove: true },
|
||||
* // ...
|
||||
* };
|
||||
*
|
||||
* Form4: typesafe using typed objects in function arguments
|
||||
*
|
||||
* let cascades3 = (vote: Vote) => [{
|
||||
* field: vote.answers,
|
||||
* insert: true,
|
||||
* update: true,
|
||||
* remove: true,
|
||||
* cascades: (voteAnswer: VoteAnswer) => [{
|
||||
* field: voteAnswer.results,
|
||||
* insert: true,
|
||||
* update: true,
|
||||
* remove: true
|
||||
* }]
|
||||
* }];
|
||||
*
|
||||
*/
|
||||
export type DynamicCascadeOptions<Entity> = CascadeOption[]|((entity: Entity) => CascadeOption[])|Object;
|
||||
@ -1,86 +0,0 @@
|
||||
import {CascadeOption, DynamicCascadeOptions} from "./CascadeOption";
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
import {RelationSchema} from "../../schema/RelationSchema";
|
||||
|
||||
export class CascadeOptionUtils {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Static Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static prepareCascadeOptions(schema: DocumentSchema, cascadeOptions: DynamicCascadeOptions<any>): CascadeOption[] {
|
||||
if (cascadeOptions instanceof Function) {
|
||||
return (<((document: Document) => CascadeOption[])> cascadeOptions)(schema.createPropertiesMirror());
|
||||
|
||||
} else if (cascadeOptions instanceof Object && !(cascadeOptions instanceof Array)) {
|
||||
return CascadeOptionUtils.convertFromObjectMap(cascadeOptions);
|
||||
}
|
||||
|
||||
return <CascadeOption[]> cascadeOptions;
|
||||
}
|
||||
|
||||
static find(cascadeOptions: CascadeOption[], fieldName: string) {
|
||||
return cascadeOptions ? cascadeOptions.reduce((found, cascade) => cascade.field === fieldName ? cascade : found, null) : null;
|
||||
}
|
||||
|
||||
static isCascadeRemove(relation: RelationSchema, cascadeOption?: CascadeOption): boolean {
|
||||
if (relation.isCascadeRemove && !cascadeOption)
|
||||
return true;
|
||||
if (relation.isCascadeRemove && cascadeOption && cascadeOption.remove !== false)
|
||||
return true;
|
||||
if (cascadeOption && cascadeOption.remove)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static isCascadeInsert(relation: RelationSchema, cascadeOption?: CascadeOption): boolean {
|
||||
if (relation.isCascadeInsert && !cascadeOption)
|
||||
return true;
|
||||
if (relation.isCascadeInsert && cascadeOption && cascadeOption.insert !== false)
|
||||
return true;
|
||||
if (cascadeOption && cascadeOption.insert)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static isCascadeUpdate(relation: RelationSchema, cascadeOption?: CascadeOption): boolean {
|
||||
if (relation.isCascadeUpdate && !cascadeOption)
|
||||
return true;
|
||||
if (relation.isCascadeUpdate && cascadeOption && cascadeOption.update !== false)
|
||||
return true;
|
||||
if (cascadeOption && cascadeOption.update)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static isCascadePersist(relation: RelationSchema, cascadeOption?: CascadeOption): boolean {
|
||||
return this.isCascadeInsert(relation, cascadeOption) || this.isCascadeUpdate(relation, cascadeOption);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Static Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static convertFromObjectMap(object: any): CascadeOption[] {
|
||||
if (!object)
|
||||
return [];
|
||||
|
||||
return Object.keys(object).map(key => {
|
||||
let subCascadeKeys = Object.keys(object).filter(k => k.substr(0, key.length + 1) === key + ".");
|
||||
let subCascades = subCascadeKeys.reduce((v: any, k: string) => { v[k.substr(key.length + 1)] = object[k]; return v; }, {});
|
||||
if (key.indexOf(".") !== -1) return null;
|
||||
|
||||
return <CascadeOption> {
|
||||
field: key,
|
||||
insert: !!object[key].insert,
|
||||
update: !!object[key].update,
|
||||
remove: !!object[key].remove,
|
||||
cascades: this.convertFromObjectMap(object[key].cascades ? object[key].cascades : subCascades)
|
||||
};
|
||||
}).filter(option => option !== null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,232 +0,0 @@
|
||||
import {Connection} from "../../connection/Connection";
|
||||
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
|
||||
import {RelationMetadata} from "../../metadata-builder/metadata/RelationMetadata";
|
||||
import {AliasMap, Alias} from "../../query-builder/QueryBuilder";
|
||||
import * as _ from "lodash";
|
||||
|
||||
export class EntityCreator {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
createFromJson<Entity>(object: any, metadata: EntityMetadata, fetchProperty?: boolean): Promise<Entity>;
|
||||
createFromJson<Entity>(object: any, metadata: EntityMetadata, fetchProperty?: Object): Promise<Entity>;
|
||||
createFromJson<Entity>(object: any, metadata: EntityMetadata, fetchOption?: boolean|Object): Promise<Entity> {
|
||||
|
||||
return Promise.resolve(this.objectToEntity(object, metadata, fetchOption));
|
||||
//return this.objectToEntity(object, metadata, fetchOption);
|
||||
}
|
||||
|
||||
objectToEntity<Entity>(objects: any[], metadata: EntityMetadata, aliasMap: AliasMap, fetchProperty?: boolean): Entity;
|
||||
objectToEntity<Entity>(objects: any[], metadata: EntityMetadata, aliasMap: AliasMap,fetchProperty?: Object): Entity;
|
||||
objectToEntity<Entity>(objects: any[], metadata: EntityMetadata, aliasMap: AliasMap, fetchOption?: boolean|Object): Entity {
|
||||
|
||||
return this.toEntity(objects, metadata, aliasMap.mainAlias(), aliasMap);
|
||||
//return this.objectToEntity(object, metadata, fetchOption);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*getLoadMap(metadata: EntityMetadata) {
|
||||
|
||||
const postId = 1;
|
||||
const postJson = {
|
||||
id: 1,
|
||||
text: "This is post about hello",
|
||||
title: "hello",
|
||||
details: {
|
||||
id: 1,
|
||||
comment: "This is post about hello",
|
||||
meta: "about-hello"
|
||||
}
|
||||
};
|
||||
|
||||
// let tableUsageIndices = 0;
|
||||
|
||||
const mainTableAlias = metadata.table.name + "_1";
|
||||
const visitedMetadatas: EntityMetadata[] = [];
|
||||
const qb = this.connection.driver
|
||||
.createQueryBuilder(this.connection.metadatas)
|
||||
.select(mainTableAlias)
|
||||
.from(metadata.target, mainTableAlias);
|
||||
|
||||
const aliasesCounter: { [type: string]: number } = { [mainTableAlias]: 0 };
|
||||
const joinRelations = (parentTableAlias: string, entityMetadata: EntityMetadata) => {
|
||||
if (visitedMetadatas.find(metadata => metadata === entityMetadata))
|
||||
return;
|
||||
|
||||
visitedMetadatas.push(metadata);
|
||||
entityMetadata.relations.filter(relation => relation.isAlwaysLeftJoin || relation.isAlwaysInnerJoin).forEach(relation => {
|
||||
let relationAlias = relation.relatedEntityMetadata.table.name;
|
||||
if (!aliasesCounter[relationAlias]) aliasesCounter[relationAlias] = 0;
|
||||
aliasesCounter[relationAlias] += 1;
|
||||
relationAlias += "_" + aliasesCounter[relationAlias];
|
||||
let condition = "";
|
||||
const relationColumn = relation.relatedEntityMetadata.primaryColumn.name;
|
||||
|
||||
if (relation.isOwning) {
|
||||
condition = relationAlias + "." + relationColumn + "=" + parentTableAlias + "." + relation.inverseSideProperty;
|
||||
} else {
|
||||
condition = relationAlias + "." + relation.inverseSideProperty + "=" + parentTableAlias + "." + relationColumn;
|
||||
}
|
||||
|
||||
if (relation.isAlwaysLeftJoin) {
|
||||
qb.addSelect(relationAlias).leftJoin(relation.type, relationAlias, "ON", condition);
|
||||
|
||||
} else { // else can be only always inner join
|
||||
qb.addSelect(relationAlias).innerJoin(relation.type, relationAlias, "ON", condition);
|
||||
}
|
||||
|
||||
// now recursively go throw its relations
|
||||
joinRelations(relationAlias, relation.relatedEntityMetadata);
|
||||
});
|
||||
};
|
||||
|
||||
joinRelations(mainTableAlias, metadata);
|
||||
|
||||
|
||||
Object.keys(postJson).forEach(key => {
|
||||
|
||||
});
|
||||
|
||||
qb.where(mainTableAlias + "." + metadata.primaryColumn.name + "='" + postId + "'");
|
||||
|
||||
console.log(qb.getSql());
|
||||
}
|
||||
|
||||
private convertTableResultToJsonTree() {
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
private toEntity(sqlResult: any[], metadata: EntityMetadata, mainAlias: Alias, aliasMap: AliasMap): any[] {
|
||||
|
||||
const objects = _.groupBy(sqlResult, result => {
|
||||
return result[mainAlias.name + "_" + metadata.primaryColumn.name];
|
||||
});
|
||||
|
||||
return Object.keys(objects).map(key => {
|
||||
//if (id && id != key) return null;
|
||||
|
||||
let isAnythingLoaded = false;
|
||||
const object = objects[key][0];
|
||||
//const entity = metadata.create();
|
||||
const jsonObject: any = {};
|
||||
|
||||
metadata.columns.forEach(column => {
|
||||
const valueInObject = object[mainAlias.name + "_" + column.name];
|
||||
if (valueInObject && column.propertyName) { // todo: add check for property relation with id as a column
|
||||
jsonObject[column.propertyName] = valueInObject;
|
||||
isAnythingLoaded = true;
|
||||
}
|
||||
});
|
||||
|
||||
metadata.relations.forEach(relation => {
|
||||
const alias = aliasMap.findAliasByParent(mainAlias.name, relation.propertyName);
|
||||
if (alias) {
|
||||
//const id = relation.isManyToOne || relation.isOneToOne ? object[mainAlias.name + "_" + relation.name] : null;
|
||||
const subSqlResult = sqlResult.filter(result => String(result[mainAlias.name + "_" + metadata.primaryColumn.name]) === key);
|
||||
const relatedEntities = this.toEntity(subSqlResult, relation.relatedEntityMetadata, alias, aliasMap);
|
||||
const res = relation.isManyToOne || relation.isOneToOne ? relatedEntities[0] : relatedEntities;
|
||||
if (res) {
|
||||
jsonObject[relation.propertyName] = res;
|
||||
isAnythingLoaded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return isAnythingLoaded ? jsonObject : null;
|
||||
|
||||
}).filter(res => res !== null);
|
||||
}
|
||||
|
||||
private objectToEntity2(object: any, metadata: EntityMetadata, doFetchProperties?: boolean): Promise<any>;
|
||||
private objectToEntity2(object: any, metadata: EntityMetadata, fetchConditions?: Object): Promise<any>;
|
||||
private objectToEntity2(object: any, metadata: EntityMetadata, fetchOption?: boolean|Object): Promise<any> {
|
||||
if (!object)
|
||||
throw new Error("Given object is empty, cannot initialize empty object.");
|
||||
|
||||
//this.getLoadMap(metadata);
|
||||
|
||||
const doFetch = !!fetchOption;
|
||||
const entityPromise = this.loadDependOnFetchOption(object, metadata, fetchOption);
|
||||
|
||||
// todo: this needs strong optimization. since repository.findById makes here multiple operations and each time loads lot of data by cascades
|
||||
|
||||
return entityPromise.then((entity: any) => {
|
||||
|
||||
const promises: Promise<any>[] = [];
|
||||
if (!entity) entity = metadata.create();
|
||||
|
||||
// copy values from the object to the entity
|
||||
Object.keys(object)
|
||||
.filter(key => metadata.hasColumnWithDbName(key))
|
||||
.forEach(key => entity[key] = object[key]);
|
||||
|
||||
// second copy relations and pre-load them
|
||||
Object.keys(object)
|
||||
.filter(key => metadata.hasRelationWithPropertyName(key))
|
||||
.forEach(key => {
|
||||
const relation = metadata.findRelationWithPropertyName(key);
|
||||
const relationEntityMetadata = this.connection.getMetadata(relation.target);
|
||||
|
||||
if (object[key] instanceof Array) {
|
||||
const subPromises = object[key].map((i: any) => {
|
||||
return this.objectToEntity(i, relationEntityMetadata, doFetch);
|
||||
});
|
||||
promises.push(Promise.all(subPromises).then(subEntities => entity[key] = subEntities));
|
||||
|
||||
} else if (object[key] instanceof Object) {
|
||||
const subPromise = this.objectToEntity(object[key], relationEntityMetadata, doFetch);
|
||||
promises.push(subPromise.then(subEntity => entity[key] = subEntity));
|
||||
}
|
||||
});
|
||||
|
||||
// todo: here actually we need to find and save to entity object three things:
|
||||
// * related entity where id is stored in the current entity
|
||||
// * related entity where id is stored in related entity
|
||||
// * related entities from many-to-many table
|
||||
|
||||
// now find relations by entity ids stored in entities
|
||||
Object.keys(entity)
|
||||
.filter(key => metadata.hasRelationWithDbName(key))
|
||||
.forEach(key => {
|
||||
const relation = metadata.findRelationWithDbName(key);
|
||||
const relationEntityMetadata = this.connection.getMetadata(relation.target);
|
||||
// todo.
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(_ => entity);
|
||||
});
|
||||
}
|
||||
|
||||
private loadDependOnFetchOption(object: any, metadata: EntityMetadata, fetchOption?: boolean|Object): Promise<any> {
|
||||
const repository = this.connection.getRepository(metadata.target);
|
||||
|
||||
if (!!fetchOption && fetchOption instanceof Object)
|
||||
return repository.findOne(fetchOption);
|
||||
|
||||
if (!!fetchOption && repository.hasId(object))
|
||||
return repository.findById(object[metadata.primaryColumn.name]);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export class BadDocumentInstanceError extends Error {
|
||||
name = "BadDocumentInstanceError";
|
||||
|
||||
constructor(document: any, expectedClass: Function) {
|
||||
super();
|
||||
document = typeof document === "object" ? JSON.stringify(document) : document;
|
||||
this.message = "Cannot persist document of this class because given document is not instance " +
|
||||
"of " + expectedClass + ", but given " + document;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
export class FieldTypeNotSupportedError extends Error {
|
||||
name = "FieldTypeNotSupportedError";
|
||||
|
||||
constructor(fieldType: string|Function, field: string, document: any) {
|
||||
super();
|
||||
this.message = fieldType + " is not supported set on the field " + field + " on the document " + document;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
export class NoDocumentWithSuchIdError extends Error {
|
||||
name = "NoDocumentWithSuchIdError";
|
||||
|
||||
constructor(id: any, collection: string) {
|
||||
super();
|
||||
this.message = "Cannot find a " + collection + " document with given document id (" + id + "), has it been already removed?";
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
export class WrongFieldTypeInDocumentError extends Error {
|
||||
name = "WrongFieldTypeInDocumentError";
|
||||
|
||||
constructor(expectedType: string|Function, fieldName: string, document: any) {
|
||||
super();
|
||||
this.message = fieldName + " expected to be a type " + expectedType + ", but " + document[fieldName] +
|
||||
" value is given for document " + JSON.stringify(document);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,176 +0,0 @@
|
||||
import {Driver} from "../../driver/Driver";
|
||||
import {Connection} from "../../connection/Connection";
|
||||
import {Repository} from "./../Repository";
|
||||
import {JoinFieldOption} from "./JoinFieldOption";
|
||||
import {RelationSchema} from "../../schema/RelationSchema";
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
|
||||
/**
|
||||
* Loads the document
|
||||
*/
|
||||
export class DocumentHydrator<Document> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
hydrate(schema: DocumentSchema,
|
||||
dbObject: Object|any,
|
||||
joinFields?: JoinFieldOption[]|any[]): Promise<Document> {
|
||||
|
||||
let allPromises: Promise<any>[] = [];
|
||||
let document: Document|any = schema.create();
|
||||
let isDocumentSkipped = false;
|
||||
|
||||
if (schema.idField) // remember that id field cannot be in embed documents
|
||||
document[schema.idField.name] = schema.getIdValue(dbObject[this.connection.driver.getIdFieldName()]);
|
||||
schema.fields.filter(field => dbObject[field.name] !== undefined).forEach(field => {
|
||||
|
||||
if (dbObject[field.name] instanceof Array && field.isTypeDocument()) {
|
||||
let embedTypeSchema = this.connection.getSchema(<Function> field.type);
|
||||
let subCondition = this.getSubFieldCondition(joinFields, field.name);
|
||||
let promises = dbObject[field.name].map((i: any) => this.hydrate(embedTypeSchema, i, subCondition));
|
||||
allPromises.push(Promise.all(promises).then((subDocuments: any[]) => {
|
||||
document[field.propertyName] = subDocuments;
|
||||
}));
|
||||
} else if (dbObject[field.name] instanceof Object && field.isTypeDocument()) {
|
||||
let embedTypeSchema = this.connection.getSchema(<Function> field.type);
|
||||
let subCondition = this.getSubFieldCondition(joinFields, field.name);
|
||||
allPromises.push(this.hydrate(embedTypeSchema, dbObject[field.name], subCondition).then(subDocument => {
|
||||
document[field.propertyName] = subDocument;
|
||||
}));
|
||||
} else {
|
||||
document[field.propertyName] = dbObject[field.name];
|
||||
}
|
||||
});
|
||||
|
||||
schema.relationWithOnes.forEach(relation => {
|
||||
|
||||
let relationId = dbObject[relation.name];
|
||||
let canLoadRelation = this.canLoadRelation(relation, joinFields);
|
||||
let isLoadInnerTyped = this.isInnerJoin(joinFields, relation.name);
|
||||
|
||||
if (!canLoadRelation)
|
||||
return;
|
||||
if (!relationId && isLoadInnerTyped) {
|
||||
isDocumentSkipped = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let relatedRepo = this.connection.getRepository(<Function> relation.type);
|
||||
let subFields = this.getSubFields(joinFields, relation.name);
|
||||
let conditions: any = { [this.connection.driver.getIdFieldName()]: relationId };
|
||||
let subCondition = this.getSubFieldCondition(joinFields, relation.name);
|
||||
if (subCondition)
|
||||
Object.keys(subCondition).forEach(key => conditions[key] = subCondition[key]);
|
||||
|
||||
allPromises.push(relatedRepo.findOne(conditions, null, subFields).then(foundRelation => {
|
||||
if (!foundRelation && isLoadInnerTyped) {
|
||||
isDocumentSkipped = true;
|
||||
} else if (foundRelation) {
|
||||
document[relation.propertyName] = foundRelation;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
schema.relationWithManies.forEach(relation => {
|
||||
|
||||
let canLoadRelation = this.canLoadRelation(relation, joinFields);
|
||||
let isLoadInnerTyped = this.isInnerJoin(joinFields, relation.name);
|
||||
|
||||
if (!canLoadRelation)
|
||||
return;
|
||||
if ((!dbObject[relation.name] || !dbObject[relation.name].length) && isLoadInnerTyped) {
|
||||
isDocumentSkipped = true;
|
||||
return;
|
||||
}
|
||||
if (!dbObject[relation.name] || !dbObject[relation.name].length)
|
||||
return;
|
||||
|
||||
let relatedRepo = this.connection.getRepository(<Function> relation.type);
|
||||
let subFields = this.getSubFields(joinFields, relation.name);
|
||||
let findPromises = dbObject[relation.name].map((i: any) => {
|
||||
let conditions: any = { [this.connection.driver.getIdFieldName()]: i };
|
||||
let subCondition = this.getSubFieldCondition(joinFields, relation.name);
|
||||
if (subCondition)
|
||||
Object.keys(subCondition).forEach(key => conditions[key] = subCondition[key]);
|
||||
return relatedRepo.findOne(conditions, null, subFields);
|
||||
});
|
||||
|
||||
allPromises.push(Promise.all(findPromises).then(foundRelations => {
|
||||
foundRelations = foundRelations.filter(relation => !!relation);
|
||||
if ((!foundRelations || !foundRelations.length) && isLoadInnerTyped) {
|
||||
isDocumentSkipped = true;
|
||||
} else if (foundRelations) {
|
||||
document[relation.propertyName] = foundRelations;
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(allPromises).then(results => !isDocumentSkipped ? document : null);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private canLoadRelation(relation: RelationSchema, joinFields?: JoinFieldOption[]|any[]): boolean {
|
||||
return this.hasKey(joinFields, relation.propertyName);
|
||||
}
|
||||
|
||||
private hasKey(joinFields: JoinFieldOption[]|any[], key: string) {
|
||||
return (
|
||||
(<string[]> joinFields).indexOf(key) !== -1 ||
|
||||
joinFields.reduce((found, field) => (field instanceof Array && field[0] === key) ? true : found, false) ||
|
||||
joinFields.reduce((found: any, field: JoinFieldOption) => (field && field.field && field.field === key) ? true : found, false)
|
||||
);
|
||||
}
|
||||
|
||||
private isInnerJoin(joinFields: JoinFieldOption[]|any[], key: string) {
|
||||
return joinFields.reduce((sub, field) => {
|
||||
if (field instanceof Array && field[0] === key)
|
||||
return field[1];
|
||||
if (field instanceof Object && field.field && field.field === key)
|
||||
return field.inner;
|
||||
|
||||
return sub;
|
||||
}, null);
|
||||
}
|
||||
|
||||
private getSubFields(joinFields: JoinFieldOption[]|any[], key: string) {
|
||||
return joinFields.reduce((sub, field) => {
|
||||
if (field instanceof Array && field[0] === key)
|
||||
return field[1];
|
||||
if (field instanceof Object && field.field && field.field === key)
|
||||
return field.joins;
|
||||
|
||||
return sub;
|
||||
}, null);
|
||||
}
|
||||
|
||||
private getSubFieldCondition(joinFields: JoinFieldOption[]|any[], key: string): any {
|
||||
return joinFields.reduce((sub, field) => {
|
||||
if (field instanceof Array && field[0] === key)
|
||||
return field[2];
|
||||
if (field instanceof Object && field.field && field.field === key)
|
||||
return field.condition;
|
||||
|
||||
return sub;
|
||||
}, null);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
|
||||
export interface JoinFieldOption {
|
||||
|
||||
inner?: boolean;
|
||||
field: string|any;
|
||||
condition?: any;
|
||||
joins: JoinFieldOption|any[]; // todo: check its type - looks wrong
|
||||
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
export class JoinFieldOptionUtils {
|
||||
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
import {RelationSchema} from "../../schema/RelationSchema";
|
||||
|
||||
/**
|
||||
* Represents single inverse side update operation.
|
||||
*/
|
||||
export interface InverseSideUpdateOperation {
|
||||
documentSchema: DocumentSchema;
|
||||
getDocumentId: () => any;
|
||||
inverseSideDocumentId: any;
|
||||
inverseSideDocumentSchema: DocumentSchema;
|
||||
inverseSideDocumentRelation: RelationSchema;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
import {InverseSideUpdateOperation} from "./InverseSideUpdateOperation";
|
||||
|
||||
/**
|
||||
* Persist operation.
|
||||
*/
|
||||
export interface PersistOperation {
|
||||
allowedPersist: boolean;
|
||||
deepness: number;
|
||||
document: any;
|
||||
dbObject: Object;
|
||||
schema: DocumentSchema;
|
||||
afterExecution?: ((document: any) => InverseSideUpdateOperation)[];
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
import {PersistOperation} from "./PersistOperation";
|
||||
|
||||
/**
|
||||
* Represents single remove operation. Remove operation is used to keep in memory what document with what id
|
||||
* should be removed in some future.
|
||||
*/
|
||||
export interface PersistOperationGrouppedByDeepness {
|
||||
deepness: number;
|
||||
operations: PersistOperation[];
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
|
||||
/**
|
||||
* Represents single remove operation. Remove operation is used to keep in memory what document with what id
|
||||
* should be removed in some future.
|
||||
*/
|
||||
export interface RemoveOperation {
|
||||
schema: DocumentSchema;
|
||||
id: string;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Validates given type.
|
||||
*/
|
||||
export class DbObjectFieldValidator {
|
||||
private static supportedTypes = ["string", "number", "boolean", "date"];
|
||||
|
||||
static isTypeSupported(type: string) {
|
||||
return this.supportedTypes.indexOf(type) !== -1;
|
||||
}
|
||||
|
||||
static validateArray(array: any[], type: string): boolean {
|
||||
return array.filter(item => !this.validate(item, type)).length === 0;
|
||||
}
|
||||
|
||||
static validate(value: any, type: string): boolean {
|
||||
let foundTypeToCheckIndex = this.supportedTypes.indexOf(type);
|
||||
return typeof value === this.supportedTypes[foundTypeToCheckIndex] ||
|
||||
(type === "date" && (value instanceof Date || !isNaN(Date.parse(value))));
|
||||
}
|
||||
|
||||
// private static
|
||||
|
||||
}
|
||||
@ -1,148 +0,0 @@
|
||||
import {Connection} from "../../connection/Connection";
|
||||
import {CascadeOption, DynamicCascadeOptions} from "./../cascade/CascadeOption";
|
||||
import {RelationMetadata} from "../../metadata-builder/metadata/RelationMetadata";
|
||||
import {DocumentToDbObjectTransformer} from "./DocumentToDbObjectTransformer";
|
||||
import {PersistOperation} from "../../../odmhelpers/operation/PersistOperation";
|
||||
import {InverseSideUpdateOperation} from "../../../odmhelpers/operation/InverseSideUpdateOperation";
|
||||
import {PersistOperationGrouppedByDeepness} from "../../../odmhelpers/operation/PersistOperationGrouppedByDeepness";
|
||||
|
||||
export class EntityPersister {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
persist<Entity>(schema: DocumentSchema, document: Document, cascadeOptions?: DynamicCascadeOptions<Document>): Promise<Entity> {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DocumentPersister<Document> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
persist(schema: DocumentSchema, document: Document, cascadeOptions?: DynamicCascadeOptions<Document>): Promise<Document> {
|
||||
let transformer = new DocumentToDbObjectTransformer<Document>(this.connection);
|
||||
let dbObject = transformer.transform(schema, document, cascadeOptions);
|
||||
let groupedPersistOperations = this.groupPersistOperationsByDeepness(transformer.persistOperations);
|
||||
groupedPersistOperations = groupedPersistOperations.sort(groupedOperation => groupedOperation.deepness * -1);
|
||||
|
||||
let pendingPromise: Promise<any>;
|
||||
let relationWithOneDocumentIdsToBeUpdated: InverseSideUpdateOperation[] = [];
|
||||
groupedPersistOperations.map(groupedPersistOperation => {
|
||||
pendingPromise = Promise.all([pendingPromise]).then(() => {
|
||||
return Promise.all(groupedPersistOperation.operations.filter(persistOperation => !!persistOperation.allowedPersist).map((persistOperation: PersistOperation) =>
|
||||
this.save(persistOperation.schema, persistOperation.document, persistOperation.dbObject).then(document => {
|
||||
if (persistOperation.afterExecution) {
|
||||
persistOperation.afterExecution.forEach(afterExecution => {
|
||||
relationWithOneDocumentIdsToBeUpdated.push(afterExecution(document));
|
||||
});
|
||||
}
|
||||
return document;
|
||||
})
|
||||
));
|
||||
});
|
||||
});
|
||||
|
||||
transformer.postPersistOperations.forEach(postPersistOperation => postPersistOperation());
|
||||
|
||||
return Promise.all([pendingPromise])
|
||||
.then(result => this.save(schema, document, dbObject))
|
||||
.then(result => this.updateRelationInverseSideIds(relationWithOneDocumentIdsToBeUpdated))
|
||||
.then(result => document);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private groupPersistOperationsByDeepness(persistOperations: PersistOperation[]): PersistOperationGrouppedByDeepness[] {
|
||||
let groupedOperations: PersistOperationGrouppedByDeepness[] = [];
|
||||
persistOperations.forEach(persistOperation => {
|
||||
let groupedOperation = groupedOperations.reduce((found, groupedOperation) => groupedOperation.deepness === persistOperation.deepness ? groupedOperation : found, null);
|
||||
if (!groupedOperation) {
|
||||
groupedOperation = { deepness: persistOperation.deepness, operations: [] };
|
||||
groupedOperations.push(groupedOperation);
|
||||
}
|
||||
|
||||
groupedOperation.operations.push(persistOperation);
|
||||
});
|
||||
return groupedOperations;
|
||||
}
|
||||
|
||||
private save(schema: DocumentSchema, document: Document|any, dbObject: Object): Promise<Document> {
|
||||
let documentId = schema.getDocumentId(document);
|
||||
let driver = this.connection.driver;
|
||||
let broadcaster = this.connection.getBroadcaster(schema.documentClass);
|
||||
|
||||
if (documentId) {
|
||||
// let conditions = driver.createIdCondition(schema.getIdValue(documentId)/*, schema.idField.isObjectId*/);
|
||||
let conditions = schema.createIdCondition(documentId);
|
||||
broadcaster.broadcastBeforeUpdate({ document: document, conditions: conditions });
|
||||
return driver.replaceOne(schema.name, conditions, dbObject, { upsert: true }).then(saved => {
|
||||
broadcaster.broadcastAfterUpdate({ document: document, conditions: conditions });
|
||||
return document;
|
||||
});
|
||||
} else {
|
||||
broadcaster.broadcastBeforeInsert({ document: document });
|
||||
return driver.insertOne(schema.name, dbObject).then(result => {
|
||||
if (result.insertedId)
|
||||
document[schema.idField.name] = schema.getIdValue(result.insertedId); // String(result.insertedId);
|
||||
broadcaster.broadcastAfterInsert({ document: document });
|
||||
return document;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateRelationInverseSideIds(relationOperations: InverseSideUpdateOperation[]): Promise<any> {
|
||||
let updateInverseSideWithIdPromises = relationOperations
|
||||
.filter(relationOperation => !!relationOperation.inverseSideDocumentRelation)
|
||||
.map(relationOperation => {
|
||||
|
||||
let inverseSideSchema = relationOperation.inverseSideDocumentSchema;
|
||||
let inverseSideProperty = relationOperation.inverseSideDocumentRelation.name;
|
||||
let id = relationOperation.getDocumentId(); // this.connection.driver.createObjectId(relationOperation.getDocumentId(), relationOperation.documentSchema.idField.isObjectId);
|
||||
// let findCondition = this.connection.driver.createIdCondition(inverseSideSchema.getIdValue(relationOperation.inverseSideDocumentId)/*, inverseSideSchema.idField.isObjectId*/);
|
||||
let findCondition = inverseSideSchema.createIdCondition(relationOperation.inverseSideDocumentId);
|
||||
|
||||
if (inverseSideSchema.hasRelationWithOneWithName(inverseSideProperty))
|
||||
return this.connection.driver.setOneRelation(inverseSideSchema.name, findCondition, inverseSideProperty, id);
|
||||
if (inverseSideSchema.hasRelationWithManyWithName(inverseSideProperty))
|
||||
return this.connection.driver.setManyRelation(inverseSideSchema.name, findCondition, inverseSideProperty, id);
|
||||
});
|
||||
|
||||
return Promise.all(updateInverseSideWithIdPromises);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
import {Connection} from "../../connection/Connection";
|
||||
import {CascadeOption, DynamicCascadeOptions} from "./../cascade/CascadeOption";
|
||||
import {DbObjectColumnValidator} from "./DbObjectColumnValidator";
|
||||
import {ColumnTypeNotSupportedError} from "../../../odmhelpers/error/ColumnTypeNotSupportedError";
|
||||
import {PersistOperation} from "../../../odmhelpers/operation/PersistOperation";
|
||||
import {CascadeOptionUtils} from "../cascade/CascadeOptionUtils";
|
||||
import {InverseSideUpdateOperation} from "../../../odmhelpers/operation/InverseSideUpdateOperation";
|
||||
import {EntityMetadata} from "../../metadata-builder/metadata/EntityMetadata";
|
||||
import {RelationMetadata} from "../../metadata-builder/metadata/RelationMetadata";
|
||||
|
||||
/**
|
||||
* Makes a transformation of a given entity to the entity that can be saved to the db.
|
||||
*/
|
||||
export class EntityToDbObjectTransformer {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
private _persistOperations: PersistOperation[];
|
||||
private _postPersistOperations: Function[];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Accessors
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
get postPersistOperations() {
|
||||
return this._postPersistOperations;
|
||||
}
|
||||
|
||||
get persistOperations() {
|
||||
return this._persistOperations;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Transforms given entity into object that can be persisted into the db.
|
||||
*/
|
||||
transform(metadata: EntityMetadata,
|
||||
entity: any,
|
||||
cascadeOptionsInCallback?: DynamicCascadeOptions<any>): Object {
|
||||
|
||||
this._persistOperations = [];
|
||||
this._postPersistOperations = [];
|
||||
return this.entityToDbObject(0, metadata, entity, cascadeOptionsInCallback);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private entityToDbObject(deepness: number,
|
||||
metadata: EntityMetadata,
|
||||
entity: any,
|
||||
cascadeOptionsInCallback?: DynamicCascadeOptions<any>): Object {
|
||||
|
||||
const cascadeOptions = CascadeOptionUtils.prepareCascadeOptions(metadata, cascadeOptionsInCallback);
|
||||
const dbObject = {};
|
||||
|
||||
//
|
||||
if (metadata.createDateColumn && !metadata.getEntityId(entity))
|
||||
entity[metadata.createDateColumn.propertyName] = new Date();
|
||||
if (metadata.updateDateColumn)
|
||||
entity[metadata.updateDateColumn.propertyName] = new Date();
|
||||
|
||||
//
|
||||
Object.keys(entity).forEach(propertyName => {
|
||||
const cascadeOption = CascadeOptionUtils.find(cascadeOptions, propertyName);
|
||||
|
||||
if (metadata.hasColumnWithPropertyName(propertyName))
|
||||
this.parseColumn(metadata, dbObject, entity, propertyName);
|
||||
|
||||
if (metadata.hasRelationWithOneWithPropertyName(propertyName))
|
||||
this.parseRelationWithOne(deepness, metadata, dbObject, entity, propertyName, cascadeOption);
|
||||
|
||||
if (metadata.hasRelationWithManyWithPropertyName(propertyName))
|
||||
this.parseRelationWithMany(deepness, metadata, dbObject, entity, propertyName, cascadeOption);
|
||||
|
||||
});
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
private parseColumn(metadata: EntityMetadata, dbObject: any, entity: any, propertyName: string) {
|
||||
const column = metadata.findColumnWithPropertyName(propertyName);
|
||||
dbObject[column.name] = entity[propertyName];
|
||||
}
|
||||
|
||||
private parseRelationWithOne(deepness: number,
|
||||
metadata: EntityMetadata,
|
||||
dbObject: any,
|
||||
entity: any,
|
||||
columnName: any,
|
||||
cascadeOption?: CascadeOption) {
|
||||
|
||||
const relation = metadata.findRelationWithOneWithPropertyName(columnName);
|
||||
const addFunction = (id: any) => dbObject[relation.name] = id;
|
||||
this.parseRelation(deepness, metadata, entity, relation, entity[columnName], addFunction, cascadeOption);
|
||||
}
|
||||
|
||||
|
||||
private parseRelationWithMany( deepness: number,
|
||||
metadata: EntityMetadata,
|
||||
dbObject: any,
|
||||
entity: any,
|
||||
columnName: any,
|
||||
cascadeOption?: CascadeOption) {
|
||||
|
||||
const relation = metadata.findRelationWithManyWithPropertyName(columnName);
|
||||
const addFunction = (id: any) => dbObject[relation.name].push(id);
|
||||
|
||||
dbObject[relation.name] = [];
|
||||
entity[columnName].forEach((columnItem: any) => {
|
||||
this.parseRelation(deepness, metadata, entity, relation, columnItem, addFunction, cascadeOption);
|
||||
});
|
||||
}
|
||||
|
||||
private parseRelation(deepness: number,
|
||||
metadata: EntityMetadata,
|
||||
entity: any,
|
||||
relation: RelationMetadata,
|
||||
value: any,
|
||||
addFunction: (objectId: any) => void,
|
||||
cascadeOption?: CascadeOption) {
|
||||
|
||||
const relationTypeMetadata = this.connection.getMetadata(relation.type);
|
||||
const relatedEntityId = value ? relationTypeMetadata.getEntityId(value) : null;
|
||||
|
||||
if (relatedEntityId && !CascadeOptionUtils.isCascadeUpdate(relation, cascadeOption)) {
|
||||
addFunction(this.createObjectId(relatedEntityId, relationTypeMetadata));
|
||||
|
||||
} else if (value) {
|
||||
// check if we already added this object for persist (can happen when object of the same instance is used in different places)
|
||||
let operationOnThisValue = this._persistOperations.reduce((found, operation) => operation.entity === value ? operation : found, null);
|
||||
let subCascades = cascadeOption ? cascadeOption.cascades : undefined;
|
||||
let relatedDbObject = this.entityToDbObject(deepness + 1, relationTypeMetadata, value, subCascades);
|
||||
let doPersist = CascadeOptionUtils.isCascadePersist(relation, cascadeOption);
|
||||
|
||||
let afterExecution = (insertedRelationEntity: any) => {
|
||||
let id = relationTypeMetadata.getEntityId(insertedRelationEntity);
|
||||
addFunction(this.createObjectId(id, relationTypeMetadata));
|
||||
const inverseSideRelationMetadata = relationTypeMetadata.findRelationWithPropertyName(relation.inverseSideProperty);
|
||||
return <InverseSideUpdateOperation> {
|
||||
inverseSideEntityId: id,
|
||||
inverseSideEntityMetadata: relationTypeMetadata,
|
||||
inverseSideEntityRelation: inverseSideRelationMetadata,
|
||||
entityMetadata: metadata,
|
||||
getEntityId: () => metadata.getEntityId(entity)
|
||||
};
|
||||
};
|
||||
|
||||
if (!operationOnThisValue) {
|
||||
operationOnThisValue = <PersistOperation> {
|
||||
allowedPersist: false,
|
||||
deepness: deepness,
|
||||
entity: value,
|
||||
metadata: relationTypeMetadata,
|
||||
dbObject: relatedDbObject,
|
||||
afterExecution: []
|
||||
};
|
||||
this._persistOperations.push(operationOnThisValue);
|
||||
}
|
||||
|
||||
// this check is required because we check
|
||||
operationOnThisValue.afterExecution.push(afterExecution);
|
||||
operationOnThisValue.allowedPersist = operationOnThisValue.allowedPersist || doPersist;
|
||||
}
|
||||
}
|
||||
|
||||
private createObjectId(id: any, metadata: EntityMetadata): any {
|
||||
if (metadata.idColumn.isAutoGenerated && !id) {
|
||||
return this.connection.driver.generateId();
|
||||
} else if (metadata.idColumn.isAutoGenerated && id) {
|
||||
return id;
|
||||
} else if (metadata.idColumn.isObjectId) {
|
||||
return this.connection.driver.createObjectId(id);
|
||||
}
|
||||
|
||||
throw new Error("Cannot create object id");
|
||||
// return this.connection.driver.createObjectId(id, metadata.idColumn.isObjectId);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,237 +0,0 @@
|
||||
import {DocumentSchema} from "../../schema/DocumentSchema";
|
||||
import {Connection} from "../../connection/Connection";
|
||||
import {RelationSchema} from "../../schema/RelationSchema";
|
||||
import {CascadeOption, DynamicCascadeOptions} from "./../cascade/CascadeOption";
|
||||
import {RemoveOperation} from "../../../odmhelpers/operation/RemoveOperation";
|
||||
import {InverseSideUpdateOperation} from "../../../odmhelpers/operation/InverseSideUpdateOperation";
|
||||
import {CascadeOptionUtils} from "../cascade/CascadeOptionUtils";
|
||||
import {NoDocumentWithSuchIdError} from "../../../odmhelpers/error/NoDocumentWithSuchIdError";
|
||||
import {ObjectID} from "mongodb";
|
||||
|
||||
/**
|
||||
* Helps to remove a document and all its relations by given cascade operations.
|
||||
*/
|
||||
export class DocumentRemover<Document> {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Properties
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private connection: Connection;
|
||||
private inverseSideUpdateOperations: InverseSideUpdateOperation[] = [];
|
||||
private removeOperations: RemoveOperation[] = [];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(connection: Connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Computes and creates a list of all related documents that should exist in the given document, but are missing.
|
||||
* Those missing documents are treated as removed (even if they are not loaded at all, so be careful).
|
||||
*/
|
||||
computeRemovedRelations(schema: DocumentSchema,
|
||||
document: Document,
|
||||
dynamicCascadeOptions?: DynamicCascadeOptions<Document>): Promise<void> {
|
||||
|
||||
let documentId = schema.getDocumentId(document);
|
||||
let cascadeOptions = CascadeOptionUtils.prepareCascadeOptions(schema, dynamicCascadeOptions);
|
||||
if (!documentId)
|
||||
return Promise.resolve();
|
||||
|
||||
// load original document so we can compare and calculate changed set
|
||||
// const query = this.connection.driver.createIdCondition(schema.getIdValue(documentId));
|
||||
const query = schema.createIdCondition(documentId);
|
||||
return this.connection.driver.findOne(schema.name, query).then((dbObject: any) => {
|
||||
if (!dbObject)
|
||||
return Promise.resolve();
|
||||
// throw new NoDocumentWithSuchIdException(documentId, schema.name);
|
||||
|
||||
// iterate throw each key in the document and find relations to compute removals of
|
||||
let promises = Object.keys(dbObject).map(originalDocumentProperty => {
|
||||
|
||||
let relationWithOne = schema.findRelationWithOneByDbName(originalDocumentProperty);
|
||||
if (relationWithOne) {
|
||||
let id = schema.getIdValue(dbObject[originalDocumentProperty]);
|
||||
return this.computeRelationToBeRemoved(id, relationWithOne, document, cascadeOptions);
|
||||
}
|
||||
|
||||
let relationWithMany = schema.findRelationWithManyByDbName(originalDocumentProperty);
|
||||
if (relationWithMany) {
|
||||
return Promise.all(dbObject[originalDocumentProperty].map((id: any) => {
|
||||
return this.computeRelationToBeRemoved(schema.getIdValue(id), relationWithMany, document, cascadeOptions);
|
||||
}));
|
||||
}
|
||||
|
||||
});
|
||||
return Promise.all<any>(promises).then(function() {});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes all remove operations. This means all document that are saved to be removed gonna remove now.
|
||||
*/
|
||||
executeRemoveOperations(): Promise<void> {
|
||||
return Promise.all(this.removeOperations.map(operation => {
|
||||
let broadcaster = this.connection.getBroadcaster(operation.schema.documentClass);
|
||||
// const query = this.connection.driver.createIdCondition(operation.schema.getIdValue(operation.id));
|
||||
const query = operation.schema.createIdCondition(operation.id);
|
||||
broadcaster.broadcastBeforeRemove({ documentId: operation.id });
|
||||
return this.connection.driver.deleteOne(operation.schema.name, query).then(result => {
|
||||
broadcaster.broadcastAfterRemove({ documentId: operation.id });
|
||||
});
|
||||
})).then(function() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs all inverse side update operations. These operations mean when we remove some document which is used
|
||||
* in another document, we must go to that document and remove this usage from him. This is what this function does.
|
||||
*/
|
||||
executeUpdateInverseSideRelationRemoveIds(): Promise<void> {
|
||||
let inverseSideUpdates = this.excludeRemovedDocumentsFromInverseSideUpdateOperations();
|
||||
let updateInverseSideWithIdPromises = inverseSideUpdates.map(relationOperation => {
|
||||
|
||||
let inverseSideSchema = relationOperation.inverseSideDocumentSchema;
|
||||
let inverseSideProperty = relationOperation.inverseSideDocumentRelation.name;
|
||||
let id = relationOperation.getDocumentId(); // this.connection.driver.createObjectId(relationOperation.getDocumentId());
|
||||
// let findCondition = this.connection.driver.createIdCondition(inverseSideSchema.getIdValue(relationOperation.inverseSideDocumentId));
|
||||
const findCondition = inverseSideSchema.createIdCondition(relationOperation.inverseSideDocumentId);
|
||||
|
||||
if (inverseSideSchema.hasRelationWithOneWithName(inverseSideProperty))
|
||||
return this.connection.driver.unsetOneRelation(inverseSideSchema.name, findCondition, inverseSideProperty, id);
|
||||
if (inverseSideSchema.hasRelationWithManyWithPropertyName(inverseSideProperty))
|
||||
return this.connection.driver.unsetManyRelation(inverseSideSchema.name, findCondition, inverseSideProperty, id);
|
||||
});
|
||||
|
||||
return Promise.all(updateInverseSideWithIdPromises).then(function() {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers given document id of the given schema for a removal operation.
|
||||
*/
|
||||
registerDocumentRemoveOperation(schema: DocumentSchema,
|
||||
documentId: string,
|
||||
dynamicCascadeOptions?: DynamicCascadeOptions<Document>): Promise<void> {
|
||||
|
||||
let cascadeOptions = CascadeOptionUtils.prepareCascadeOptions(schema, dynamicCascadeOptions);
|
||||
|
||||
// load original document so we can compare and calculate which of its relations to remove by cascades
|
||||
// const query = this.connection.driver.createIdCondition(schema.getIdValue(documentId));
|
||||
const query = schema.createIdCondition(documentId);
|
||||
return this.connection.driver.findOne(schema.name, query).then((dbObject: any) => {
|
||||
if (!dbObject)
|
||||
return Promise.resolve();
|
||||
// throw new NoDocumentWithSuchIdException(documentId, schema.name);
|
||||
|
||||
// iterate throw each key in the db document and find relations to compute removals of
|
||||
let promises = Object.keys(dbObject).map(originalDocumentProperty => {
|
||||
|
||||
let relationWithOneField = schema.findRelationWithOneByDbName(originalDocumentProperty);
|
||||
if (relationWithOneField) {
|
||||
let id = schema.getIdValue(dbObject[originalDocumentProperty]);
|
||||
return this.parseRelationForRemovalOperation(id, schema, documentId, relationWithOneField, cascadeOptions);
|
||||
}
|
||||
|
||||
let relationWithManyField = schema.findRelationWithManyByDbName(originalDocumentProperty);
|
||||
if (relationWithManyField)
|
||||
return Promise.all(dbObject[originalDocumentProperty].map((id: any) => {
|
||||
return this.parseRelationForRemovalOperation(schema.getIdValue(id), schema, documentId, relationWithManyField, cascadeOptions);
|
||||
}));
|
||||
});
|
||||
|
||||
// register a new remove operation
|
||||
this.removeOperations.push({ schema: schema, id: documentId });
|
||||
return Promise.all<any>(promises).then(function() {});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Computes if item with given id in a given relation should be removed or not.
|
||||
*/
|
||||
private computeRelationToBeRemoved(id: string, relation: RelationSchema, document: Document|any, cascadeOptions?: CascadeOption[]): Promise<void> {
|
||||
let cascadeOption = CascadeOptionUtils.find(cascadeOptions, relation.propertyName);
|
||||
let relatedSchema = this.connection.getSchema(<Function> relation.type);
|
||||
let subCascades = cascadeOption ? cascadeOption.cascades : undefined;
|
||||
|
||||
// if cascades are not enabled for this relation then skip it
|
||||
if (!CascadeOptionUtils.isCascadeRemove(relation, cascadeOption)) return;
|
||||
|
||||
// if such document id already marked for remove operations then do nothing - no need to add it again
|
||||
if (this.isRemoveOperation(id, relatedSchema)) return;
|
||||
|
||||
// if related document with given id does exists in the document then it means that nothing is removed from document and we dont have to remove anything from the db
|
||||
let isThisIdInDocumentsRelation = !!document[relation.propertyName];
|
||||
if (document[relation.propertyName] instanceof Array)
|
||||
isThisIdInDocumentsRelation = document[relation.propertyName].filter((item: any) => {
|
||||
const idFieldValue = relatedSchema.getDocumentId(item);
|
||||
return idFieldValue instanceof ObjectID ? idFieldValue.equals(id) : idFieldValue === id;
|
||||
}).length > 0;
|
||||
|
||||
if (isThisIdInDocumentsRelation) return;
|
||||
|
||||
return this.registerDocumentRemoveOperation(relatedSchema, id, subCascades);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse given documents relation and registers relations's data remove operations.
|
||||
*/
|
||||
private parseRelationForRemovalOperation(id: string,
|
||||
schema: DocumentSchema,
|
||||
documentId: string,
|
||||
relation: RelationSchema,
|
||||
cascadeOptions?: CascadeOption[]): Promise<void> {
|
||||
|
||||
let cascadeOption = CascadeOptionUtils.find(cascadeOptions, relation.propertyName);
|
||||
let relatedSchema = this.connection.getSchema(<Function> relation.type);
|
||||
let subCascades = cascadeOption ? cascadeOption.cascades : undefined;
|
||||
|
||||
// if removal operation already registered then no need to register it again
|
||||
if (this.isRemoveOperation(id, relatedSchema)) return;
|
||||
|
||||
// add new inverse side update operation
|
||||
if (relation.inverseSideProperty) {
|
||||
const inverseSideRelationSchema = relatedSchema.findRelationWithPropertyName(relation.inverseSideProperty);
|
||||
this.inverseSideUpdateOperations.push({
|
||||
inverseSideDocumentId: id,
|
||||
inverseSideDocumentSchema: relatedSchema,
|
||||
inverseSideDocumentRelation: inverseSideRelationSchema,
|
||||
documentSchema: schema,
|
||||
getDocumentId: () => documentId
|
||||
});
|
||||
}
|
||||
|
||||
// register document and its relations for removal if cascade operation is set
|
||||
if (CascadeOptionUtils.isCascadeRemove(relation, cascadeOption)) {
|
||||
return this.registerDocumentRemoveOperation(relatedSchema, id, subCascades);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if remove operation with given document id and schema is registered or not.
|
||||
*/
|
||||
private isRemoveOperation(id: string, schema: DocumentSchema): boolean {
|
||||
return this.removeOperations.filter(operation => operation.id === id && operation.schema === schema).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* From operations that are scheduled for update we remove all updates of documents that are scheduled for removal.
|
||||
* Since they are removed there is no since in updating them, e.g. they are removed and nothing to update.
|
||||
*/
|
||||
private excludeRemovedDocumentsFromInverseSideUpdateOperations(): InverseSideUpdateOperation[] {
|
||||
return this.inverseSideUpdateOperations.filter(updateOperation => {
|
||||
return this.removeOperations.filter(removeOperation => removeOperation.id === updateOperation.inverseSideDocumentId).length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
23
package.json
23
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "typeodm",
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"description": "ODM for MongoDB used Typescript",
|
||||
"version": "0.0.1",
|
||||
"description": "Data-mapper ORM for Typescript",
|
||||
"license": "Apache-2.0",
|
||||
"readmeFilename": "README.md",
|
||||
"author": {
|
||||
@ -11,17 +11,17 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PLEEROCK/typeodm.git"
|
||||
"url": "https://github.com/pleerock/typeorm.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/PLEEROCK/typeodm/issues"
|
||||
"url": "https://github.com/pleerock/typeorm/issues"
|
||||
},
|
||||
"tags": [
|
||||
"odm",
|
||||
"orm",
|
||||
"typescript",
|
||||
"typescript-odm",
|
||||
"mongodb",
|
||||
"mongodb-odm"
|
||||
"typescript-orm",
|
||||
"mysql",
|
||||
"mysql-orm"
|
||||
],
|
||||
"devDependencies": {
|
||||
"chai": "^3.4.1",
|
||||
@ -33,7 +33,6 @@
|
||||
"gulp-tslint": "^4.3.1",
|
||||
"gulpclass": "0.1.0",
|
||||
"mocha": "^2.3.2",
|
||||
"mysql": "^2.10.2",
|
||||
"sinon": "^1.17.2",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"tslint": "^3.3.0",
|
||||
@ -44,11 +43,11 @@
|
||||
"dependencies": {
|
||||
"fs": "^0.0.2",
|
||||
"lodash": "^4.5.0",
|
||||
"mongodb": ">=2.0.0",
|
||||
"path": "^0.11.14",
|
||||
"reflect-metadata": "^0.1.3",
|
||||
"sha1": "^1.1.1",
|
||||
"typedi": "~0.2.0"
|
||||
"typedi": "~0.2.0",
|
||||
"mysql": "^2.10.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postversion": "./node_modules/.bin/gulp package",
|
||||
|
||||
@ -16,7 +16,7 @@ export class TypeORM {
|
||||
static connectionManager = new ConnectionManager();
|
||||
|
||||
/**
|
||||
* Creates a new connection to mongodb. Imports documents and subscribers from the given directories.
|
||||
* Creates a new connection to mysql. Imports documents and subscribers from the given directories.
|
||||
*/
|
||||
static createMysqlConnection(options: string, documentDirectories: string[]|Function[], subscriberDirectories?: string[]): Promise<Connection>;
|
||||
static createMysqlConnection(options: ConnectionOptions, documentDirectories: string[]|Function[], subscriberDirectories?: string[]): Promise<Connection>;
|
||||
|
||||
@ -3,9 +3,9 @@ import {defaultMetadataStorage} from "../metadata-builder/MetadataStorage";
|
||||
import {OrmEventSubscriberMetadata} from "../metadata-builder/metadata/OrmEventSubscriberMetadata";
|
||||
|
||||
/**
|
||||
* Subscribers that gonna listen to odm events must be annotated with this annotation.
|
||||
* Subscribers that gonna listen to orm events must be annotated with this annotation.
|
||||
*/
|
||||
export function OdmEventSubscriber() {
|
||||
export function OrmEventSubscriber() {
|
||||
return function (target: Function) {
|
||||
const metadata = new OrmEventSubscriberMetadata(target);
|
||||
defaultMetadataStorage.addOrmEventSubscriberMetadata(metadata);
|
||||
@ -19,7 +19,7 @@ export function OrmRepository(className: Function, connectionName?: string): Fun
|
||||
try {
|
||||
container = require("typedi/Container").Container;
|
||||
} catch (err) {
|
||||
throw new Error("OdmRepository cannot be used because typedi extension is not installed.");
|
||||
throw new Error("OrmRepository cannot be used because typedi extension is not installed.");
|
||||
}
|
||||
|
||||
container.registerParamHandler({
|
||||
|
||||
@ -7,7 +7,7 @@ import {EntityMetadata} from "../metadata-builder/metadata/EntityMetadata";
|
||||
import {Connection} from "../connection/Connection";
|
||||
|
||||
/**
|
||||
* This driver organizes work with mongodb database.
|
||||
* This driver organizes work with mysql database.
|
||||
*/
|
||||
export class MysqlDriver implements Driver {
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import {RemoveEvent} from "./event/RemoveEvent";
|
||||
import {InsertEvent} from "./event/InsertEvent";
|
||||
|
||||
/**
|
||||
* Classes that implement this interface are subscribers that subscribe for the specific events of the ODM.
|
||||
* Classes that implement this interface are subscribers that subscribe for the specific events of the ORM.
|
||||
*/
|
||||
export interface OrmSubscriber<Entity> {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user