mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
added versioning column, fixed bug when dates are not added after persistment
This commit is contained in:
parent
83e89a1352
commit
b36937c099
@ -5,7 +5,7 @@ import {CompositeIndex} from "../../../src/decorator/indices/CompositeIndex";
|
||||
|
||||
@Table("sample16_post")
|
||||
@CompositeIndex("my_index_with_id_and_text", ["id", "text"])
|
||||
@CompositeIndex(["title", "likesCount"])
|
||||
@CompositeIndex(["title", "likesCount"], { unique: true })
|
||||
@CompositeIndex((post: Post) => [post.title, post.text])
|
||||
@CompositeIndex("my_index_with_id_and_title", (post: Post) => [post.id, post.title])
|
||||
export class Post {
|
||||
|
||||
41
sample/sample17-versioning/app.ts
Normal file
41
sample/sample17-versioning/app.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import {createConnection, CreateConnectionOptions} from "../../src/typeorm";
|
||||
import {Post} from "./entity/Post";
|
||||
|
||||
const options: CreateConnectionOptions = {
|
||||
driver: "mysql",
|
||||
connection: {
|
||||
host: "192.168.99.100",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "admin",
|
||||
database: "test",
|
||||
autoSchemaCreate: true,
|
||||
logging: {
|
||||
logOnlyFailedQueries: true,
|
||||
logFailedQueryError: true
|
||||
}
|
||||
},
|
||||
entities: [Post]
|
||||
};
|
||||
|
||||
createConnection(options).then(connection => {
|
||||
|
||||
let post = new Post();
|
||||
post.text = "Hello how are you?";
|
||||
post.title = "hello";
|
||||
|
||||
let postRepository = connection.getRepository(Post);
|
||||
|
||||
postRepository
|
||||
.persist(post)
|
||||
.then(post => {
|
||||
console.log(`Post has been saved: `, post);
|
||||
console.log(`Post's version is ${post.version}. Lets change post's text and update it:`);
|
||||
post.title = "updating title";
|
||||
return postRepository.persist(post);
|
||||
|
||||
}).then(post => {
|
||||
console.log(`Post has been updated. Post's version is ${post.version}`);
|
||||
});
|
||||
|
||||
}, error => console.log("Cannot connect: ", error));
|
||||
20
sample/sample17-versioning/entity/Post.ts
Normal file
20
sample/sample17-versioning/entity/Post.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {VersionColumn} from "../../../src/decorator/columns/VersionColumn";
|
||||
|
||||
@Table("sample17_post")
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
@VersionColumn()
|
||||
version: number;
|
||||
|
||||
}
|
||||
34
src/decorator/columns/VersionColumn.ts
Normal file
34
src/decorator/columns/VersionColumn.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {ColumnOptions} from "../../metadata/options/ColumnOptions";
|
||||
import {ColumnTypes} from "../../metadata/types/ColumnTypes";
|
||||
import {defaultMetadataStorage} from "../../typeorm";
|
||||
import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
import "reflect-metadata";
|
||||
|
||||
/**
|
||||
* This column will store a number - version of the entity. Every time your entity will be persisted, this number will
|
||||
* be increased by one - so you can organize visioning and update strategies of your entity.
|
||||
*/
|
||||
export function VersionColumn(options?: ColumnOptions): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
|
||||
const reflectedType = ColumnTypes.typeToString(Reflect.getMetadata("design:type", object, propertyName));
|
||||
|
||||
// if column options are not given then create a new empty options
|
||||
if (!options) options = {} as ColumnOptions;
|
||||
|
||||
// implicitly set a type, because this column's type cannot be anything else except date
|
||||
options.type = ColumnTypes.INTEGER;
|
||||
|
||||
// todo: check if reflectedType is number too
|
||||
|
||||
// create and register a new column metadata
|
||||
defaultMetadataStorage().columnMetadatas.add(new ColumnMetadata({
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
propertyType: reflectedType,
|
||||
isVersion: true,
|
||||
options: options
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
import {CompositeIndexMetadata} from "../../metadata/CompositeIndexMetadata";
|
||||
import {defaultMetadataStorage} from "../../typeorm";
|
||||
import {CompositeIndexOptions} from "../../metadata/options/CompositeIndexOptions";
|
||||
|
||||
/**
|
||||
* Composite indexes must be set on entity classes and must specify fields to be indexed.
|
||||
*/
|
||||
export function CompositeIndex(name: string, fields: string[]): Function;
|
||||
export function CompositeIndex(fields: string[]): Function;
|
||||
export function CompositeIndex(fields: (object: any) => any[]): Function;
|
||||
export function CompositeIndex(name: string, fields: (object: any) => any[]): Function;
|
||||
export function CompositeIndex(nameOrFields: string|string[]|((object: any) => any[]), maybeFields?: ((object: any) => any[])|string[]): Function {
|
||||
export function CompositeIndex(name: string, fields: string[], options?: CompositeIndexOptions): Function;
|
||||
export function CompositeIndex(fields: string[], options?: CompositeIndexOptions): Function;
|
||||
export function CompositeIndex(fields: (object: any) => any[], options?: CompositeIndexOptions): Function;
|
||||
export function CompositeIndex(name: string, fields: (object: any) => any[], options?: CompositeIndexOptions): Function;
|
||||
export function CompositeIndex(nameOrFields: string|string[]|((object: any) => any[]),
|
||||
maybeFieldsOrOptions?: ((object: any) => any[])|CompositeIndexOptions|string[],
|
||||
maybeOptions?: CompositeIndexOptions): Function {
|
||||
const name = typeof nameOrFields === "string" ? nameOrFields : undefined;
|
||||
const fields = typeof nameOrFields === "string" ? <((object: any) => any[])|string[]> maybeFields : nameOrFields;
|
||||
const fields = typeof nameOrFields === "string" ? <((object: any) => any[])|string[]> maybeFieldsOrOptions : nameOrFields;
|
||||
const options = typeof maybeFieldsOrOptions === "object" ? <CompositeIndexOptions> maybeFieldsOrOptions : maybeOptions;
|
||||
|
||||
return function (cls: Function) {
|
||||
defaultMetadataStorage().compositeIndexMetadatas.add(new CompositeIndexMetadata(cls, name, fields));
|
||||
defaultMetadataStorage().compositeIndexMetadatas.add(new CompositeIndexMetadata(cls, name, fields, options));
|
||||
};
|
||||
}
|
||||
@ -66,6 +66,11 @@ export class ColumnMetadata extends PropertyMetadata {
|
||||
*/
|
||||
readonly isUpdateDate = false;
|
||||
|
||||
/**
|
||||
* Indicates if column will contain a version.
|
||||
*/
|
||||
readonly isVersion = false;
|
||||
|
||||
/**
|
||||
* Indicates if column will contain an updated date or not.
|
||||
*/
|
||||
@ -125,6 +130,8 @@ export class ColumnMetadata extends PropertyMetadata {
|
||||
this.isCreateDate = args.isCreateDate;
|
||||
if (args.isUpdateDate)
|
||||
this.isUpdateDate = args.isUpdateDate;
|
||||
if (args.isVersion)
|
||||
this.isVersion = args.isVersion;
|
||||
if (args.isVirtual)
|
||||
this.isVirtual = args.isVirtual;
|
||||
if (args.propertyType)
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {TargetMetadata} from "./TargetMetadata";
|
||||
import {NamingStrategyInterface} from "../naming-strategy/NamingStrategy";
|
||||
import {EntityMetadata} from "./EntityMetadata";
|
||||
import {CompositeIndexOptions} from "./options/CompositeIndexOptions";
|
||||
|
||||
/**
|
||||
* This metadata interface contains all information about table's composite index.
|
||||
@ -25,6 +26,11 @@ export class CompositeIndexMetadata extends TargetMetadata {
|
||||
// Readonly Properties
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Indicates if this index must be unique.
|
||||
*/
|
||||
readonly isUnique: boolean;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Private Properties
|
||||
// ---------------------------------------------------------------------
|
||||
@ -43,11 +49,16 @@ export class CompositeIndexMetadata extends TargetMetadata {
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
constructor(target: Function, name: string|undefined, columns: ((object: any) => any[])|string[]) {
|
||||
constructor(target: Function,
|
||||
name: string|undefined,
|
||||
columns: ((object: any) => any[])|string[],
|
||||
options?: CompositeIndexOptions) {
|
||||
super(target);
|
||||
this._columns = columns;
|
||||
if (name)
|
||||
this._name = name;
|
||||
if (options && options.unique)
|
||||
this.isUnique = options.unique;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@ -91,6 +91,10 @@ export class EntityMetadata {
|
||||
return this.columns.find(column => column.isUpdateDate);
|
||||
}
|
||||
|
||||
get versionColumn(): ColumnMetadata {
|
||||
return this.columns.find(column => column.isVersion);
|
||||
}
|
||||
|
||||
get hasPrimaryKey(): boolean {
|
||||
return !!this.primaryColumn;
|
||||
}
|
||||
|
||||
@ -40,6 +40,16 @@ export interface ColumnMetadataArgs {
|
||||
*/
|
||||
isVirtual?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if this column is version column.
|
||||
*/
|
||||
isVersion?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if this column is order id column.
|
||||
*/
|
||||
isOrderId?: boolean;
|
||||
|
||||
/**
|
||||
* Extra column options.
|
||||
*/
|
||||
|
||||
11
src/metadata/options/CompositeIndexOptions.ts
Normal file
11
src/metadata/options/CompositeIndexOptions.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Describes all composite index's options.
|
||||
*/
|
||||
export interface CompositeIndexOptions {
|
||||
|
||||
/**
|
||||
* Indicates if this composite index must be unique or not.
|
||||
*/
|
||||
unique?: boolean;
|
||||
|
||||
}
|
||||
@ -328,7 +328,7 @@ export class EntityPersistOperationBuilder {
|
||||
|
||||
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
|
||||
return metadata.columns
|
||||
.filter(column => !column.isVirtual && !column.isUpdateDate && !column.isCreateDate)
|
||||
.filter(column => !column.isVirtual && !column.isUpdateDate && !column.isVersion && !column.isCreateDate)
|
||||
.filter(column => newEntity[column.propertyName] !== dbEntity[column.propertyName]);
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ export class PersistOperationExecutor {
|
||||
.then(() => this.driver.endTransaction())
|
||||
.then(() => this.updateIdsOfInsertedEntities(persistOperation))
|
||||
.then(() => this.updateIdsOfRemovedEntities(persistOperation))
|
||||
.then(() => this.updateSpecialColumnsInEntities(persistOperation))
|
||||
.then(() => this.broadcastAfterEvents(persistOperation));
|
||||
}
|
||||
|
||||
@ -106,7 +107,7 @@ export class PersistOperationExecutor {
|
||||
*/
|
||||
private executeInsertOperations(persistOperation: PersistOperation) {
|
||||
return Promise.all(persistOperation.inserts.map(operation => {
|
||||
return this.insert(operation.entity).then((insertId: any) => {
|
||||
return this.insert(operation).then((insertId: any) => {
|
||||
operation.entityId = insertId;
|
||||
});
|
||||
}));
|
||||
@ -177,7 +178,31 @@ export class PersistOperationExecutor {
|
||||
private updateIdsOfInsertedEntities(persistOperation: PersistOperation) {
|
||||
persistOperation.inserts.forEach(insertOperation => {
|
||||
const metadata = this.entityMetadatas.findByTarget(insertOperation.entity.constructor);
|
||||
insertOperation.entity[metadata.primaryColumn.name] = insertOperation.entityId;
|
||||
insertOperation.entity[metadata.primaryColumn.propertyName] = insertOperation.entityId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all special columns of the saving entities (create date, update date, versioning).
|
||||
*/
|
||||
private updateSpecialColumnsInEntities(persistOperation: PersistOperation) {
|
||||
persistOperation.inserts.forEach(insertOperation => {
|
||||
const metadata = this.entityMetadatas.findByTarget(insertOperation.entity.constructor);
|
||||
if (metadata.updateDateColumn)
|
||||
insertOperation.entity[metadata.updateDateColumn.propertyName] = insertOperation.date;
|
||||
if (metadata.createDateColumn)
|
||||
insertOperation.entity[metadata.createDateColumn.propertyName] = insertOperation.date;
|
||||
if (metadata.versionColumn)
|
||||
insertOperation.entity[metadata.versionColumn.propertyName] = 1;
|
||||
});
|
||||
persistOperation.updates.forEach(updateOperation => {
|
||||
const metadata = this.entityMetadatas.findByTarget(updateOperation.entity.constructor);
|
||||
if (metadata.updateDateColumn)
|
||||
updateOperation.entity[metadata.updateDateColumn.propertyName] = updateOperation.date;
|
||||
if (metadata.createDateColumn)
|
||||
updateOperation.entity[metadata.createDateColumn.propertyName] = updateOperation.date;
|
||||
if (metadata.versionColumn)
|
||||
updateOperation.entity[metadata.versionColumn.propertyName]++;
|
||||
});
|
||||
}
|
||||
|
||||
@ -226,8 +251,13 @@ export class PersistOperationExecutor {
|
||||
return object;
|
||||
}, {});
|
||||
|
||||
// todo: what if number of updated columns = 0 ? + probably no need to update updated date and version columns
|
||||
|
||||
if (metadata.updateDateColumn)
|
||||
values[metadata.updateDateColumn.name] = this.driver.preparePersistentValue(new Date(), metadata.updateDateColumn);
|
||||
|
||||
if (metadata.versionColumn)
|
||||
values[metadata.versionColumn.name] = this.driver.preparePersistentValue(entity[metadata.versionColumn.propertyName] + 1, metadata.versionColumn);
|
||||
|
||||
return this.driver.update(metadata.table.name, values, { [metadata.primaryColumn.name]: metadata.getEntityId(entity) });
|
||||
}
|
||||
@ -249,7 +279,8 @@ export class PersistOperationExecutor {
|
||||
return this.driver.delete(metadata.table.name, { [metadata.primaryColumn.name]: entity[metadata.primaryColumn.propertyName] });
|
||||
}
|
||||
|
||||
private insert(entity: any) {
|
||||
private insert(operation: InsertOperation) {
|
||||
const entity = operation.entity;
|
||||
const metadata = this.entityMetadatas.findByTarget(entity.constructor);
|
||||
const columns = metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
@ -276,12 +307,17 @@ export class PersistOperationExecutor {
|
||||
|
||||
if (metadata.createDateColumn) {
|
||||
allColumns.push(metadata.createDateColumn.name);
|
||||
allValues.push(this.driver.preparePersistentValue(new Date(), metadata.createDateColumn));
|
||||
allValues.push(this.driver.preparePersistentValue(operation.date, metadata.createDateColumn));
|
||||
}
|
||||
|
||||
if (metadata.updateDateColumn) {
|
||||
allColumns.push(metadata.updateDateColumn.name);
|
||||
allValues.push(this.driver.preparePersistentValue(new Date(), metadata.updateDateColumn));
|
||||
allValues.push(this.driver.preparePersistentValue(operation.date, metadata.updateDateColumn));
|
||||
}
|
||||
|
||||
if (metadata.versionColumn) {
|
||||
allColumns.push(metadata.versionColumn.name);
|
||||
allValues.push(this.driver.preparePersistentValue(1, metadata.versionColumn));
|
||||
}
|
||||
|
||||
return this.driver.insert(metadata.table.name, this.zipObject(allColumns, allValues));
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
*/
|
||||
export class InsertOperation {
|
||||
constructor(public entity: any,
|
||||
public entityId?: number) {
|
||||
public entityId?: number,
|
||||
public date = new Date()) {
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ import {ColumnMetadata} from "../../metadata/ColumnMetadata";
|
||||
export class UpdateOperation {
|
||||
constructor(public entity: any,
|
||||
public entityId: any,
|
||||
public columns: ColumnMetadata[]) {
|
||||
public columns: ColumnMetadata[],
|
||||
public date = new Date()) {
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import {MysqlDriver} from "../driver/MysqlDriver";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {TableMetadata} from "../metadata/TableMetadata";
|
||||
import {CompositeIndexMetadata} from "../metadata/CompositeIndexMetadata";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -117,8 +118,8 @@ export class MysqlSchemaBuilder extends SchemaBuilder {
|
||||
return this.query(sql).then(() => {});
|
||||
}
|
||||
|
||||
createIndex(tableName: string, indexName: string, columns: string[]): Promise<void> {
|
||||
const sql = `CREATE INDEX \`${indexName}\` ON ${tableName}(${columns.join(", ")})`;
|
||||
createIndex(tableName: string, index: CompositeIndexMetadata): Promise<void> {
|
||||
const sql = `CREATE ${index.isUnique ? "UNIQUE" : ""} INDEX \`${index.name}\` ON ${tableName}(${index.columns.join(", ")})`;
|
||||
return this.query(sql).then(() => {});
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
import {ForeignKeyMetadata} from "../metadata/ForeignKeyMetadata";
|
||||
import {TableMetadata} from "../metadata/TableMetadata";
|
||||
import {CompositeIndexMetadata} from "../metadata/CompositeIndexMetadata";
|
||||
|
||||
/**
|
||||
* todo: make internal too (need to refactor driver).
|
||||
@ -19,7 +20,7 @@ export abstract class SchemaBuilder {
|
||||
abstract getTableIndicesQuery(tableName: string): Promise<{ key: string, sequence: number, column: string }[]>;
|
||||
abstract getPrimaryConstraintName(tableName: string): Promise<string>;
|
||||
abstract dropIndex(tableName: string, indexName: string): Promise<void>;
|
||||
abstract createIndex(tableName: string, indexName: string, columns: string[]): Promise<void>;
|
||||
abstract createIndex(tableName: string, index: CompositeIndexMetadata): Promise<void>;
|
||||
abstract addUniqueKey(tableName: string, columnName: string, keyName: string): Promise<void>;
|
||||
abstract getTableColumns(tableName: string): Promise<string[]>;
|
||||
abstract changeColumnQuery(tableName: string, columnName: string, newColumn: ColumnMetadata, skipPrimary?: boolean): Promise<void>;
|
||||
|
||||
@ -222,7 +222,7 @@ export class SchemaCreator {
|
||||
// then create table indices for all composite indices we have
|
||||
const addQueries = compositeIndices
|
||||
.filter(compositeIndex => !tableIndices.find(i => i.key === compositeIndex.name))
|
||||
.map(compositeIndex => this.schemaBuilder.createIndex(table.name, compositeIndex.name, compositeIndex.columns));
|
||||
.map(compositeIndex => this.schemaBuilder.createIndex(table.name, compositeIndex));
|
||||
|
||||
return Promise.all([dropQueries, addQueries]);
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user