Skip to content

Commit

Permalink
fix: get length attribute of postgres array columns (#7239)
Browse files Browse the repository at this point in the history
Change loadTables in PostgresQueryRunner to use pg_catalog to find length attribute of array columns. Previously, it relied on information_schema.columns character_maximum_length, which is null for array columns. This caused synchronize to always drop and recreate array columns.

Closes: #6990
  • Loading branch information
edcolvin committed Jan 4, 2021
1 parent f47b877 commit eb82f78
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 7 deletions.
38 changes: 31 additions & 7 deletions src/driver/postgres/PostgresQueryRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1417,12 +1417,28 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
return `("table_schema" = '${schema}' AND "table_name" = '${name}')`;
}).join(" OR ");
const tablesSql = `SELECT * FROM "information_schema"."tables" WHERE ` + tablesCondition;

/**
* Uses standard SQL information_schema.columns table and postgres-specific
* pg_catalog.pg_attribute table to get column information.
* @see https://stackoverflow.com/a/19541865
*/
const columnsSql = `
SELECT
*,
pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) as description,
('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype"
FROM "information_schema"."columns"
SELECT columns.*,
pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) AS description,
('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype",
pg_catalog.format_type("col_attr"."atttypid", "col_attr"."atttypmod") AS "format_type"
FROM "information_schema"."columns"
LEFT JOIN "pg_catalog"."pg_attribute" AS "col_attr"
ON "col_attr"."attname" = "columns"."column_name"
AND "col_attr"."attrelid" = (
SELECT
"cls"."oid" FROM "pg_catalog"."pg_class" AS "cls"
LEFT JOIN "pg_catalog"."pg_namespace" AS "ns"
ON "ns"."oid" = "cls"."relnamespace"
WHERE "cls"."relname" = "columns"."table_name"
AND "ns"."nspname" = "columns"."table_schema"
)
WHERE
` + tablesCondition;

Expand Down Expand Up @@ -1595,9 +1611,17 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner
}

// check only columns that have length property
if (this.driver.withLengthColumnTypes.indexOf(tableColumn.type as ColumnType) !== -1 && dbColumn["character_maximum_length"]) {
const length = dbColumn["character_maximum_length"].toString();
if (this.driver.withLengthColumnTypes.indexOf(tableColumn.type as ColumnType) !== -1) {
let length;
if (tableColumn.isArray) {
const match = /\((\d+)\)/.exec(dbColumn["format_type"]);
length = match ? match[1] : undefined;
} else if (dbColumn["character_maximum_length"]) {
length = dbColumn["character_maximum_length"].toString();
}
if (length) {
tableColumn.length = !this.isDefaultColumnLength(table, tableColumn, length) ? length : "";
}
}
tableColumn.isNullable = dbColumn["is_nullable"] === "YES";
tableColumn.isPrimary = !!columnConstraints.find(constraint => constraint["constraint_type"] === "PRIMARY");
Expand Down
15 changes: 15 additions & 0 deletions test/github-issues/6990/entity/foo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Column, Entity, PrimaryGeneratedColumn } from "../../../../src";

@Entity()
export class Foo {
@PrimaryGeneratedColumn()
id: number;

@Column({
array: true,
type: "varchar",
length: 64,
nullable: true,
})
varchararray: string[];
}
46 changes: 46 additions & 0 deletions test/github-issues/6990/issue-6990.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import "reflect-metadata";
import {
closeTestingConnections,
createTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils";
import { Connection } from "../../../src/connection/Connection";
import { Foo } from "./entity/foo";
import { expect } from "chai";

describe("github issues > #6990 synchronize drops array columns in postgres if a length is set", () => {
let connections: Connection[];
before(
async () =>
(connections = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
subscribers: [__dirname + "/subscriber/*{.js,.ts}"],
enabledDrivers: ["postgres"],
}))
);
beforeEach(() => reloadTestingDatabases(connections));
after(() => closeTestingConnections(connections));

it("should not drop varchar array column on synchronize using postgres driver", () =>
Promise.all(
connections.map(async function (connection) {
const foo = new Foo();
foo.id = 1;
foo.varchararray = ["able", "baker", "charlie"];
await connection.manager.save(foo);

await connection.synchronize();

const loadedFoo:
| Foo
| undefined = await connection.manager.findOne(Foo, 1);

expect(loadedFoo).to.be.not.empty;
expect(loadedFoo!.varchararray).to.deep.eq([
"able",
"baker",
"charlie",
]);
})
));
});

0 comments on commit eb82f78

Please sign in to comment.