removed old odm sources

This commit is contained in:
Umed Khudoiberdiev 2016-02-29 12:40:33 +05:00
parent 976d6fd10a
commit bdfc2eb05f
23 changed files with 17 additions and 1280 deletions

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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?";
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -1,9 +0,0 @@
export interface JoinFieldOption {
inner?: boolean;
field: string|any;
condition?: any;
joins: JoinFieldOption|any[]; // todo: check its type - looks wrong
}

View File

@ -1,3 +0,0 @@
export class JoinFieldOptionUtils {
}

View File

@ -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;
}

View File

@ -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)[];
}

View File

@ -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[];
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
});
}
}

View File

@ -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",

View File

@ -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>;

View File

@ -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({

View File

@ -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 {

View File

@ -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> {