mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-26 05:04:05 -08:00
fix(core): Use consistent timezone-aware timestamps in postgres (#6948)
Fixes: * ENG-51 / N8N-2490 * PAY-397 * #2178 * #2810 * #3855 Supersedes #2813 [DB Tests](https://github.com/n8n-io/n8n/actions/runs/6000780146/job/16273596338)
This commit is contained in:
parent
ebce6fe1b0
commit
0132514f8b
|
@ -1,3 +1,4 @@
|
||||||
|
import type { ColumnOptions } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
BeforeInsert,
|
BeforeInsert,
|
||||||
BeforeUpdate,
|
BeforeUpdate,
|
||||||
|
@ -5,7 +6,6 @@ import {
|
||||||
PrimaryColumn,
|
PrimaryColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { IsDate, IsOptional } from 'class-validator';
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { generateNanoId } from '../utils/generators';
|
import { generateNanoId } from '../utils/generators';
|
||||||
|
|
||||||
|
@ -21,9 +21,10 @@ const timestampSyntax = {
|
||||||
export const jsonColumnType = dbType === 'sqlite' ? 'simple-json' : 'json';
|
export const jsonColumnType = dbType === 'sqlite' ? 'simple-json' : 'json';
|
||||||
export const datetimeColumnType = dbType === 'postgresdb' ? 'timestamptz' : 'datetime';
|
export const datetimeColumnType = dbType === 'postgresdb' ? 'timestamptz' : 'datetime';
|
||||||
|
|
||||||
const tsColumnOptions = {
|
const tsColumnOptions: ColumnOptions = {
|
||||||
precision: 3,
|
precision: 3,
|
||||||
default: () => timestampSyntax,
|
default: () => timestampSyntax,
|
||||||
|
type: datetimeColumnType,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Constructor<T> = new (...args: any[]) => T;
|
type Constructor<T> = new (...args: any[]) => T;
|
||||||
|
@ -46,16 +47,9 @@ function mixinStringId<T extends Constructor<{}>>(base: T) {
|
||||||
function mixinTimestamps<T extends Constructor<{}>>(base: T) {
|
function mixinTimestamps<T extends Constructor<{}>>(base: T) {
|
||||||
class Derived extends base {
|
class Derived extends base {
|
||||||
@CreateDateColumn(tsColumnOptions)
|
@CreateDateColumn(tsColumnOptions)
|
||||||
@IsOptional() // ignored by validation because set at DB level
|
|
||||||
@IsDate()
|
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@UpdateDateColumn({
|
@UpdateDateColumn(tsColumnOptions)
|
||||||
...tsColumnOptions,
|
|
||||||
onUpdate: timestampSyntax,
|
|
||||||
})
|
|
||||||
@IsOptional() // ignored by validation because set at DB level
|
|
||||||
@IsDate()
|
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
||||||
@BeforeUpdate()
|
@BeforeUpdate()
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import type { IrreversibleMigration, MigrationContext } from '@/databases/types';
|
||||||
|
|
||||||
|
const defaultTimestampColumns = ['createdAt', 'updatedAt'];
|
||||||
|
const tablesWithDefaultTimestamps = [
|
||||||
|
'auth_identity',
|
||||||
|
'credentials_entity',
|
||||||
|
'event_destinations',
|
||||||
|
'installed_packages',
|
||||||
|
'role',
|
||||||
|
'shared_credentials',
|
||||||
|
'shared_workflow',
|
||||||
|
'tag_entity',
|
||||||
|
'user',
|
||||||
|
'workflow_entity',
|
||||||
|
];
|
||||||
|
|
||||||
|
const additionalColumns = {
|
||||||
|
auth_provider_sync_history: ['endedAt', 'startedAt'],
|
||||||
|
execution_entity: ['startedAt', 'stoppedAt', 'waitTill'],
|
||||||
|
workflow_statistics: ['latestEvent'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export class MigrateToTimestampTz1694091729095 implements IrreversibleMigration {
|
||||||
|
async up({ queryRunner, tablePrefix }: MigrationContext) {
|
||||||
|
const changeColumnType = async (tableName: string, columnName: string, setDefault: boolean) => {
|
||||||
|
const alterColumnQuery = `ALTER TABLE "${tablePrefix}${tableName}" ALTER COLUMN "${columnName}"`;
|
||||||
|
await queryRunner.query(`${alterColumnQuery} TYPE TIMESTAMP(3) WITH TIME ZONE`);
|
||||||
|
if (setDefault)
|
||||||
|
await queryRunner.query(`${alterColumnQuery} SET DEFAULT CURRENT_TIMESTAMP(3)`);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const tableName of tablesWithDefaultTimestamps) {
|
||||||
|
for (const columnName of defaultTimestampColumns) {
|
||||||
|
await changeColumnType(tableName, columnName, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [tableName, columnNames] of Object.entries(additionalColumns)) {
|
||||||
|
for (const columnName of columnNames) {
|
||||||
|
await changeColumnType(tableName, columnName, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,6 +47,7 @@ import { CreateWorkflowHistoryTable1692967111175 } from '../common/1692967111175
|
||||||
import { DisallowOrphanExecutions1693554410387 } from '../common/1693554410387-DisallowOrphanExecutions';
|
import { DisallowOrphanExecutions1693554410387 } from '../common/1693554410387-DisallowOrphanExecutions';
|
||||||
import { ExecutionSoftDelete1693491613982 } from '../common/1693491613982-ExecutionSoftDelete';
|
import { ExecutionSoftDelete1693491613982 } from '../common/1693491613982-ExecutionSoftDelete';
|
||||||
import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata';
|
import { AddWorkflowMetadata1695128658538 } from '../common/1695128658538-AddWorkflowMetadata';
|
||||||
|
import { MigrateToTimestampTz1694091729095 } from './1694091729095-MigrateToTimestampTz';
|
||||||
|
|
||||||
export const postgresMigrations: Migration[] = [
|
export const postgresMigrations: Migration[] = [
|
||||||
InitialMigration1587669153312,
|
InitialMigration1587669153312,
|
||||||
|
@ -97,4 +98,5 @@ export const postgresMigrations: Migration[] = [
|
||||||
DisallowOrphanExecutions1693554410387,
|
DisallowOrphanExecutions1693554410387,
|
||||||
ExecutionSoftDelete1693491613982,
|
ExecutionSoftDelete1693491613982,
|
||||||
AddWorkflowMetadata1695128658538,
|
AddWorkflowMetadata1695128658538,
|
||||||
|
MigrateToTimestampTz1694091729095,
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue