Skip to content

Commit

Permalink
feat: add support for Postgres 10+ GENERATED ALWAYS AS IDENTITY
Browse files Browse the repository at this point in the history
allow developers to create a PrimaryGeneratedColumn of identity
and choose between `ALWAYS` and `BY DEFAULT`.

Closes: typeorm#8370
  • Loading branch information
leoromanovsky committed Nov 22, 2021
1 parent 0334d10 commit 63b64f0
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/decorator-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export class User {
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`
* `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` together with the `generatedIdentity` option the column will be produced using `GENERATED [ALWAYS|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.
Expand Down
5 changes: 3 additions & 2 deletions src/decorator/columns/PrimaryGeneratedColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {PrimaryGeneratedColumnNumericOptions} from "../options/PrimaryGeneratedC
import {PrimaryGeneratedColumnUUIDOptions} from "../options/PrimaryGeneratedColumnUUIDOptions";
import {GeneratedMetadataArgs} from "../../metadata-args/GeneratedMetadataArgs";
import { ColumnOptions } from "../options/ColumnOptions";
import { PrimaryGeneratedColumnIdentityOptions } from "../options/PrimaryGeneratedColumnIdentityOptions";

/**
* Column decorator is used to mark a specific class property as a table column.
Expand All @@ -29,15 +30,15 @@ export function PrimaryGeneratedColumn(strategy: "uuid", options?: PrimaryGenera
*/
export function PrimaryGeneratedColumn(strategy: "rowid", options?: PrimaryGeneratedColumnUUIDOptions): PropertyDecorator;

export function PrimaryGeneratedColumn(strategy: "identity", options?: PrimaryGeneratedColumnUUIDOptions): PropertyDecorator;
export function PrimaryGeneratedColumn(strategy: "identity", options?: PrimaryGeneratedColumnIdentityOptions): 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"|"identity"|PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions,
maybeOptions?: PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions): PropertyDecorator {
maybeOptions?: PrimaryGeneratedColumnNumericOptions|PrimaryGeneratedColumnUUIDOptions|PrimaryGeneratedColumnIdentityOptions): PropertyDecorator {

// normalize parameters
const options: ColumnOptions = {};
Expand Down
5 changes: 5 additions & 0 deletions src/decorator/options/ColumnOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ export interface ColumnOptions extends ColumnCommonOptions {
*/
generatedType?: "VIRTUAL"|"STORED";

/**
* Identity column type. Supported only in Postgres 10+.
*/
generatedIdentity?: "ALWAYS"|"BY DEFAULT";

/**
* Return type of HSTORE column.
* Returns value as string or as object.
Expand Down
27 changes: 27 additions & 0 deletions src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {PrimaryGeneratedColumnType} from "../../driver/types/ColumnTypes";

/**
* Describes all options for PrimaryGeneratedColumn decorator with identity generation strategy.
*/
export interface PrimaryGeneratedColumnIdentityOptions {

/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: PrimaryGeneratedColumnType;

/**
* Column name in the database.
*/
name?: string;

/**
* Column comment. Not supported by all database types.
*/
comment?: string;

/**
* Identity column type. Supports only in Postgres 10+.
*/
generatedIdentity?: "ALWAYS"|"BY DEFAULT";
}
4 changes: 3 additions & 1 deletion src/driver/postgres/PostgresQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
if (dbColumn.is_identity === "YES") { // Postgres 10+ Identity column
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "identity";
tableColumn.generatedIdentity = dbColumn.identity_generation;
} 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)`;
Expand Down Expand Up @@ -2334,7 +2335,8 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
let c = "\"" + column.name + "\"";
if (column.isGenerated === true && column.generationStrategy !== "uuid") {
if (column.generationStrategy === "identity") { // Postgres 10+ Identity generated column
c += ` ${column.type} GENERATED BY DEFAULT AS IDENTITY`;
const generatedIdentityOrDefault = column.generatedIdentity || "BY DEFAULT";
c += ` ${column.type} GENERATED ${generatedIdentityOrDefault} AS IDENTITY`;
} else { // classic SERIAL primary column
if (column.type === "integer" || column.type === "int" || column.type === "int4")
c += " SERIAL";
Expand Down
5 changes: 5 additions & 0 deletions src/schema-builder/table/TableColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ export class TableColumn {
*/
generationStrategy?: "uuid"|"increment"|"rowid"|"identity";

/**
* Identity column type. Supported only in Postgres 10+.
*/
generatedIdentity?: "ALWAYS"|"BY DEFAULT";

/**
* Indicates if column is a primary key.
*/
Expand Down
30 changes: 30 additions & 0 deletions test/github-issues/8370/entity/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src";

@Entity()
export class User {
// yest primary generated column
@PrimaryGeneratedColumn("identity", {
generatedIdentity: "ALWAYS",
})
id: number;

// test generatedIdentity=ALWAYS
@Column({
type: "bigint",
generated: "identity",
generatedIdentity: "ALWAYS",
})
secondId: number;

// test generatedIdentity=BY DEFAULT
@Column({
type: "int",
generated: "identity",
generatedIdentity: "BY DEFAULT",
})
thirdId: number;

// test default behavior of `generatedIdentity`
@Column({ type: "int", generated: "identity" })
fourthId: number;
}
59 changes: 59 additions & 0 deletions test/github-issues/8370/issue-8370.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import "reflect-metadata";
import {
createTestingConnections,
closeTestingConnections,
} from "../../utils/test-utils";
import { Connection } from "../../../src";
import { User } from "./entity/User";

import { expect } from "chai";

describe("github issues > #8370 Add support for Postgres GENERATED ALWAYS AS IDENTITY", () => {
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 identity columns", () =>
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.contain(
`"id" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL`
);

expect(sqlInMemory.upQueries[0])
.to.have.property("query")
.that.contain(
`"secondId" bigint GENERATED ALWAYS AS IDENTITY NOT NULL`
);

expect(sqlInMemory.upQueries[0])
.to.have.property("query")
.that.contain(
`"thirdId" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL`
);

expect(sqlInMemory.upQueries[0])
.to.have.property("query")
.that.contain(
`"fourthId" integer GENERATED BY DEFAULT AS IDENTITY NOT NULL`
);
})
));
});

0 comments on commit 63b64f0

Please sign in to comment.