diff --git a/package-lock.json b/package-lock.json index 7151d5b666..e63083f4c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3812,7 +3812,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -4562,7 +4562,7 @@ }, "is-accessor-descriptor": { "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { @@ -4627,7 +4627,7 @@ }, "is-data-descriptor": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { @@ -5370,7 +5370,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -5383,7 +5383,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -6865,7 +6865,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } diff --git a/package.json b/package.json index c5bb8ab233..4b60bfacd4 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "test": "rimraf ./build && tsc && mocha --file ./build/compiled/test/utils/test-setup.js --bail --recursive --timeout 60000 ./build/compiled/test", "test-fast": "mocha --file ./build/compiled/test/utils/test-setup.js --bail --recursive --timeout 60000 ./build/compiled/test", "compile": "rimraf ./build && tsc", + "watch": "./node_modules/.bin/tsc -w", "package": "gulp package", "lint": "tslint -p .", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 2" diff --git a/src/persistence/SubjectChangedColumnsComputer.ts b/src/persistence/SubjectChangedColumnsComputer.ts index 9a55c0386d..ab5f28ca21 100644 --- a/src/persistence/SubjectChangedColumnsComputer.ts +++ b/src/persistence/SubjectChangedColumnsComputer.ts @@ -40,10 +40,11 @@ export class SubjectChangedColumnsComputer { // ignore special columns if (column.isVirtual || - column.isDiscriminator || - column.isUpdateDate || - column.isVersion || - column.isCreateDate) + column.isDiscriminator // || + // column.isUpdateDate || + // column.isVersion || + // column.isCreateDate + ) return; const changeMap = subject.changeMaps.find(changeMap => changeMap.column === column); diff --git a/src/query-builder/InsertQueryBuilder.ts b/src/query-builder/InsertQueryBuilder.ts index 1ea9b4233c..469739ad28 100644 --- a/src/query-builder/InsertQueryBuilder.ts +++ b/src/query-builder/InsertQueryBuilder.ts @@ -422,7 +422,8 @@ export class InsertQueryBuilder extends QueryBuilder { } // newly inserted entities always have a version equal to 1 (first version) - if (column.isVersion) { + // also, user-specified version must be empty + if (column.isVersion && value === undefined) { expression += "1"; // } else if (column.isNestedSetLeft) { diff --git a/test/core/column-kinds/create-date-column/create-date-column.ts b/test/core/column-kinds/create-date-column/create-date-column.ts new file mode 100644 index 0000000000..6081b2b921 --- /dev/null +++ b/test/core/column-kinds/create-date-column/create-date-column.ts @@ -0,0 +1,96 @@ +import { expect } from "chai"; +import "reflect-metadata"; +import { Connection } from "../../../../src"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, + sleep +} from "../../../utils/test-utils"; +import { Post } from "./entity/Post"; + +describe("column kinds > create date column", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("create date column should automatically be set by a database", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // load and check if createdAt is a date (generated by db) + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.createdAt).to.be.instanceOf(Date); + }))); + + it("create date column can also be manually set by user", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + const createdAt = new Date(Date.parse("2020-01-01T00:00:00+0000")); + + // save a new post + const post = new Post(); + post.title = "Post"; + post.createdAt = createdAt; + await postRepository.save(post); + + // load and check if createdAt was a value set by us + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.createdAt).to.be.eql(createdAt); + + }))); + + it("create date column should not be updated automatically on every change", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // load to get created date we had after first save + const loadedPostBeforeUpdate = await postRepository.findOne(); + + // wait a second + await sleep(1000); + + // create post once again + post.title = "Updated Title"; + await postRepository.save(post); + + // check if date was created + const loadedPostAfterUpdate = await postRepository.findOne(); + expect(loadedPostAfterUpdate!.createdAt.toString()).to.be.eql(loadedPostBeforeUpdate!.createdAt.toString()); + }))); + + it("create date column should set a custom date when specified", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // create post once again + const createdAt = new Date(Date.parse("2020-01-01T00:00:00+0000")); + post.title = "Updated Title"; + post.createdAt = createdAt; + await postRepository.save(post); + + // check if date was created + const loadedPost = await postRepository.findOne(); + expect(loadedPost!.createdAt).to.be.eql(createdAt); + }))); +}); diff --git a/test/core/column-kinds/create-date-column/entity/Post.ts b/test/core/column-kinds/create-date-column/entity/Post.ts new file mode 100644 index 0000000000..ddc2b7baa9 --- /dev/null +++ b/test/core/column-kinds/create-date-column/entity/Post.ts @@ -0,0 +1,15 @@ +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "../../../../../src"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @CreateDateColumn() + createdAt: Date; + +} diff --git a/test/core/column-kinds/update-date-column/entity/Post.ts b/test/core/column-kinds/update-date-column/entity/Post.ts new file mode 100644 index 0000000000..2a1908e2cd --- /dev/null +++ b/test/core/column-kinds/update-date-column/entity/Post.ts @@ -0,0 +1,15 @@ +import { Column, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from "../../../../../src"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @UpdateDateColumn() + updatedAt: Date; + +} diff --git a/test/core/column-kinds/update-date-column/update-date-column.ts b/test/core/column-kinds/update-date-column/update-date-column.ts new file mode 100644 index 0000000000..1b1561c789 --- /dev/null +++ b/test/core/column-kinds/update-date-column/update-date-column.ts @@ -0,0 +1,115 @@ +import { expect } from "chai"; +import "reflect-metadata"; +import { Connection } from "../../../../src"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, + sleep +} from "../../../utils/test-utils"; +import { Post } from "./entity/Post"; + +describe("column kinds > update date column", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("update date column should automatically be set by a database", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // load and check if updatedAt is a date (generated by db) + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.updatedAt).to.be.instanceOf(Date); + }))); + + it("update column should not update if no changes were detected", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // update post once again + const loadedPost1 = await postRepository.findOneOrFail(); + await postRepository.save(loadedPost1); + + // load and check if version was a value set by us + const loadedPost2 = await postRepository.findOne(); + + // make sure version is the same + expect(loadedPost2!.title).to.be.eql("Post"); + expect(loadedPost2!.updatedAt).to.be.eql(loadedPost1.updatedAt); + }))); + + it("update date column can also be manually set by user", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + const updatedAt = new Date(Date.parse("2020-01-01T00:00:00+0000")); + + // save a new post + const post = new Post(); + post.title = "Post"; + post.updatedAt = updatedAt; + await postRepository.save(post); + + // load and check if updatedAt was a value set by us + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.updatedAt).to.be.eql(updatedAt); + }))); + + it("update date column should be updated automatically on every change", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // load to get updated date we had after first save + const loadedPostBeforeUpdate = await postRepository.findOne(); + + // wait a second + await sleep(1000); + + // update post once again + post.title = "Updated Title"; + await postRepository.save(post); + + // check if date was updated + const loadedPostAfterUpdate = await postRepository.findOne(); + expect(loadedPostAfterUpdate!.updatedAt.toString()).to.be.not.eql(loadedPostBeforeUpdate!.updatedAt.toString()); + }))); + + it("update date column should set a custom date when specified", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // update post once again + const updatedAt = new Date(Date.parse("2020-01-01T00:00:00+0000")); + post.title = "Updated Title"; + post.updatedAt = updatedAt; + await postRepository.save(post); + + // check if date was updated + const loadedPost = await postRepository.findOne(); + expect(loadedPost!.updatedAt).to.be.eql(updatedAt); + }))); +}); diff --git a/test/core/column-kinds/version-column/entity/Post.ts b/test/core/column-kinds/version-column/entity/Post.ts new file mode 100644 index 0000000000..5373c7693d --- /dev/null +++ b/test/core/column-kinds/version-column/entity/Post.ts @@ -0,0 +1,15 @@ +import { Column, Entity, PrimaryGeneratedColumn, VersionColumn } from "../../../../../src"; + +@Entity() +export class Post { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + title: string; + + @VersionColumn() + version: number; + +} diff --git a/test/core/column-kinds/version-column/version-column.ts b/test/core/column-kinds/version-column/version-column.ts new file mode 100644 index 0000000000..d3da25e465 --- /dev/null +++ b/test/core/column-kinds/version-column/version-column.ts @@ -0,0 +1,109 @@ +import { expect } from "chai"; +import "reflect-metadata"; +import { Connection } from "../../../../src"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, + sleep +} from "../../../utils/test-utils"; +import { Post } from "./entity/Post"; + +describe("column kinds > version column", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("version column should automatically be set by a database", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // load and check if version is a date (generated by db) + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.version).to.be.eql(1); + }))); + + it("version column should not update version if no changes were detected", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // update post once again + const loadedPost1 = await postRepository.findOneOrFail(); + await postRepository.save(loadedPost1); + + // load and check if version was a value set by us + const loadedPost2 = await postRepository.findOne(); + + // make sure version is the same + expect(loadedPost2!.title).to.be.eql("Post"); + expect(loadedPost2!.version).to.be.eql(1); + }))); + + it("version column can also be manually set by user", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + post.version = 5; + await postRepository.save(post); + + // load and check if version was a value set by us + const loadedPost = await postRepository.findOne(); + expect(loadedPost).to.be.not.empty; + expect(loadedPost!.title).to.be.eql("Post"); + expect(loadedPost!.version).to.be.eql(5); + }))); + + it("version column should be updated automatically on every change", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // wait a second + await sleep(1000); + + // update post once again + post.title = "Updated Title"; + await postRepository.save(post); + + // check if date was updated + const loadedPostAfterUpdate = await postRepository.findOne(); + expect(loadedPostAfterUpdate!.version).to.be.eql(2); + }))); + + it("version column should set a custom value when specified", () => Promise.all(connections.map(async connection => { + const postRepository = connection.getRepository(Post); + + // save a new post + const post = new Post(); + post.title = "Post"; + await postRepository.save(post); + + // update post once again + post.title = "Updated Title"; + post.version = 6; + await postRepository.save(post); + + // check if date was updated + const loadedPost = await postRepository.findOne(); + expect(loadedPost!.version).to.be.eql(6); + }))); +}); diff --git a/test/other-issues/mongodb-entity-change-in-subscribers/mongodb-entity-change-in-subscribers.ts b/test/other-issues/mongodb-entity-change-in-subscribers/mongodb-entity-change-in-subscribers.ts index 5dacc59257..69865b28da 100644 --- a/test/other-issues/mongodb-entity-change-in-subscribers/mongodb-entity-change-in-subscribers.ts +++ b/test/other-issues/mongodb-entity-change-in-subscribers/mongodb-entity-change-in-subscribers.ts @@ -35,7 +35,7 @@ describe("other issues > mongodb entity change in subscribers should affect pers const loadedUpdatedPost = await connection.manager.findOne(Post); expect(loadedUpdatedPost).not.to.be.undefined; expect(loadedUpdatedPost!.title).to.equals("hello world!"); - expect(loadedUpdatedPost!.updatedColumns).to.equals(3); // it actually should be 2, but ObjectId column always added + expect(loadedUpdatedPost!.updatedColumns).to.equals(4); // it actually should be 3, but ObjectId column always added await connection.manager.save(loadedPost!);