diff --git a/docs/decorator-reference.md b/docs/decorator-reference.md index 97fbeaac238..3d7370613aa 100644 --- a/docs/decorator-reference.md +++ b/docs/decorator-reference.md @@ -237,9 +237,10 @@ export class User { } ``` -There are two generation strategies: +There are four generation strategies: * `increment` - uses AUTO_INCREMENT / SERIAL / SEQUENCE (depend on database type) to generate incremental number. +* `identity` - only for [PostgreSQL 10+](https://www.postgresql.org/docs/13/sql-createtable.html). Postgres versions above 10 support the SQL-Compliant **IDENTITY** column. When marking the generation strategy as `identity` the column will be produced using `GENERATED BY DEFAULT AS IDENTITY` * `uuid` - generates unique `uuid` string. * `rowid` - only for [CockroachDB](https://www.cockroachlabs.com/docs/stable/serial.html). Value is automatically generated using the `unique_rowid()` function. This produces a 64-bit integer from the current timestamp and ID of the node executing the `INSERT` or `UPSERT` operation. diff --git a/docs/entities.md b/docs/entities.md index 848309696e9..bf70eb0a6c5 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -125,7 +125,7 @@ export class User { } ``` -* `@PrimaryGeneratedColumn()` creates a primary column which value will be automatically generated with an auto-increment value. It will create `int` column with `auto-increment`/`serial`/`sequence` (depend on the database). You don't have to manually assign its value before save - value will be automatically generated. +* `@PrimaryGeneratedColumn()` creates a primary column which value will be automatically generated with an auto-increment value. It will create `int` column with `auto-increment`/`serial`/`sequence`/`identity` (depend on the database and configuration provided). You don't have to manually assign its value before save - value will be automatically generated. ```typescript import {Entity, PrimaryGeneratedColumn} from "typeorm"; @@ -196,13 +196,13 @@ There are several special column types with additional functionality available: * `@CreateDateColumn` is a special column that is automatically set to the entity's insertion date. You don't need to set this column - it will be automatically set. -* `@UpdateDateColumn` is a special column that is automatically set to the entity's update time +* `@UpdateDateColumn` is a special column that is automatically set to the entity's update time each time you call `save` of entity manager or repository. You don't need to set this column - it will be automatically set. * `@DeleteDateColumn` is a special column that is automatically set to the entity's delete time each time you call soft-delete of entity manager or repository. You don't need to set this column - it will be automatically set. If the @DeleteDateColumn is set, the default scope will be "non-deleted". -* `@VersionColumn` is a special column that is automatically set to the version of the entity (incremental number) +* `@VersionColumn` is a special column that is automatically set to the version of the entity (incremental number) each time you call `save` of entity manager or repository. You don't need to set this column - it will be automatically set. @@ -524,7 +524,7 @@ export class User { `uuid` value will be automatically generated and stored into the database. -Besides "uuid" there is also "increment" and "rowid" (CockroachDB only) generated types, however there are some limitations +Besides "uuid" there is also "increment", "identity" (Postgres 10+ only) and "rowid" (CockroachDB only) generated types, however there are some limitations on some database platforms with this type of generation (for example some databases can only have one increment column, or some of them require increment to be a primary key). diff --git a/package-lock.json b/package-lock.json index c694f496270..b7e52474cad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typeorm-legacy-mssql", - "version": "0.2.38", + "version": "0.2.39", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typeorm-legacy-mssql", - "version": "0.2.38", + "version": "0.2.39", "license": "MIT", "dependencies": { "@sqltools/formatter": "^1.2.2", diff --git a/src/decorator/columns/PrimaryGeneratedColumn.ts b/src/decorator/columns/PrimaryGeneratedColumn.ts index 048a626be0a..d69cf03d0f1 100644 --- a/src/decorator/columns/PrimaryGeneratedColumn.ts +++ b/src/decorator/columns/PrimaryGeneratedColumn.ts @@ -29,20 +29,22 @@ export function PrimaryGeneratedColumn(strategy: "uuid", options?: PrimaryGenera */ export function PrimaryGeneratedColumn(strategy: "rowid", options?: PrimaryGeneratedColumnUUIDOptions): PropertyDecorator; +export function PrimaryGeneratedColumn(strategy: "identity", options?: PrimaryGeneratedColumnUUIDOptions): PropertyDecorator; + /** * 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. * This column creates an integer PRIMARY COLUMN with generated set to true. */ -export function PrimaryGeneratedColumn(strategyOrOptions?: "increment"|"uuid"|"rowid"|PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions, +export function PrimaryGeneratedColumn(strategyOrOptions?: "increment"|"uuid"|"rowid"|"identity"|PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions, maybeOptions?: PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions): PropertyDecorator { // normalize parameters const options: ColumnOptions = {}; - let strategy: "increment"|"uuid"|"rowid"; + let strategy: "increment"|"uuid"|"rowid"|"identity"; if (strategyOrOptions) { if (typeof strategyOrOptions === "string") - strategy = strategyOrOptions as "increment"|"uuid"|"rowid"; + strategy = strategyOrOptions as "increment"|"uuid"|"rowid"|"identity"; if (strategyOrOptions instanceof Object) { strategy = "increment"; @@ -58,7 +60,7 @@ export function PrimaryGeneratedColumn(strategyOrOptions?: "increment"|"uuid"|"r // if column type is not explicitly set then determine it based on generation strategy if (!options.type) { - if (strategy === "increment") { + if (strategy === "increment" || strategy === "identity") { options.type = Number; } else if (strategy === "uuid") { options.type = "uuid"; diff --git a/src/decorator/options/ColumnCommonOptions.ts b/src/decorator/options/ColumnCommonOptions.ts index 5f3a6c8dc97..b9bf53c1d68 100644 --- a/src/decorator/options/ColumnCommonOptions.ts +++ b/src/decorator/options/ColumnCommonOptions.ts @@ -26,7 +26,7 @@ export interface ColumnCommonOptions { * Specifies if this column will use auto increment (sequence, generated identity, rowid). * Note that in some databases only one column in entity can be marked as generated, and it must be a primary column. */ - generated?: boolean|"increment"|"uuid"|"rowid"; + generated?: boolean|"increment"|"uuid"|"rowid"|"identity"; /** * Specifies if column's value must be unique or not. diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index 4f37b3a9f68..a0e5677e8c9 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -957,7 +957,10 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner } } - if (oldColumn.isGenerated !== newColumn.isGenerated && newColumn.generationStrategy !== "uuid") { + if (oldColumn.isGenerated !== newColumn.isGenerated && + newColumn.generationStrategy !== "uuid" && + newColumn.generationStrategy !== "identity" + ) { if (newColumn.isGenerated === true) { upQueries.push(new Query(`CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(this.buildSequencePath(table, newColumn))} OWNED BY ${this.escapePath(table)}."${newColumn.name}"`)); downQueries.push(new Query(`DROP SEQUENCE ${this.escapePath(this.buildSequencePath(table, newColumn))}`)); @@ -1779,7 +1782,10 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner : false; tableColumn.isUnique = !!uniqueConstraint && !isConstraintComposite; - if (dbColumn["column_default"] !== null && dbColumn["column_default"] !== undefined) { + if (dbColumn.is_identity === "YES") { // Postgres 10+ Identity column + tableColumn.isGenerated = true; + tableColumn.generationStrategy = "identity"; + } else if (dbColumn["column_default"] !== null && dbColumn["column_default"] !== undefined) { const serialDefaultName = `nextval('${this.buildSequenceName(table, dbColumn["column_name"])}'::regclass)`; const serialDefaultPath = `nextval('${this.buildSequencePath(table, dbColumn["column_name"])}'::regclass)`; @@ -2327,12 +2333,16 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner protected buildCreateColumnSql(table: Table, column: TableColumn) { let c = "\"" + column.name + "\""; if (column.isGenerated === true && column.generationStrategy !== "uuid") { - if (column.type === "integer" || column.type === "int" || column.type === "int4") - c += " SERIAL"; - if (column.type === "smallint" || column.type === "int2") - c += " SMALLSERIAL"; - if (column.type === "bigint" || column.type === "int8") - c += " BIGSERIAL"; + if (column.generationStrategy === "identity") { // Postgres 10+ Identity generated column + c += ` ${column.type} GENERATED BY DEFAULT AS IDENTITY`; + } else { // classic SERIAL primary column + if (column.type === "integer" || column.type === "int" || column.type === "int4") + c += " SERIAL"; + if (column.type === "smallint" || column.type === "int2") + c += " SMALLSERIAL"; + if (column.type === "bigint" || column.type === "int8") + c += " BIGSERIAL"; + } } if (column.type === "enum" || column.type === "simple-enum") { c += " " + this.buildEnumName(table, column); diff --git a/src/schema-builder/options/TableColumnOptions.ts b/src/schema-builder/options/TableColumnOptions.ts index 323b2c44854..d53b3dbcf5a 100644 --- a/src/schema-builder/options/TableColumnOptions.ts +++ b/src/schema-builder/options/TableColumnOptions.ts @@ -41,7 +41,7 @@ export interface TableColumnOptions { /** * Specifies generation strategy if this column will use auto increment. */ - generationStrategy?: "uuid"|"increment"|"rowid"; + generationStrategy?: "uuid"|"increment"|"rowid"|"identity"; /** * Indicates if column is a primary key. diff --git a/src/schema-builder/table/TableColumn.ts b/src/schema-builder/table/TableColumn.ts index d7e342b19a2..6af900838da 100644 --- a/src/schema-builder/table/TableColumn.ts +++ b/src/schema-builder/table/TableColumn.ts @@ -43,7 +43,7 @@ export class TableColumn { * Specifies generation strategy if this column will use auto increment. * `rowid` option supported only in CockroachDB. */ - generationStrategy?: "uuid"|"increment"|"rowid"; + generationStrategy?: "uuid"|"increment"|"rowid"|"identity"; /** * Indicates if column is a primary key. diff --git a/test/github-issues/5365/entity/UserEntity.ts b/test/github-issues/5365/entity/UserEntity.ts new file mode 100644 index 00000000000..86c94edcdde --- /dev/null +++ b/test/github-issues/5365/entity/UserEntity.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../src"; + +@Entity() +export class User { + @PrimaryGeneratedColumn("identity") + id!: number; +} diff --git a/test/github-issues/5365/issue-5365.ts b/test/github-issues/5365/issue-5365.ts new file mode 100644 index 00000000000..7b970e9602c --- /dev/null +++ b/test/github-issues/5365/issue-5365.ts @@ -0,0 +1,24 @@ +import "reflect-metadata"; +import { createTestingConnections, closeTestingConnections } from "../../utils/test-utils"; +import { Connection } from "../../../src"; +import { User } from "./entity/UserEntity"; + +import { expect } from "chai"; + +describe("github issues > #5365 Generated Identity for Postgres 10+", () => { + + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + entities: [User], + schemaCreate: false, + dropSchema: true, + enabledDrivers: ["postgres"], + })); + after(() => closeTestingConnections(connections)); + it("should produce proper SQL for creating a table with identity column", () => Promise.all(connections.map(async connection => { + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + expect(sqlInMemory).to.have.property("upQueries").that.is.an("array").and.has.length(1); + expect(sqlInMemory.upQueries[0]).to.have.property("query").that + .eql(`CREATE TABLE "user" ("id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`); + }))); +});