Skip to content

Commit

Permalink
Merge pull request #3537 from dacruz21/master
Browse files Browse the repository at this point in the history
Add Postgres connection option to use the pgcrypto extension to generate UUIDs
  • Loading branch information
pleerock committed Feb 27, 2019
2 parents 53d03cd + b96aad8 commit 122601b
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 9 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ however since API is already quite stable we don't expect too much breaking chan
If we missed a note on some change or you have a questions on migrating from old version,
feel free to ask us and community.

## 0.2.15

### Bug fixes

### Features

* added `uuidExtension` option to Postgres connection options, which allows TypeORM to use the newer `pgcrypto` extension to generate UUIDs

## 0.2.14 (2019-02-25)

### Bug fixes
Expand Down
2 changes: 2 additions & 0 deletions docs/connection-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ See [SSL options](https://github.com/mysqljs/mysql#ssl-options).

* `ssl` - Object with ssl parameters. See [TLS/SSL](https://node-postgres.com/features/ssl).

* `uuidExtension` - The Postgres extension to use when generating UUIDs. Defaults to `uuid-ossp`. Can be changed to `pgcrypto` if the `uuid-ossp` extension is unavailable.

## `sqlite` connection options

* `database` - Database path. For example "./mydb.sql"
Expand Down
3 changes: 2 additions & 1 deletion src/connection/options-reader/ConnectionOptionsEnvReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export class ConnectionOptionsEnvReader {
migrationsDir: PlatformTools.getEnvVariable("TYPEORM_MIGRATIONS_DIR"),
subscribersDir: PlatformTools.getEnvVariable("TYPEORM_SUBSCRIBERS_DIR"),
},
cache: this.transformCaching()
cache: this.transformCaching(),
uuidExtension: PlatformTools.getEnvVariable("TYPEORM_UUID_EXTENSION")
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/driver/postgres/PostgresConnectionOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ export interface PostgresConnectionOptions extends BaseConnectionOptions, Postgr

};

}
/**
* The Postgres extension to use to generate UUID columns. Defaults to uuid-ossp.
* If pgcrypto is selected, TypeORM will use the gen_random_uuid() function from this extension.
* If uuid-ossp is selected, TypeORM will use the uuid_generate_v4() function from this extension.
*/
readonly uuidExtension?: "pgcrypto" | "uuid-ossp";
}
8 changes: 6 additions & 2 deletions src/driver/postgres/PostgresDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,9 @@ export class PostgresDriver implements Driver {
if (err) return fail(err);
if (hasUuidColumns)
try {
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "${this.options.uuidExtension || "uuid-ossp"}"`);
} catch (_) {
logger.log("warn", "At least one of the entities has uuid column, but the 'uuid-ossp' extension cannot be installed automatically. Please install it manually using superuser rights");
logger.log("warn", `At least one of the entities has uuid column, but the '${this.options.uuidExtension || "uuid-ossp"}' extension cannot be installed automatically. Please install it manually using superuser rights, or select another uuid extension.`);
}
if (hasCitextColumns)
try {
Expand Down Expand Up @@ -805,6 +805,10 @@ export class PostgresDriver implements Driver {
return true;
}

get uuidGenerator(): string {
return this.options.uuidExtension === "pgcrypto" ? "gen_random_uuid()" : "uuid_generate_v4()";
}

/**
* Creates an escaped parameter.
*/
Expand Down
6 changes: 2 additions & 4 deletions src/driver/postgres/PostgresQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1473,11 +1473,9 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
if (dbColumn["column_default"].replace(/"/gi, "") === `nextval('${this.buildSequenceName(table, dbColumn["column_name"], currentSchema, true)}'::regclass)`) {
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "increment";

} else if (/^uuid_generate_v\d\(\)/.test(dbColumn["column_default"])) {
} else if (dbColumn["column_default"] === "gen_random_uuid()" || /^uuid_generate_v\d\(\)/.test(dbColumn["column_default"])) {
tableColumn.isGenerated = true;
tableColumn.generationStrategy = "uuid";

} else {
tableColumn.default = dbColumn["column_default"].replace(/::.*/, "");
}
Expand Down Expand Up @@ -1916,7 +1914,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
if (column.default !== undefined && column.default !== null)
c += " DEFAULT " + column.default;
if (column.isGenerated && column.generationStrategy === "uuid" && !column.default)
c += " DEFAULT uuid_generate_v4()";
c += ` DEFAULT ${this.driver.uuidGenerator}`;

return c;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import {closeTestingConnections, createTestingConnections, reloadTestingDatabase
import {Post} from "./entity/Post";
import {Question} from "./entity/Question";

describe("uuid-postgres", () => {
describe("pgcrypto", () => {

let connections: Connection[];
before(async () => {
connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["postgres"],
driverSpecific: {
uuidExtension: "pgcrypto"
}
});
});
beforeEach(() => reloadTestingDatabases(connections));
Expand Down
98 changes: 98 additions & 0 deletions test/functional/uuid/postgres/uuid-ossp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import "reflect-metadata";
import {expect} from "chai";
import {Record} from "./entity/Record";
import {Connection} from "../../../../src/connection/Connection";
import {closeTestingConnections, createTestingConnections, reloadTestingDatabases} from "../../../utils/test-utils";
import {Post} from "./entity/Post";
import {Question} from "./entity/Question";

describe("uuid-ossp", () => {

let connections: Connection[];
before(async () => {
connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
enabledDrivers: ["postgres"],
driverSpecific: {
uuidExtension: "uuid-ossp"
}
});
});
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should make correct schema with Postgres' uuid type", () => Promise.all(connections.map(async connection => {
const queryRunner = connection.createQueryRunner();
const schema = await queryRunner.getTable("record");
await queryRunner.release();
expect(schema).not.to.be.empty;
expect(schema!.columns.find(tableColumn => tableColumn.name === "id" && tableColumn.type === "uuid" && tableColumn.isGenerated)).to.be.not.empty;
})));

it("should persist uuid correctly", () => Promise.all(connections.map(async connection => {
const recordRepo = connection.getRepository(Record);
const record = new Record();
record.id = "fd357b8f-8838-42f6-b7a2-ae027444e895";
const persistedRecord = await recordRepo.save(record);
const foundRecord = await recordRepo.findOne(persistedRecord.id);
expect(foundRecord).to.be.exist;
expect(foundRecord!.id).to.eq("fd357b8f-8838-42f6-b7a2-ae027444e895");
})));

it("should persist uuid correctly when it is generated non primary column", () => Promise.all(connections.map(async connection => {

const postRepository = connection.getRepository(Post);
const questionRepository = connection.getRepository(Question);
const queryRunner = connection.createQueryRunner();
const postTable = await queryRunner.getTable("post");
const questionTable = await queryRunner.getTable("question");
await queryRunner.release();

const post = new Post();
await postRepository.save(post);
const loadedPost = await postRepository.findOne(1);
expect(loadedPost!.uuid).to.be.exist;
postTable!.findColumnByName("uuid")!.type.should.be.equal("uuid");

const post2 = new Post();
post2.uuid = "fd357b8f-8838-42f6-b7a2-ae027444e895";
await postRepository.save(post2);
const loadedPost2 = await postRepository.findOne(2);
expect(loadedPost2!.uuid).to.equal("fd357b8f-8838-42f6-b7a2-ae027444e895");

const question = new Question();
question.uuid2 = "fd357b8f-8838-42f6-b7a2-ae027444e895";

const savedQuestion = await questionRepository.save(question);
expect(savedQuestion!.id).to.be.exist;
expect(savedQuestion!.uuid).to.be.exist;
expect(savedQuestion!.uuid2).to.equal("fd357b8f-8838-42f6-b7a2-ae027444e895");
expect(savedQuestion!.uuid3).to.be.null;
expect(savedQuestion!.uuid4).to.be.exist;

const loadedQuestion = await questionRepository.findOne(savedQuestion.id);
expect(loadedQuestion!.id).to.be.exist;
expect(loadedQuestion!.uuid).to.be.exist;
expect(loadedQuestion!.uuid2).to.equal("fd357b8f-8838-42f6-b7a2-ae027444e895");
expect(loadedQuestion!.uuid3).to.be.null;
expect(loadedQuestion!.uuid4).to.be.exist;
questionTable!.findColumnByName("id")!.type.should.be.equal("uuid");
questionTable!.findColumnByName("uuid")!.type.should.be.equal("uuid");
questionTable!.findColumnByName("uuid2")!.type.should.be.equal("uuid");
questionTable!.findColumnByName("uuid3")!.type.should.be.equal("uuid");

const question2 = new Question();
question2.id = "1ecad7f6-23ee-453e-bb44-16eca26d5189";
question2.uuid = "35b44650-b2cd-44ec-aa54-137fbdf1c373";
question2.uuid2 = "fd357b8f-8838-42f6-b7a2-ae027444e895";
question2.uuid3 = null;
question2.uuid4 = null;
await questionRepository.save(question2);
const loadedQuestion2 = await questionRepository.findOne("1ecad7f6-23ee-453e-bb44-16eca26d5189");
expect(loadedQuestion2!.id).to.equal("1ecad7f6-23ee-453e-bb44-16eca26d5189");
expect(loadedQuestion2!.uuid).to.equal("35b44650-b2cd-44ec-aa54-137fbdf1c373");
expect(loadedQuestion2!.uuid2).to.equal("fd357b8f-8838-42f6-b7a2-ae027444e895");
expect(loadedQuestion2!.uuid3).to.be.null;
expect(loadedQuestion2!.uuid4).to.be.null;
})));
});

0 comments on commit 122601b

Please sign in to comment.