implemented partial persist

This commit is contained in:
Umed Khudoiberdiev 2017-02-27 12:33:04 +05:00
parent 2984c4dc2c
commit 540ffffafc
23 changed files with 489 additions and 140 deletions

View File

@ -22,12 +22,13 @@ each for its own `findOne*` or `find*` methods
### NEW FEATURES
* added `mongodb` support
* entity now can be saved partially within `persist` method
* entity now can be saved partially within `update` method
* added prefix support to embeddeds
### BUG FIXES
* fixes [#285](https://github.com/typeorm/typeorm/issues/285) - issue when cli commands rise `CannotCloseNotConnectedError`
* fixes [#309](https://github.com/typeorm/typeorm/issues/309) - issue when `andHaving` didn't work without calling `having` on `QueryBuilder`
# 0.0.9 (latest)

View File

@ -86,7 +86,7 @@ createConnection(options).then(connection => {
.then(entity => {
console.log("Entity is loaded: ", entity);
console.log("Now remove it");
return postRepository.remove(entity);
return postRepository.remove(entity!);
})
.then(entity => {
console.log("Entity has been removed");

View File

@ -60,7 +60,7 @@ export class EverythingEntity {
jsonColumn: any;
@Column()
alsoJson: Object;
alsoJson: any;
@Column("simple_array")
simpleArrayColumn: string[];

View File

@ -49,12 +49,12 @@ createConnection(options).then(connection => {
return authorRepository.persist(author);
})
.then(author => {
.then((author: any) => { // temporary
console.log("Author with a new post has been saved. Lets try to update post in the author");
return author.posts.then(posts => {
posts[0].title = "should be updated second post";
return authorRepository.persist(author);
return author.posts!.then((posts: any) => { // temporary
posts![0]!.title = "should be updated second post";
return authorRepository.persist(author!);
});
})
.then(updatedAuthor => {
@ -97,8 +97,8 @@ createConnection(options).then(connection => {
.then(posts => {
console.log("Post with categories are loaded: ", posts);
console.log("Lets remove one of the categories: ");
return posts[0].categories.then(categories => {
categories.splice(0, 1);
return posts[0].categories.then((categories: any) => { // temporary
categories!.splice(0, 1);
// console.log(posts[0]);
return postRepository.persist(posts[0]);
});

View File

@ -78,9 +78,9 @@ createConnection(options).then(connection => {
console.log("Lets update a post - return old author back:");
console.log("updating with: ", author);
loadedPost.title = "Umed's post";
loadedPost.author = author;
return postRepository.persist(loadedPost);
loadedPost!.title = "Umed's post";
loadedPost!.author = author;
return postRepository.persist(loadedPost!);
})
.then(updatedPost => {
return postRepository

View File

@ -44,10 +44,10 @@ createConnection(options).then(connection => {
.then(loadedQuestion => {
console.log("question has been loaded: ", loadedQuestion);
loadedQuestion.counters.commentCount = 7;
loadedQuestion.counters.metadata = "#updated question";
loadedQuestion!.counters.commentCount = 7;
loadedQuestion!.counters.metadata = "#updated question";
return questionRepository.persist(loadedQuestion);
return questionRepository.persist(loadedQuestion!);
})
.then(updatedQuestion => {
console.log("question has been updated: ", updatedQuestion);

View File

@ -52,15 +52,15 @@ createConnection(options).then(connection => {
.createQueryBuilder("p")
.leftJoinAndSelect("p.author", "author")
.leftJoinAndSelect("p.categories", "categories")
.where("p.id = :id", { id: loadedPost.id })
.where("p.id = :id", { id: loadedPost!.id })
.getOne();
})
.then(loadedPost => {
console.log("---------------------------");
console.log("load finished. Now lets update entity");
loadedPost.text = "post updated";
loadedPost.author.name = "Bakha";
return postRepository.persist(loadedPost);
loadedPost!.text = "post updated";
loadedPost!.author.name = "Bakha";
return postRepository.persist(loadedPost!);
})
.then(loadedPost => {
console.log("---------------------------");

View File

@ -44,22 +44,22 @@ createConnection(options).then(connection => {
return postRepository.findOneById(post.id);
})
.then(loadedPost => {
console.log("post is loaded. Its uid is " + loadedPost.uid);
console.log("post is loaded. Its uid is " + loadedPost!.uid);
console.log("Lets now load it with relations.");
console.log("---------------------------");
return postRepository
.createQueryBuilder("p")
.leftJoinAndSelect("p.author", "author")
.leftJoinAndSelect("p.categories", "categories")
.where("p.id = :id", { id: loadedPost.id })
.where("p.id = :id", { id: loadedPost!.id })
.getOne();
})
.then(loadedPost => {
console.log("load finished. Now lets update entity");
console.log("---------------------------");
loadedPost.text = "post updated";
loadedPost.author.name = "Bakha";
return postRepository.persist(loadedPost);
loadedPost!.text = "post updated";
loadedPost!.author.name = "Bakha";
return postRepository.persist(loadedPost!);
})
.then(loadedPost => {
console.log("update finished. Now lets remove entity");

View File

@ -1,3 +1,6 @@
/**
* Same as Partial<T> but goes deeper and makes Partial<T> all its properties and sub-properties.
*/
export type DeepPartial<T> = {
readonly [P in keyof T]?: DeepPartial<T[P]>;
[P in keyof T]?: DeepPartial<T[P]>;
};

View File

@ -4,6 +4,8 @@ import {getMetadataArgsStorage} from "../../index";
import {PrimaryColumnCannotBeNullableError} from "../error/PrimaryColumnCannotBeNullableError";
import {ColumnMetadataArgs} from "../../metadata-args/ColumnMetadataArgs";
// todo: add overloads for PrimaryGeneratedColumn(generationType: "sequence"|"uuid" = "sequence", options?: ColumnOptions)
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.

View File

@ -174,22 +174,41 @@ export abstract class BaseEntityManager {
/**
* Checks if entity has an id.
*/
hasId(entity: Object): boolean;
hasId(entity: any): boolean;
/**
* Checks if entity of given schema name has an id.
*/
hasId(target: string, entity: Object): boolean;
hasId(target: string, entity: any): boolean;
/**
* Checks if entity has an id by its Function type or schema name.
*/
hasId(targetOrEntity: Object|string, maybeEntity?: Object): boolean {
hasId(targetOrEntity: any|string, maybeEntity?: any): boolean {
const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor;
const entity = arguments.length === 2 ? <Object> maybeEntity : <Object> targetOrEntity;
const entity = arguments.length === 2 ? maybeEntity : targetOrEntity;
return this.getRepository(target as any).hasId(entity);
}
/**
* Gets entity mixed id.
*/
getId(entity: any): any;
/**
* Gets entity mixed id.
*/
getId(target: string, entity: any): any;
/**
* Gets entity mixed id.
*/
getId(targetOrEntity: any|string, maybeEntity?: any): any {
const target = arguments.length === 2 ? targetOrEntity : targetOrEntity.constructor;
const entity = arguments.length === 2 ? maybeEntity : targetOrEntity;
return this.getRepository(target as any).getId(entity);
}
/**
* Creates a new query builder that can be used to build an sql query.
*/

View File

@ -6,6 +6,9 @@ import {QueryRunnerProviderAlreadyReleasedError} from "../query-runner/error/Que
import {QueryRunnerProvider} from "../query-runner/QueryRunnerProvider";
import {ObjectLiteral} from "../common/ObjectLiteral";
import {FindOneOptions} from "../find-options/FindOneOptions";
import {DeepPartial} from "../common/DeepPartial";
import {RemoveOptions} from "../repository/RemoveOptions";
import {PersistOptions} from "../repository/PersistOptions";
/**
* Entity manager supposed to work with any entity, automatically find its repository and call its methods,
@ -29,60 +32,87 @@ export class EntityManager extends BaseEntityManager {
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(entity: Entity): Promise<Entity>;
persist<Entity>(entity: Entity, options?: PersistOptions): Promise<Entity>;
/**
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(targetOrEntity: Function, entity: Entity): Promise<Entity>;
persist<Entity>(targetOrEntity: Function, entity: Entity, options?: PersistOptions): Promise<Entity>;
/**
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(targetOrEntity: string, entity: Entity): Promise<Entity>;
persist<Entity>(targetOrEntity: string, entity: Entity, options?: PersistOptions): Promise<Entity>;
/**
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(entities: Entity[]): Promise<Entity[]>;
persist<Entity>(entities: Entity[], options?: PersistOptions): Promise<Entity[]>;
/**
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(targetOrEntity: Function, entities: Entity[]): Promise<Entity[]>;
persist<Entity>(targetOrEntity: Function, entities: Entity[], options?: PersistOptions): Promise<Entity[]>;
/**
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
persist<Entity>(targetOrEntity: string, entities: Entity[]): Promise<Entity[]>;
persist<Entity>(targetOrEntity: string, entities: Entity[], options?: PersistOptions): Promise<Entity[]>;
/**
* Persists (saves) a given entity in the database.
*/
persist<Entity>(targetOrEntity: (Entity|Entity[])|Function|string, maybeEntity?: Entity|Entity[]): Promise<Entity|Entity[]> {
persist<Entity>(targetOrEntity: (Entity|Entity[])|Function|string, maybeEntity?: Entity|Entity[], options?: PersistOptions): Promise<Entity|Entity[]> {
const target = arguments.length === 2 ? maybeEntity as Entity|Entity[] : targetOrEntity as Function|string;
const entity = arguments.length === 2 ? maybeEntity as Entity|Entity[] : targetOrEntity as Entity|Entity[];
return Promise.resolve().then(() => { // we MUST call "fake" resolve here to make sure all properties of lazily loaded properties are resolved.
if (typeof target === "string") {
return this.getRepository<Entity|Entity[]>(target).persist(entity);
return this.getRepository<Entity|Entity[]>(target).persist(entity, options);
} else {
// todo: throw exception if constructor in target is not set
if (target instanceof Array) {
if (target.length === 0)
return Promise.resolve(target);
return this.getRepository<Entity[]>(target[0].constructor).persist(entity as Entity[]);
return this.getRepository<Entity[]>(target[0].constructor).persist(entity as Entity[], options);
} else {
return this.getRepository<Entity>(target.constructor).persist(entity as Entity);
return this.getRepository<Entity>(target.constructor).persist(entity as Entity, options);
}
}
});
}
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
async update<Entity>(target: Function|string, conditions: Partial<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given find options.
*/
async update<Entity>(target: Function|string, findOptions: FindOneOptions<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
async update<Entity>(target: Function|string, conditionsOrFindOptions: Partial<Entity>|FindOneOptions<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void> {
return this.getRepository<Entity|Entity[]>(target as any)
.update(conditionsOrFindOptions as any, partialEntity, options);
}
/**
* Updates entity partially. Entity will be found by a given id.
*/
async updateById<Entity>(target: Function|string, id: any, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void> {
return this.getRepository<Entity|Entity[]>(target as any)
.updateById(id, partialEntity, options);
}
/**
* Removes a given entity from the database.
*/
@ -91,45 +121,53 @@ export class EntityManager extends BaseEntityManager {
/**
* Removes a given entity from the database.
*/
remove<Entity>(targetOrEntity: Function, entity: Entity): Promise<Entity>;
remove<Entity>(targetOrEntity: Function, entity: Entity, options?: RemoveOptions): Promise<Entity>;
/**
* Removes a given entity from the database.
*/
remove<Entity>(targetOrEntity: string, entity: Entity): Promise<Entity>;
remove<Entity>(targetOrEntity: string, entity: Entity, options?: RemoveOptions): Promise<Entity>;
/**
* Removes a given entity from the database.
*/
remove<Entity>(entity: Entity[]): Promise<Entity>;
remove<Entity>(entity: Entity[], options?: RemoveOptions): Promise<Entity>;
/**
* Removes a given entity from the database.
*/
remove<Entity>(targetOrEntity: Function, entity: Entity[]): Promise<Entity[]>;
remove<Entity>(targetOrEntity: Function, entity: Entity[], options?: RemoveOptions): Promise<Entity[]>;
/**
* Removes a given entity from the database.
*/
remove<Entity>(targetOrEntity: string, entity: Entity[]): Promise<Entity[]>;
remove<Entity>(targetOrEntity: string, entity: Entity[], options?: RemoveOptions): Promise<Entity[]>;
/**
* Removes a given entity from the database.
*/
remove<Entity>(targetOrEntity: (Entity|Entity[])|Function|string, maybeEntity?: Entity|Entity[]): Promise<Entity|Entity[]> {
remove<Entity>(targetOrEntity: (Entity|Entity[])|Function|string, maybeEntity?: Entity|Entity[], options?: RemoveOptions): Promise<Entity|Entity[]> {
const target = arguments.length === 2 ? maybeEntity as Entity|Entity[] : targetOrEntity as Function|string;
const entity = arguments.length === 2 ? maybeEntity as Entity|Entity[] : targetOrEntity as Entity|Entity[];
if (typeof target === "string") {
return this.getRepository<Entity|Entity[]>(target).remove(entity);
return this.getRepository<Entity|Entity[]>(target).remove(entity, options);
} else {
// todo: throw exception if constructor in target is not set
if (target instanceof Array) {
return this.getRepository<Entity[]>(target[0].constructor).remove(entity as Entity[]);
return this.getRepository<Entity[]>(target[0].constructor).remove(entity as Entity[], options);
} else {
return this.getRepository<Entity>(target.constructor).remove(entity as Entity);
return this.getRepository<Entity>(target.constructor).remove(entity as Entity, options);
}
}
}
/**
* Removes entity by a given entity id.
*/
async removeById(targetOrEntity: Function|string, id: any, options?: RemoveOptions): Promise<void> {
return this.getRepository(targetOrEntity as any).removeById(id, options);
}
/**
* Counts entities that match given options.
*/

View File

@ -0,0 +1,12 @@
/**
* Special options passed to Repository#persist method.
*/
export interface PersistOptions {
/**
* Additional data to be passed with persist method.
* This data can be used in subscribers then.
*/
data?: any;
}

View File

@ -0,0 +1,12 @@
/**
* Special options passed to Repository#remove method.
*/
export interface RemoveOptions {
/**
* Additional data to be passed with remove method.
* This data can be used in subscribers then.
*/
data?: any;
}

View File

@ -11,6 +11,8 @@ import {SubjectOperationExecutor} from "../persistence/SubjectOperationExecutor"
import {SubjectBuilder} from "../persistence/SubjectBuilder";
import {FindOneOptions} from "../find-options/FindOneOptions";
import {DeepPartial} from "../common/DeepPartial";
import {PersistOptions} from "./PersistOptions";
import {RemoveOptions} from "./RemoveOptions";
/**
* Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
@ -57,6 +59,13 @@ export class Repository<Entity extends ObjectLiteral> {
return this.metadata.hasId(entity);
}
/**
* Gets entity mixed id.
*/
getId(entity: Entity): any {
return this.metadata.getEntityIdMixedMap(entity);
}
/**
* Creates a new query builder that can be used to build a sql query.
*/
@ -87,7 +96,7 @@ export class Repository<Entity extends ObjectLiteral> {
* Creates a new entity instance or instances.
* Can copy properties from the given object into new entities.
*/
create(plainEntityLikeOrPlainEntityLikes?: DeepPartial<Entity>|Partial<Entity>[]): Entity|Entity[] {
create(plainEntityLikeOrPlainEntityLikes?: DeepPartial<Entity>|DeepPartial<Entity>[]): Entity|Entity[] {
if (!plainEntityLikeOrPlainEntityLikes)
return this.metadata.create();
@ -130,20 +139,18 @@ export class Repository<Entity extends ObjectLiteral> {
* Persists (saves) all given entities in the database.
* If entities do not exist in the database then inserts, otherwise updates.
*/
async persist(entities: Entity[]): Promise<Entity[]>;
async persist(entities: Entity[], options?: PersistOptions): Promise<Entity[]>;
/**
* Persists (saves) a given entity in the database.
* If entity does not exist in the database then inserts, otherwise updates.
*/
async persist(entity: Entity): Promise<Entity>;
async persist(entity: Entity, options?: PersistOptions): Promise<Entity>;
/**
* Persists one or many given entities.
*
* todo: use Partial<Entity> instead, and make sure it works properly
*/
async persist(entityOrEntities: Entity|Entity[]): Promise<Entity|Entity[]> {
async persist(entityOrEntities: Entity|Entity[], options?: PersistOptions): Promise<Entity|Entity[]> {
// if for some reason non empty entity was passed then return it back without having to do anything
if (!entityOrEntities)
@ -171,20 +178,54 @@ export class Repository<Entity extends ObjectLiteral> {
}
}
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
async update(conditions: Partial<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given find options.
*/
async update(findOptions: FindOneOptions<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void>;
/**
* Updates entity partially. Entity can be found by a given conditions.
*/
async update(conditionsOrFindOptions: Partial<Entity>|FindOneOptions<Entity>, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void> {
const entity = await this.findOne(conditionsOrFindOptions as any); // this is temporary, in the future can be refactored to perform better
if (!entity)
throw new Error(`Cannot find entity to update by a given criteria`);
Object.assign(entity, partialEntity);
await this.persist(entity, options);
}
/**
* Updates entity partially. Entity will be found by a given id.
*/
async updateById(id: any, partialEntity: DeepPartial<Entity>, options?: PersistOptions): Promise<void> {
const entity = await this.findOneById(id as any); // this is temporary, in the future can be refactored to perform better
if (!entity)
throw new Error(`Cannot find entity to update by a id`);
Object.assign(entity, partialEntity);
await this.persist(entity, options);
}
/**
* Removes a given entities from the database.
*/
async remove(entities: Entity[]): Promise<Entity[]>;
async remove(entities: Entity[], options?: RemoveOptions): Promise<Entity[]>;
/**
* Removes a given entity from the database.
*/
async remove(entity: Entity): Promise<Entity>;
async remove(entity: Entity, options?: RemoveOptions): Promise<Entity>;
/**
* Removes one or many given entities.
*/
async remove(entityOrEntities: Entity|Entity[]): Promise<Entity|Entity[]> {
async remove(entityOrEntities: Entity|Entity[], options?: RemoveOptions): Promise<Entity|Entity[]> {
// if for some reason non empty entity was passed then return it back without having to do anything
if (!entityOrEntities)
@ -212,6 +253,17 @@ export class Repository<Entity extends ObjectLiteral> {
}
}
/**
* Removes entity by a given entity id.
*/
async removeById(id: any, options?: RemoveOptions): Promise<void> {
const entity = await this.findOneById(id); // this is temporary, in the future can be refactored to perform better
if (!entity)
throw new Error(`Cannot find entity to remove by a given id`);
await this.remove(entity, options);
}
/**
* Counts entities that match given options.
*/
@ -220,12 +272,12 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Counts entities that match given conditions.
*/
count(conditions?: Partial<Entity>): Promise<number>;
count(conditions?: DeepPartial<Entity>): Promise<number>;
/**
* Counts entities that match given find options or conditions.
*/
count(optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<number> {
count(optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<number> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getCount();
}
@ -238,12 +290,12 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Finds entities that match given conditions.
*/
find(conditions?: Partial<Entity>): Promise<Entity[]>;
find(conditions?: DeepPartial<Entity>): Promise<Entity[]>;
/**
* Finds entities that match given find options or conditions.
*/
find(optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<Entity[]> {
find(optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<Entity[]> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getMany();
}
@ -260,14 +312,14 @@ export class Repository<Entity extends ObjectLiteral> {
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
findAndCount(conditions?: Partial<Entity>): Promise<[ Entity[], number ]>;
findAndCount(conditions?: DeepPartial<Entity>): Promise<[ Entity[], number ]>;
/**
* Finds entities that match given find options or conditions.
* Also counts all entities that match given conditions,
* but ignores pagination settings (from and take options).
*/
findAndCount(optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<[ Entity[], number ]> {
findAndCount(optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<[ Entity[], number ]> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getManyAndCount();
}
@ -282,13 +334,13 @@ export class Repository<Entity extends ObjectLiteral> {
* Finds entities by ids.
* Optionally conditions can be applied.
*/
findByIds(ids: any[], conditions?: Partial<Entity>): Promise<Entity[]>;
findByIds(ids: any[], conditions?: DeepPartial<Entity>): Promise<Entity[]>;
/**
* Finds entities by ids.
* Optionally find options can be applied.
*/
findByIds(ids: any[], optionsOrConditions?: FindManyOptions<Entity>|Partial<Entity>): Promise<Entity[]> {
findByIds(ids: any[], optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<Entity[]> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions)
.andWhereInIds(ids)
@ -303,12 +355,12 @@ export class Repository<Entity extends ObjectLiteral> {
/**
* Finds first entity that matches given conditions.
*/
findOne(conditions?: Partial<Entity>): Promise<Entity|undefined>;
findOne(conditions?: DeepPartial<Entity>): Promise<Entity|undefined>;
/**
* Finds first entity that matches given conditions.
*/
findOne(optionsOrConditions?: FindOneOptions<Entity>|Partial<Entity>): Promise<Entity|undefined> {
findOne(optionsOrConditions?: FindOneOptions<Entity>|DeepPartial<Entity>): Promise<Entity|undefined> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindOneOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindOneOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getOne();
}
@ -323,13 +375,13 @@ export class Repository<Entity extends ObjectLiteral> {
* Finds entity by given id.
* Optionally conditions can be applied.
*/
findOneById(id: any, conditions?: Partial<Entity>): Promise<Entity|undefined>;
findOneById(id: any, conditions?: DeepPartial<Entity>): Promise<Entity|undefined>;
/**
* Finds entity by given id.
* Optionally find options or conditions can be applied.
*/
findOneById(id: any, optionsOrConditions?: FindOneOptions<Entity>|Partial<Entity>): Promise<Entity|undefined> {
findOneById(id: any, optionsOrConditions?: FindOneOptions<Entity>|DeepPartial<Entity>): Promise<Entity|undefined> {
const qb = this.createQueryBuilder(FindOptionsUtils.extractFindOneOptionsAlias(optionsOrConditions) || this.metadata.table.name);
return FindOptionsUtils.applyFindOneOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions)
.andWhereInIds([id])

View File

@ -35,11 +35,11 @@ describe("persistence > one-to-many", function() {
let newCategory = categoryRepository.create();
newCategory.name = "Animals";
newCategory = await categoryRepository.persist(newCategory);
await categoryRepository.persist(newCategory);
let newPost = postRepository.create();
newPost.title = "All about animals";
newPost = await postRepository.persist(newPost);
await postRepository.persist(newPost);
newPost.categories = [newCategory];
await postRepository.persist(newPost);
@ -67,7 +67,7 @@ describe("persistence > one-to-many", function() {
let newCategory = categoryRepository.create();
newCategory.name = "Animals";
newCategory = await categoryRepository.persist(newCategory);
await categoryRepository.persist(newCategory);
let newPost = postRepository.create();
newPost.title = "All about animals";
@ -97,11 +97,11 @@ describe("persistence > one-to-many", function() {
let firstNewCategory = categoryRepository.create();
firstNewCategory.name = "Animals";
firstNewCategory = await categoryRepository.persist(firstNewCategory);
await categoryRepository.persist(firstNewCategory);
let secondNewCategory = categoryRepository.create();
secondNewCategory.name = "Insects";
secondNewCategory = await categoryRepository.persist(secondNewCategory);
await categoryRepository.persist(secondNewCategory);
let newPost = postRepository.create();
newPost.title = "All about animals";
@ -137,11 +137,11 @@ describe("persistence > one-to-many", function() {
let firstNewCategory = categoryRepository.create();
firstNewCategory.name = "Animals";
firstNewCategory = await categoryRepository.persist(firstNewCategory);
await categoryRepository.persist(firstNewCategory);
let secondNewCategory = categoryRepository.create();
secondNewCategory.name = "Insects";
secondNewCategory = await categoryRepository.persist(secondNewCategory);
await categoryRepository.persist(secondNewCategory);
let newPost = postRepository.create();
newPost.title = "All about animals";
@ -175,11 +175,11 @@ describe("persistence > one-to-many", function() {
let firstNewCategory = categoryRepository.create();
firstNewCategory.name = "Animals";
firstNewCategory = await categoryRepository.persist(firstNewCategory);
await categoryRepository.persist(firstNewCategory);
let secondNewCategory = categoryRepository.create();
secondNewCategory.name = "Insects";
secondNewCategory = await categoryRepository.persist(secondNewCategory);
await categoryRepository.persist(secondNewCategory);
let newPost = postRepository.create();
newPost.title = "All about animals";

View File

@ -0,0 +1,22 @@
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Post} from "./Post";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
position: number;
@ManyToMany(type => Post, post => post.categories)
posts: Post[];
}

View File

@ -0,0 +1,16 @@
import {EmbeddableEntity} from "../../../../../src/decorator/entity/EmbeddableEntity";
import {Column} from "../../../../../src/decorator/columns/Column";
@EmbeddableEntity()
export class Counters {
@Column()
stars: number;
@Column()
commentCount: number;
@Column()
metadata: string;
}

View File

@ -0,0 +1,31 @@
import {Category} from "./Category";
import {Entity} from "../../../../../src/decorator/entity/Entity";
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
import {Column} from "../../../../../src/decorator/columns/Column";
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
import {JoinTable} from "../../../../../src/decorator/relations/JoinTable";
import {Embedded} from "../../../../../src/decorator/Embedded";
import {Counters} from "./Counters";
@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
description: string;
@Embedded(type => Counters)
counters: Counters;
@ManyToMany(type => Category, category => category.posts, {
cascadeUpdate: true
})
@JoinTable()
categories: Category[];
}

View File

@ -0,0 +1,141 @@
import "reflect-metadata";
import {createTestingConnections, closeTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Connection} from "../../../../src/connection/Connection";
import {Post} from "./entity/Post";
import {Category} from "./entity/Category";
import {expect} from "chai";
import {Counters} from "./entity/Counters";
describe("persistence > partial persist", () => {
// -------------------------------------------------------------------------
// Configuration
// -------------------------------------------------------------------------
let connections: Connection[];
before(async () => connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchemaOnConnection: true
}));
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));
// -------------------------------------------------------------------------
// Specifications
// -------------------------------------------------------------------------
it("should persist partial entities without data loose", () => Promise.all(connections.map(async connection => {
const postRepository = connection.getRepository(Post);
const categoryRepository = connection.getRepository(Category);
// save a new category
const newCategory = new Category();
newCategory.name = "Animals";
newCategory.position = 999;
await categoryRepository.persist(newCategory);
// save a new post
const newPost = new Post();
newPost.title = "All about animals";
newPost.description = "Description of the post about animals";
newPost.categories = [newCategory];
newPost.counters = new Counters();
newPost.counters.stars = 5;
newPost.counters.commentCount = 2;
newPost.counters.metadata = "Animals Metadata";
await postRepository.persist(newPost);
// load a post
const loadedPost = await postRepository.findOneById(1, {
join: {
alias: "post",
leftJoinAndSelect: {
categories: "post.categories"
}
}
});
expect(loadedPost!).not.to.be.empty;
expect(loadedPost!.categories).not.to.be.empty;
loadedPost!.title.should.be.equal("All about animals");
loadedPost!.description.should.be.equal("Description of the post about animals");
loadedPost!.categories[0].name.should.be.equal("Animals");
loadedPost!.categories[0].position.should.be.equal(999);
loadedPost!.counters.metadata.should.be.equal("Animals Metadata");
loadedPost!.counters.stars.should.be.equal(5);
loadedPost!.counters.commentCount.should.be.equal(2);
// now update partially
await postRepository.update({ title: "All about animals" }, { title: "All about bears" });
// now check if update worked as expected, title is updated and all other columns are not touched
const loadedPostAfterTitleUpdate = await postRepository.findOneById(1, {
join: {
alias: "post",
leftJoinAndSelect: {
categories: "post.categories"
}
}
});
expect(loadedPostAfterTitleUpdate!).not.to.be.empty;
expect(loadedPostAfterTitleUpdate!.categories).not.to.be.empty;
loadedPostAfterTitleUpdate!.title.should.be.equal("All about bears");
loadedPostAfterTitleUpdate!.description.should.be.equal("Description of the post about animals");
loadedPostAfterTitleUpdate!.categories[0].name.should.be.equal("Animals");
loadedPostAfterTitleUpdate!.categories[0].position.should.be.equal(999);
loadedPostAfterTitleUpdate!.counters.metadata.should.be.equal("Animals Metadata");
loadedPostAfterTitleUpdate!.counters.stars.should.be.equal(5);
loadedPostAfterTitleUpdate!.counters.commentCount.should.be.equal(2);
// now update in partial embeddable column
await postRepository.update({ id: 1 }, { counters: { stars: 10 } });
// now check if update worked as expected, stars counter is updated and all other columns are not touched
const loadedPostAfterStarsUpdate = await postRepository.findOneById(1, {
join: {
alias: "post",
leftJoinAndSelect: {
categories: "post.categories"
}
}
});
expect(loadedPostAfterStarsUpdate!).not.to.be.empty;
expect(loadedPostAfterStarsUpdate!.categories).not.to.be.empty;
loadedPostAfterStarsUpdate!.title.should.be.equal("All about bears");
loadedPostAfterStarsUpdate!.description.should.be.equal("Description of the post about animals");
loadedPostAfterStarsUpdate!.categories[0].name.should.be.equal("Animals");
loadedPostAfterStarsUpdate!.categories[0].position.should.be.equal(999);
loadedPostAfterStarsUpdate!.counters.metadata.should.be.equal("Animals Metadata");
loadedPostAfterStarsUpdate!.counters.stars.should.be.equal(10);
loadedPostAfterStarsUpdate!.counters.commentCount.should.be.equal(2);
// now update in relational column
await postRepository.updateById(1, { categories: [{ id: 1, name: "Bears" }] });
// now check if update worked as expected, name of category is updated and all other columns are not touched
const loadedPostAfterCategoryUpdate = await postRepository.findOneById(1, {
join: {
alias: "post",
leftJoinAndSelect: {
categories: "post.categories"
}
}
});
expect(loadedPostAfterCategoryUpdate!).not.to.be.empty;
expect(loadedPostAfterCategoryUpdate!.categories).not.to.be.empty;
loadedPostAfterCategoryUpdate!.title.should.be.equal("All about bears");
loadedPostAfterCategoryUpdate!.description.should.be.equal("Description of the post about animals");
loadedPostAfterCategoryUpdate!.categories[0].name.should.be.equal("Bears");
loadedPostAfterCategoryUpdate!.categories[0].position.should.be.equal(999);
loadedPostAfterCategoryUpdate!.counters.metadata.should.be.equal("Animals Metadata");
loadedPostAfterCategoryUpdate!.counters.stars.should.be.equal(10);
loadedPostAfterCategoryUpdate!.counters.commentCount.should.be.equal(2);
})));
});

View File

@ -65,7 +65,7 @@ describe("one-to-one", function() {
newPost.text = "Hello post";
newPost.title = "this is post title";
newPost.details = details;
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -185,7 +185,7 @@ describe("one-to-one", function() {
newPost.title = "this is post title";
newPost.category = category;
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -264,13 +264,13 @@ describe("one-to-one", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
newPost.details.comment = "i am updated comment";
return postRepository.persist(newPost).then(updatedPost => {
updatedPost.details.comment.should.be.equal("i am updated comment");
updatedPost.details!.comment!.should.be.equal("i am updated comment");
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.details", "details")
@ -278,7 +278,7 @@ describe("one-to-one", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
updatedPostReloaded.details.comment.should.be.equal("this is post");
updatedPostReloaded!.details.comment.should.be.equal("this is post");
});
}); // todo: also check that updates throw exception in strict cascades mode
});
@ -302,7 +302,7 @@ describe("one-to-one", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
@ -315,7 +315,7 @@ describe("one-to-one", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
updatedPostReloaded.details.comment.should.be.equal("this is post");
updatedPostReloaded!.details.comment.should.be.equal("this is post");
});
});
});
@ -337,12 +337,12 @@ describe("one-to-one", function() {
return postImageRepository
.persist(newImage)
.then(image => {
savedImage = image;
newPost.image = image;
savedImage = image as PostImage;
newPost.image = image as PostImage;
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.image", "image")
@ -351,8 +351,8 @@ describe("one-to-one", function() {
.getOne();
}).then(loadedPost => {
loadedPost.image.url = "new-logo.png";
return postRepository.persist(loadedPost);
loadedPost!.image.url = "new-logo.png";
return postRepository.persist(loadedPost!);
}).then(() => {
return postRepository
@ -363,7 +363,7 @@ describe("one-to-one", function() {
.getOne();
}).then(reloadedPost => {
reloadedPost.image.url.should.be.equal("new-logo.png");
reloadedPost!.image.url.should.be.equal("new-logo.png");
});
});
@ -386,12 +386,12 @@ describe("one-to-one", function() {
return postMetadataRepository
.persist(newMetadata)
.then(metadata => {
savedMetadata = metadata;
newPost.metadata = metadata;
savedMetadata = metadata as PostMetadata;
newPost.metadata = metadata as PostMetadata;
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.metadata", "metadata")
@ -400,8 +400,8 @@ describe("one-to-one", function() {
.getOne();
}).then(loadedPost => {
loadedPost.metadata = null;
return postRepository.persist(loadedPost);
loadedPost!.metadata = null;
return postRepository.persist(loadedPost!);
}).then(() => {
return postRepository
@ -412,7 +412,7 @@ describe("one-to-one", function() {
.getOne();
}).then(reloadedPost => {
expect(reloadedPost.metadata).to.not.exist;
expect(reloadedPost!.metadata).to.not.exist;
});
});

View File

@ -65,7 +65,7 @@ describe("many-to-one", function() {
newPost.text = "Hello post";
newPost.title = "this is post title";
newPost.details = details;
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -188,7 +188,7 @@ describe("many-to-one", function() {
newPost.title = "this is post title";
newPost.category = category;
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -267,13 +267,13 @@ describe("many-to-one", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
newPost.details.comment = "i am updated comment";
return postRepository.persist(newPost).then(updatedPost => {
updatedPost.details.comment.should.be.equal("i am updated comment");
updatedPost.details!.comment!.should.be.equal("i am updated comment");
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.details", "details")
@ -281,7 +281,7 @@ describe("many-to-one", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
updatedPostReloaded.details.comment.should.be.equal("this is post");
updatedPostReloaded!.details.comment.should.be.equal("this is post");
});
}); // todo: also check that updates throw exception in strict cascades mode
});
@ -305,7 +305,7 @@ describe("many-to-one", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
@ -318,7 +318,7 @@ describe("many-to-one", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
updatedPostReloaded.details.comment.should.be.equal("this is post");
updatedPostReloaded!.details.comment.should.be.equal("this is post");
});
});
});
@ -340,12 +340,12 @@ describe("many-to-one", function() {
return postImageRepository
.persist(newImage)
.then(image => {
savedImage = image;
newPost.image = image;
savedImage = image as PostImage;
newPost.image = image as PostImage;
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.image", "image")
@ -354,8 +354,8 @@ describe("many-to-one", function() {
.getOne();
}).then(loadedPost => {
loadedPost.image.url = "new-logo.png";
return postRepository.persist(loadedPost);
loadedPost!.image.url = "new-logo.png";
return postRepository.persist(loadedPost!);
}).then(() => {
return postRepository
@ -366,7 +366,7 @@ describe("many-to-one", function() {
.getOne();
}).then(reloadedPost => {
reloadedPost.image.url.should.be.equal("new-logo.png");
reloadedPost!.image.url.should.be.equal("new-logo.png");
});
});
@ -389,12 +389,12 @@ describe("many-to-one", function() {
return postMetadataRepository
.persist(newMetadata)
.then(metadata => {
savedMetadata = metadata;
newPost.metadata = metadata;
savedMetadata = metadata as PostMetadata;
newPost.metadata = metadata as PostMetadata;
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.metadata", "metadata")
@ -403,8 +403,8 @@ describe("many-to-one", function() {
.getOne();
}).then(loadedPost => {
loadedPost.metadata = null;
return postRepository.persist(loadedPost);
loadedPost!.metadata = null;
return postRepository.persist(loadedPost!);
}).then(() => {
return postRepository
@ -415,7 +415,7 @@ describe("many-to-one", function() {
.getOne();
}).then(reloadedPost => {
expect(reloadedPost.metadata).to.be.empty;
expect(reloadedPost!.metadata).to.be.empty;
});
});
@ -436,7 +436,7 @@ describe("many-to-one", function() {
details.posts = [];
details.posts.push(newPost);
return postDetailsRepository.persist(details).then(details => savedDetails = details);
return postDetailsRepository.persist(details).then(details => savedDetails = details as PostDetails);
});
it("should return the same post instance after its created", function () {

View File

@ -65,7 +65,7 @@ describe("many-to-many", function() {
newPost.details = [];
newPost.details.push(details);
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -190,7 +190,7 @@ describe("many-to-many", function() {
newPost.categories = [];
newPost.categories.push(category);
return postRepository.persist(newPost).then(post => savedPost = post);
return postRepository.persist(newPost).then(post => savedPost = post as Post);
});
it("should return the same post instance after its created", function () {
@ -271,13 +271,13 @@ describe("many-to-many", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should ignore updates in the model and do not update the db when entity is updated", function () {
newPost.details[0].comment = "i am updated comment";
return postRepository.persist(newPost).then(updatedPost => {
updatedPost.details[0].comment.should.be.equal("i am updated comment");
return postRepository.persist(newPost).then((updatedPost: any) => { // temporary
updatedPost!.details![0]!.comment!.should.be.equal("i am updated comment");
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.details", "details")
@ -285,7 +285,7 @@ describe("many-to-many", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
updatedPostReloaded.details[0].comment.should.be.equal("this is post");
updatedPostReloaded!.details[0].comment.should.be.equal("this is post");
});
}); // todo: also check that updates throw exception in strict cascades mode
});
@ -310,7 +310,7 @@ describe("many-to-many", function() {
return postRepository
.persist(newPost)
.then(post => savedPost = post);
.then(post => savedPost = post as Post);
});
it("should remove relation however should not remove details itself", function () {
@ -323,7 +323,7 @@ describe("many-to-many", function() {
.setParameter("id", updatedPost.id)
.getOne();
}).then(updatedPostReloaded => {
expect(updatedPostReloaded.details).to.be.empty;
expect(updatedPostReloaded!.details).to.be.empty;
return postDetailsRepository
.createQueryBuilder("details")
@ -355,13 +355,13 @@ describe("many-to-many", function() {
return postImageRepository
.persist(newImage)
.then(image => {
savedImage = image;
savedImage = image as PostImage;
newPost.images = [];
newPost.images.push(image);
newPost.images.push(image as PostImage);
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.images", "images")
@ -370,8 +370,8 @@ describe("many-to-many", function() {
.getOne();
}).then(loadedPost => {
loadedPost.images[0].url = "new-logo.png";
return postRepository.persist(loadedPost);
loadedPost!.images[0].url = "new-logo.png";
return postRepository.persist(loadedPost!);
}).then(() => {
return postRepository
@ -382,7 +382,7 @@ describe("many-to-many", function() {
.getOne();
}).then(reloadedPost => {
reloadedPost.images[0].url.should.be.equal("new-logo.png");
reloadedPost!.images[0].url.should.be.equal("new-logo.png");
});
});
@ -405,13 +405,13 @@ describe("many-to-many", function() {
return postMetadataRepository
.persist(newMetadata)
.then(metadata => {
savedMetadata = metadata;
savedMetadata = metadata as PostMetadata;
newPost.metadatas = [];
newPost.metadatas.push(metadata);
newPost.metadatas.push(metadata as PostMetadata);
return postRepository.persist(newPost);
}).then(post => {
newPost = post;
newPost = post as Post;
return postRepository
.createQueryBuilder("post")
.leftJoinAndSelect("post.metadatas", "metadatas")
@ -420,8 +420,8 @@ describe("many-to-many", function() {
.getOne();
}).then(loadedPost => {
loadedPost.metadatas = [];
return postRepository.persist(loadedPost);
loadedPost!.metadatas = [];
return postRepository.persist(loadedPost as Post);
}).then(() => {
return postRepository
@ -432,7 +432,7 @@ describe("many-to-many", function() {
.getOne();
}).then(reloadedPost => {
expect(reloadedPost.metadatas).to.be.empty;
expect(reloadedPost!.metadatas).to.be.empty;
});
});
@ -453,7 +453,7 @@ describe("many-to-many", function() {
details.posts = [];
details.posts.push(newPost);
return postDetailsRepository.persist(details).then(details => savedDetails = details);
return postDetailsRepository.persist(details).then(details => savedDetails = details as PostDetails);
});
it("should return the same post instance after its created", function () {
@ -526,7 +526,7 @@ describe("many-to-many", function() {
return postRepository
.persist(newPost) // first save
.then(savedPost => {
savedPostId = savedPost.id;
savedPostId = (savedPost as Post).id;
savedDetailsId = details.id;
return postRepository.remove(newPost);
}); // now remove newly saved