diff --git a/.code-health-baseline.json b/.code-health-baseline.json
index 4fd32e04709..ad0ed460005 100644
--- a/.code-health-baseline.json
+++ b/.code-health-baseline.json
@@ -1,7 +1,7 @@
{
"version": 1,
"generated": "2026-06-04T06:55:45.363Z",
- "totalViolations": 148,
+ "totalViolations": 159,
"violations": {
"packages/@n8n/ai-workflow-builder.ee/package.json": [
{
@@ -1058,6 +1058,74 @@
"message": "Override \"langsmith\" duplicates a catalog entry for langsmith — set the override to \"catalog:\" or remove it",
"hash": "170e854b445c"
}
+ ],
+ "packages/@n8n/typeorm/package.json": [
+ {
+ "rule": "catalog-violations",
+ "line": 118,
+ "message": "reflect-metadata@^0.2.2 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "558f12c2dd9c"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 122,
+ "message": "uuid@^9.0.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "0304587b7acc"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 67,
+ "message": "@types/node@^18.13.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "0df18a02f2d0"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 72,
+ "message": "@types/uuid@^9.0.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "0bcbecb362c9"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 76,
+ "message": "eslint@^8.44.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "0036144a407d"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 78,
+ "message": "pg@^8.17.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "6cac348bdcc8"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 80,
+ "message": "prettier@^2.8.3 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "869ee9ce64bc"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 81,
+ "message": "rimraf@^4.1.2 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "48f117898c57"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 84,
+ "message": "source-map-support@^0.5.21 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "c27bcf7b8b06"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 87,
+ "message": "typescript@^5.3.3 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "050d5b2d74e1"
+ },
+ {
+ "rule": "catalog-violations",
+ "line": 90,
+ "message": "pg@^8.17.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
+ "hash": "6cac348bdcc8"
+ }
]
}
}
diff --git a/packages/@n8n/backend-test-utils/package.json b/packages/@n8n/backend-test-utils/package.json
index a0f1694002f..a48a1b06adc 100644
--- a/packages/@n8n/backend-test-utils/package.json
+++ b/packages/@n8n/backend-test-utils/package.json
@@ -28,7 +28,7 @@
"@n8n/db": "workspace:*",
"@n8n/di": "workspace:*",
"@n8n/permissions": "workspace:*",
- "@n8n/typeorm": "catalog:",
+ "@n8n/typeorm": "workspace:*",
"n8n-workflow": "workspace:*",
"reflect-metadata": "catalog:",
"uuid": "catalog:",
diff --git a/packages/@n8n/db/package.json b/packages/@n8n/db/package.json
index f957faa634c..b130f54ccd0 100644
--- a/packages/@n8n/db/package.json
+++ b/packages/@n8n/db/package.json
@@ -38,7 +38,7 @@
"@n8n/di": "workspace:*",
"@n8n/permissions": "workspace:*",
"@n8n/utils": "workspace:*",
- "@n8n/typeorm": "catalog:",
+ "@n8n/typeorm": "workspace:*",
"class-validator": "0.14.0",
"flatted": "catalog:",
"lodash": "catalog:",
diff --git a/packages/@n8n/typeorm/.gitignore b/packages/@n8n/typeorm/.gitignore
new file mode 100644
index 00000000000..c40df25e8d8
--- /dev/null
+++ b/packages/@n8n/typeorm/.gitignore
@@ -0,0 +1,20 @@
+.nyc_output/
+.DS_Store
+.vscode/
+.idea/
+build/
+dist/
+coverage/
+node_modules/
+/ormconfig.json
+ormlogs.log
+npm-debug.log
+/test/github-issues/799/tmp/*
+# test projects generated by test/github-issues/issue-8975.ts
+/*TestProject
+# ignore yarn2 artifacts but allow yarn.lock (forces yarn1 compat which is node_modules)
+.yarn/
+.yarn*
+*.iml
+temp/**/*.db*
+temp/test
diff --git a/packages/@n8n/typeorm/LICENSE b/packages/@n8n/typeorm/LICENSE
new file mode 100644
index 00000000000..e6d5739a62d
--- /dev/null
+++ b/packages/@n8n/typeorm/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2015-2024 TypeORM. http://typeorm.github.io
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/packages/@n8n/typeorm/README.md b/packages/@n8n/typeorm/README.md
new file mode 100644
index 00000000000..bbcd6fa7db0
--- /dev/null
+++ b/packages/@n8n/typeorm/README.md
@@ -0,0 +1,1252 @@
+
+
+TypeORM is an [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping)
+that can run in NodeJS platforms and can be used with TypeScript and JavaScript (ES2021).
+Its goal is to always support the latest JavaScript features and provide additional features
+that help you to develop any kind of application that uses databases - from
+small applications with a few tables to large-scale enterprise applications
+with multiple databases.
+
+TypeORM supports both [Active Record](./docs/active-record-data-mapper.md#what-is-the-active-record-pattern) and [Data Mapper](./docs/active-record-data-mapper.md#what-is-the-data-mapper-pattern) patterns,
+unlike all other JavaScript ORMs currently in existence,
+which means you can write high-quality, loosely coupled, scalable,
+maintainable applications in the most productive way.
+
+TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate.org/orm/),
+[Doctrine](http://www.doctrine-project.org/) and [Entity Framework](https://www.asp.net/entity-framework).
+
+## Features
+
+- Supports both [DataMapper](./docs/active-record-data-mapper.md#what-is-the-data-mapper-pattern) and [ActiveRecord](./docs/active-record-data-mapper.md#what-is-the-active-record-pattern) (your choice).
+- Entities and columns.
+- Database-specific column types.
+- Entity manager.
+- Repositories and custom repositories.
+- Clean object-relational model.
+- Associations (relations).
+- Eager and lazy relations.
+- Uni-directional, bi-directional, and self-referenced relations.
+- Supports multiple inheritance patterns.
+- Cascades.
+- Indices.
+- Transactions.
+- Migrations and automatic migrations generation.
+- Connection pooling.
+- Replication.
+- Using multiple database instances.
+- Working with multiple database types.
+- Cross-database and cross-schema queries.
+- Elegant-syntax, flexible and powerful QueryBuilder.
+- Left and inner joins.
+- Proper pagination for queries using joins.
+- Query caching.
+- Streaming raw results.
+- Logging.
+- Listeners and subscribers (hooks).
+- Supports closure table pattern.
+- Schema declaration in models or separate configuration files.
+- Supports MySQL / MariaDB / Postgres / SQLite.
+- Works in NodeJS platforms.
+- TypeScript and JavaScript support.
+- ESM and CommonJS support.
+- Produced code is performant, flexible, clean, and maintainable.
+- Follows all possible best practices.
+- CLI.
+
+And more...
+
+## Supported Drivers
+
+This is a specialized fork of TypeORM that supports only the following database drivers:
+
+- **MySQL** - Full MySQL database support
+- **MariaDB** - Full MariaDB database support (uses MySQL driver)
+- **PostgreSQL** - Full PostgreSQL database support
+- **SQLite** - Standard file-based SQLite support
+- **SQLite-Pooled** - Enhanced SQLite with connection pooling for better performance
+
+This fork focuses on server-side Node.js applications and core SQL database support.
+
+With TypeORM your models look like this:
+
+```typescript
+import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
+
+@Entity()
+export class User {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column()
+ firstName: string
+
+ @Column()
+ lastName: string
+
+ @Column()
+ age: number
+}
+```
+
+And your domain logic looks like this:
+
+```typescript
+const userRepository = MyDataSource.getRepository(User)
+
+const user = new User()
+user.firstName = "Timber"
+user.lastName = "Saw"
+user.age = 25
+await userRepository.save(user)
+
+const allUsers = await userRepository.find()
+const firstUser = await userRepository.findOneBy({
+ id: 1,
+}) // find by id
+const timber = await userRepository.findOneBy({
+ firstName: "Timber",
+ lastName: "Saw",
+}) // find by firstName and lastName
+
+await userRepository.remove(timber)
+```
+
+Alternatively, if you prefer to use the `ActiveRecord` implementation, you can use it as well:
+
+```typescript
+import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm"
+
+@Entity()
+export class User extends BaseEntity {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column()
+ firstName: string
+
+ @Column()
+ lastName: string
+
+ @Column()
+ age: number
+}
+```
+
+And your domain logic will look this way:
+
+```typescript
+const user = new User()
+user.firstName = "Timber"
+user.lastName = "Saw"
+user.age = 25
+await user.save()
+
+const allUsers = await User.find()
+const firstUser = await User.findOneBy({
+ id: 1,
+})
+const timber = await User.findOneBy({
+ firstName: "Timber",
+ lastName: "Saw"
+})
+
+await timber.remove()
+```
+
+## Installation
+
+1. Install the npm package:
+
+ `npm install typeorm --save`
+
+2. You need to install `reflect-metadata` shim:
+
+ `npm install reflect-metadata --save`
+
+ and import it somewhere in the global place of your app (for example in `app.ts`):
+
+ `import "reflect-metadata"`
+
+3. You may need to install node typings:
+
+ `npm install @types/node --save-dev`
+
+4. Install a database driver:
+
+ - for **MySQL** or **MariaDB**
+
+ `npm install mysql --save` (you can install `mysql2` instead as well)
+
+ - for **PostgreSQL** or **CockroachDB**
+
+ `npm install pg --save`
+
+ - for **SQLite**
+
+ `npm install sqlite3 --save`
+
+ Install only _one_ of them, depending on which database you use.
+
+##### TypeScript configuration
+
+Also, make sure you are using TypeScript version **4.5** or higher,
+and you have enabled the following settings in `tsconfig.json`:
+
+```json
+"emitDecoratorMetadata": true,
+"experimentalDecorators": true,
+```
+
+You may also need to enable `es6` in the `lib` section of compiler options, or install `es6-shim` from `@types`.
+
+## Quick Start
+
+The quickest way to get started with TypeORM is to use its CLI commands to generate a starter project.
+Quick start works only if you are using TypeORM in a NodeJS application.
+If you are using other platforms, proceed to the [step-by-step guide](#step-by-step-guide).
+
+To create a new project using CLI, run the following command:
+
+```shell
+npx typeorm init --name MyProject --database postgres
+```
+
+Where `name` is the name of your project and `database` is the database you'll use.
+Database can be one of the following values: `mysql`, `mariadb`, `postgres`, `sqlite`.
+
+This command will generate a new project in the `MyProject` directory with the following files:
+
+```
+MyProject
+├── src // place of your TypeScript code
+│ ├── entity // place where your entities (database models) are stored
+│ │ └── User.ts // sample entity
+│ ├── migration // place where your migrations are stored
+│ ├── data-source.ts // data source and all connection configuration
+│ └── index.ts // start point of your application
+├── .gitignore // standard gitignore file
+├── package.json // node module dependencies
+├── README.md // simple readme file
+└── tsconfig.json // TypeScript compiler options
+```
+
+> You can also run `typeorm init` on an existing node project, but be careful - it may override some files you already have.
+
+The next step is to install new project dependencies:
+
+```shell
+cd MyProject
+npm install
+```
+
+After you have all dependencies installed, edit the `data-source.ts` file and put your own database connection configuration options in there:
+
+```ts
+export const AppDataSource = new DataSource({
+ type: "postgres",
+ host: "localhost",
+ port: 5432,
+ username: "test",
+ password: "test",
+ database: "test",
+ synchronize: true,
+ logging: true,
+ entities: [Post, Category],
+ subscribers: [],
+ migrations: [],
+})
+```
+
+Particularly, most of the time you'll only need to configure
+`host`, `username`, `password`, `database` and maybe `port` options.
+
+Once you finish with configuration and all node modules are installed, you can run your application:
+
+```shell
+npm start
+```
+
+That's it, your application should successfully run and insert a new user into the database.
+You can continue to work with this project and integrate other modules you need and start
+creating more entities.
+
+> You can generate an ESM project by running
+> `npx typeorm init --name MyProject --database postgres --module esm` command.
+
+> You can generate an even more advanced project with express installed by running
+> `npx typeorm init --name MyProject --database mysql --express` command.
+
+> You can generate a docker-compose file by running
+> `npx typeorm init --name MyProject --database postgres --docker` command.
+
+## Step-by-Step Guide
+
+What are you expecting from ORM?
+First of all, you are expecting it will create database tables for you
+and find / insert / update / delete your data without the pain of
+having to write lots of hardly maintainable SQL queries.
+This guide will show you how to set up TypeORM from scratch and make it do what you are expecting from an ORM.
+
+### Create a model
+
+Working with a database starts with creating tables.
+How do you tell TypeORM to create a database table?
+The answer is - through the models.
+Your models in your app are your database tables.
+
+For example, you have a `Photo` model:
+
+```typescript
+export class Photo {
+ id: number
+ name: string
+ description: string
+ filename: string
+ views: number
+ isPublished: boolean
+}
+```
+
+And you want to store photos in your database.
+To store things in the database, first, you need a database table,
+and database tables are created from your models.
+Not all models, but only those you define as _entities_.
+
+### Create an entity
+
+_Entity_ is your model decorated by an `@Entity` decorator.
+A database table will be created for such models.
+You work with entities everywhere in TypeORM.
+You can load/insert/update/remove and perform other operations with them.
+
+Let's make our `Photo` model an entity:
+
+```typescript
+import { Entity } from "typeorm"
+
+@Entity()
+export class Photo {
+ id: number
+ name: string
+ description: string
+ filename: string
+ views: number
+ isPublished: boolean
+}
+```
+
+Now, a database table will be created for the `Photo` entity and we'll be able to work with it anywhere in our app.
+We have created a database table, however, what table can exist without columns?
+Let's create a few columns in our database table.
+
+### Adding table columns
+
+To add database columns, you simply need to decorate an entity's properties you want to make into a column
+with a `@Column` decorator.
+
+```typescript
+import { Entity, Column } from "typeorm"
+
+@Entity()
+export class Photo {
+ @Column()
+ id: number
+
+ @Column()
+ name: string
+
+ @Column()
+ description: string
+
+ @Column()
+ filename: string
+
+ @Column()
+ views: number
+
+ @Column()
+ isPublished: boolean
+}
+```
+
+Now `id`, `name`, `description`, `filename`, `views`, and `isPublished` columns will be added to the `photo` table.
+Column types in the database are inferred from the property types you used, e.g.
+`number` will be converted into `integer`, `string` into `varchar`, `boolean` into `bool`, etc.
+But you can use any column type your database supports by explicitly specifying a column type into the `@Column` decorator.
+
+We generated a database table with columns, but there is one thing left.
+Each database table must have a column with a primary key.
+
+### Creating a primary column
+
+Each entity **must** have at least one primary key column.
+This is a requirement and you can't avoid it.
+To make a column a primary key, you need to use the `@PrimaryColumn` decorator.
+
+```typescript
+import { Entity, Column, PrimaryColumn } from "typeorm"
+
+@Entity()
+export class Photo {
+ @PrimaryColumn()
+ id: number
+
+ @Column()
+ name: string
+
+ @Column()
+ description: string
+
+ @Column()
+ filename: string
+
+ @Column()
+ views: number
+
+ @Column()
+ isPublished: boolean
+}
+```
+
+### Creating an auto-generated column
+
+Now, let's say you want your id column to be auto-generated (this is known as auto-increment / sequence / serial / generated identity column).
+To do that, you need to change the `@PrimaryColumn` decorator to a `@PrimaryGeneratedColumn` decorator:
+
+```typescript
+import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
+
+@Entity()
+export class Photo {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column()
+ name: string
+
+ @Column()
+ description: string
+
+ @Column()
+ filename: string
+
+ @Column()
+ views: number
+
+ @Column()
+ isPublished: boolean
+}
+```
+
+### Column data types
+
+Next, let's fix our data types. By default, the string is mapped to a varchar(255)-like type (depending on the database type).
+The number is mapped to an integer-like type (depending on the database type).
+We don't want all our columns to be limited varchars or integers.
+Let's setup the correct data types:
+
+```typescript
+import { Entity, Column, PrimaryGeneratedColumn } from "typeorm"
+
+@Entity()
+export class Photo {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column({
+ length: 100,
+ })
+ name: string
+
+ @Column("text")
+ description: string
+
+ @Column()
+ filename: string
+
+ @Column("double")
+ views: number
+
+ @Column()
+ isPublished: boolean
+}
+```
+
+Column types are database-specific.
+You can set any column type your database supports.
+More information on supported column types can be found [here](./docs/entities.md#column-types).
+
+### Creating a new `DataSource`
+
+Now, when our entity is created, let's create `index.ts` file and set up our `DataSource` there:
+
+```typescript
+import "reflect-metadata"
+import { DataSource } from "typeorm"
+import { Photo } from "./entity/Photo"
+
+const AppDataSource = new DataSource({
+ type: "postgres",
+ host: "localhost",
+ port: 5432,
+ username: "root",
+ password: "admin",
+ database: "test",
+ entities: [Photo],
+ synchronize: true,
+ logging: false,
+})
+
+// to initialize the initial connection with the database, register all entities
+// and "synchronize" database schema, call "initialize()" method of a newly created database
+// once in your application bootstrap
+AppDataSource.initialize()
+ .then(() => {
+ // here you can start to work with your database
+ })
+ .catch((error) => console.log(error))
+```
+
+We are using Postgres in this example, but you can use any other supported database.
+To use another database, simply change the `type` in the options to the database type you are using:
+`mysql`, `mariadb`, `postgres`, or `sqlite`.
+Also make sure to use your own host, port, username, password, and database settings.
+
+We added our Photo entity to the list of entities for this data source.
+Each entity you are using in your connection must be listed there.
+
+Setting `synchronize` makes sure your entities will be synced with the database, every time you run the application.
+
+### Running the application
+
+Now if you run your `index.ts`, a connection with the database will be initialized and a database table for your photos will be created.
+
+```shell
++-------------+--------------+----------------------------+
+| photo |
++-------------+--------------+----------------------------+
+| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
+| name | varchar(100) | |
+| description | text | |
+| filename | varchar(255) | |
+| views | int(11) | |
+| isPublished | boolean | |
++-------------+--------------+----------------------------+
+```
+
+### Creating and inserting a photo into the database
+
+Now let's create a new photo to save it in the database:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const photo = new Photo()
+photo.name = "Me and Bears"
+photo.description = "I am near polar bears"
+photo.filename = "photo-with-bears.jpg"
+photo.views = 1
+photo.isPublished = true
+
+await AppDataSource.manager.save(photo)
+console.log("Photo has been saved. Photo id is", photo.id)
+```
+
+Once your entity is saved it will get a newly generated id.
+`save` method returns an instance of the same object you pass to it.
+It's not a new copy of the object, it modifies its "id" and returns it.
+
+### Using Entity Manager
+
+We just created a new photo and saved it in the database.
+We used `EntityManager` to save it.
+Using entity manager you can manipulate any entity in your app.
+For example, let's load our saved entity:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const savedPhotos = await AppDataSource.manager.find(Photo)
+console.log("All photos from the db: ", savedPhotos)
+```
+
+`savedPhotos` will be an array of Photo objects with the data loaded from the database.
+
+Learn more about EntityManager [here](./docs/working-with-entity-manager.md).
+
+### Using Repositories
+
+Now let's refactor our code and use `Repository` instead of `EntityManager`.
+Each entity has its own repository which handles all operations with its entity.
+When you deal with entities a lot, Repositories are more convenient to use than EntityManagers:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const photo = new Photo()
+photo.name = "Me and Bears"
+photo.description = "I am near polar bears"
+photo.filename = "photo-with-bears.jpg"
+photo.views = 1
+photo.isPublished = true
+
+const photoRepository = AppDataSource.getRepository(Photo)
+
+await photoRepository.save(photo)
+console.log("Photo has been saved")
+
+const savedPhotos = await photoRepository.find()
+console.log("All photos from the db: ", savedPhotos)
+```
+
+Learn more about Repository [here](./docs/working-with-repository.md).
+
+### Loading from the database
+
+Let's try more load operations using the Repository:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const photoRepository = AppDataSource.getRepository(Photo)
+const allPhotos = await photoRepository.find()
+console.log("All photos from the db: ", allPhotos)
+
+const firstPhoto = await photoRepository.findOneBy({
+ id: 1,
+})
+console.log("First photo from the db: ", firstPhoto)
+
+const meAndBearsPhoto = await photoRepository.findOneBy({
+ name: "Me and Bears",
+})
+console.log("Me and Bears photo from the db: ", meAndBearsPhoto)
+
+const allViewedPhotos = await photoRepository.findBy({ views: 1 })
+console.log("All viewed photos: ", allViewedPhotos)
+
+const allPublishedPhotos = await photoRepository.findBy({ isPublished: true })
+console.log("All published photos: ", allPublishedPhotos)
+
+const [photos, photosCount] = await photoRepository.findAndCount()
+console.log("All photos: ", photos)
+console.log("Photos count: ", photosCount)
+```
+
+### Updating in the database
+
+Now let's load a single photo from the database, update it and save it:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const photoRepository = AppDataSource.getRepository(Photo)
+const photoToUpdate = await photoRepository.findOneBy({
+ id: 1,
+})
+photoToUpdate.name = "Me, my friends and polar bears"
+await photoRepository.save(photoToUpdate)
+```
+
+Now photo with `id = 1` will be updated in the database.
+
+### Removing from the database
+
+Now let's remove our photo from the database:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { AppDataSource } from "./index"
+
+const photoRepository = AppDataSource.getRepository(Photo)
+const photoToRemove = await photoRepository.findOneBy({
+ id: 1,
+})
+await photoRepository.remove(photoToRemove)
+```
+
+Now photo with `id = 1` will be removed from the database.
+
+### Creating a one-to-one relation
+
+Let's create a one-to-one relationship with another class.
+Let's create a new class in `PhotoMetadata.ts`. This PhotoMetadata class is supposed to contain our photo's additional meta-information:
+
+```typescript
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ OneToOne,
+ JoinColumn,
+} from "typeorm"
+import { Photo } from "./Photo"
+
+@Entity()
+export class PhotoMetadata {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column("int")
+ height: number
+
+ @Column("int")
+ width: number
+
+ @Column()
+ orientation: string
+
+ @Column()
+ compressed: boolean
+
+ @Column()
+ comment: string
+
+ @OneToOne(() => Photo)
+ @JoinColumn()
+ photo: Photo
+}
+```
+
+Here, we are using a new decorator called `@OneToOne`. It allows us to create a one-to-one relationship between two entities.
+`type => Photo` is a function that returns the class of the entity with which we want to make our relationship.
+We are forced to use a function that returns a class, instead of using the class directly, because of the language specifics.
+We can also write it as `() => Photo`, but we use `type => Photo` as a convention to increase code readability.
+The type variable itself does not contain anything.
+
+We also add a `@JoinColumn` decorator, which indicates that this side of the relationship will own the relationship.
+Relations can be unidirectional or bidirectional.
+Only one side of relational can be owning.
+Using `@JoinColumn` decorator is required on the owner side of the relationship.
+
+If you run the app, you'll see a newly generated table, and it will contain a column with a foreign key for the photo relation:
+
+```shell
++-------------+--------------+----------------------------+
+| photo_metadata |
++-------------+--------------+----------------------------+
+| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
+| height | int(11) | |
+| width | int(11) | |
+| comment | varchar(255) | |
+| compressed | boolean | |
+| orientation | varchar(255) | |
+| photoId | int(11) | FOREIGN KEY |
++-------------+--------------+----------------------------+
+```
+
+### Save a one-to-one relation
+
+Now let's save a photo, and its metadata and attach them to each other.
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { PhotoMetadata } from "./entity/PhotoMetadata"
+
+// create a photo
+const photo = new Photo()
+photo.name = "Me and Bears"
+photo.description = "I am near polar bears"
+photo.filename = "photo-with-bears.jpg"
+photo.views = 1
+photo.isPublished = true
+
+// create a photo metadata
+const metadata = new PhotoMetadata()
+metadata.height = 640
+metadata.width = 480
+metadata.compressed = true
+metadata.comment = "cybershoot"
+metadata.orientation = "portrait"
+metadata.photo = photo // this way we connect them
+
+// get entity repositories
+const photoRepository = AppDataSource.getRepository(Photo)
+const metadataRepository = AppDataSource.getRepository(PhotoMetadata)
+
+// first we should save a photo
+await photoRepository.save(photo)
+
+// photo is saved. Now we need to save a photo metadata
+await metadataRepository.save(metadata)
+
+// done
+console.log(
+ "Metadata is saved, and the relation between metadata and photo is created in the database too",
+)
+```
+
+### Inverse side of the relationship
+
+Relations can be unidirectional or bidirectional.
+Currently, our relation between PhotoMetadata and Photo is unidirectional.
+The owner of the relation is PhotoMetadata, and Photo doesn't know anything about PhotoMetadata.
+This makes it complicated to access PhotoMetadata from the Photo side.
+To fix this issue we should add an inverse relation, and make relations between PhotoMetadata and Photo bidirectional.
+Let's modify our entities:
+
+```typescript
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ OneToOne,
+ JoinColumn,
+} from "typeorm"
+import { Photo } from "./Photo"
+
+@Entity()
+export class PhotoMetadata {
+ /* ... other columns */
+
+ @OneToOne(() => Photo, (photo) => photo.metadata)
+ @JoinColumn()
+ photo: Photo
+}
+```
+
+```typescript
+import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm"
+import { PhotoMetadata } from "./PhotoMetadata"
+
+@Entity()
+export class Photo {
+ /* ... other columns */
+
+ @OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
+ metadata: PhotoMetadata
+}
+```
+
+`photo => photo.metadata` is a function that returns the name of the inverse side of the relation.
+Here we show that the metadata property of the Photo class is where we store PhotoMetadata in the Photo class.
+Instead of passing a function that returns a property of the photo, you could alternatively simply pass a string to `@OneToOne` decorator, like `"metadata"`.
+But we used this function-typed approach to make our refactoring easier.
+
+Note that we should use the `@JoinColumn` decorator only on one side of a relation.
+Whichever side you put this decorator on will be the owning side of the relationship.
+The owning side of a relationship contains a column with a foreign key in the database.
+
+### Relations in ESM projects
+
+If you use ESM in your TypeScript project, you should use the `Relation` wrapper type in relation properties to avoid circular dependency issues.
+Let's modify our entities:
+
+```typescript
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ OneToOne,
+ JoinColumn,
+ Relation,
+} from "typeorm"
+import { Photo } from "./Photo"
+
+@Entity()
+export class PhotoMetadata {
+ /* ... other columns */
+
+ @OneToOne(() => Photo, (photo) => photo.metadata)
+ @JoinColumn()
+ photo: Relation
+}
+```
+
+```typescript
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ OneToOne,
+ Relation,
+} from "typeorm"
+import { PhotoMetadata } from "./PhotoMetadata"
+
+@Entity()
+export class Photo {
+ /* ... other columns */
+
+ @OneToOne(() => PhotoMetadata, (photoMetadata) => photoMetadata.photo)
+ metadata: Relation
+}
+```
+
+### Loading objects with their relations
+
+Now let's load our photo and its photo metadata in a single query.
+There are two ways to do it - using `find*` methods or using `QueryBuilder` functionality.
+Let's use `find*` method first.
+`find*` methods allow you to specify an object with the `FindOneOptions` / `FindManyOptions` interface.
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { PhotoMetadata } from "./entity/PhotoMetadata"
+import { AppDataSource } from "./index"
+
+const photoRepository = AppDataSource.getRepository(Photo)
+const photos = await photoRepository.find({
+ relations: {
+ metadata: true,
+ },
+})
+```
+
+Here, photos will contain an array of photos from the database, and each photo will contain its photo metadata.
+Learn more about Find Options in [this documentation](./docs/find-options.md).
+
+Using find options is good and dead simple, but if you need a more complex query, you should use `QueryBuilder` instead.
+`QueryBuilder` allows more complex queries to be used in an elegant way:
+
+```typescript
+import { Photo } from "./entity/Photo"
+import { PhotoMetadata } from "./entity/PhotoMetadata"
+import { AppDataSource } from "./index"
+
+const photos = await AppDataSource.getRepository(Photo)
+ .createQueryBuilder("photo")
+ .innerJoinAndSelect("photo.metadata", "metadata")
+ .getMany()
+```
+
+`QueryBuilder` allows the creation and execution of SQL queries of almost any complexity.
+When you work with `QueryBuilder`, think like you are creating an SQL query.
+In this example, "photo" and "metadata" are aliases applied to selected photos.
+You use aliases to access columns and properties of the selected data.
+
+### Using cascades to automatically save related objects
+
+We can set up cascade options in our relations, in the cases when we want our related object to be saved whenever the other object is saved.
+Let's change our photo's `@OneToOne` decorator a bit:
+
+```typescript
+export class Photo {
+ // ... other columns
+
+ @OneToOne(() => PhotoMetadata, (metadata) => metadata.photo, {
+ cascade: true,
+ })
+ metadata: PhotoMetadata
+}
+```
+
+Using `cascade` allows us not to separately save photos and separately save metadata objects now.
+Now we can simply save a photo object, and the metadata object will be saved automatically because of cascade options.
+
+```typescript
+import { AppDataSource } from "./index"
+
+// create photo object
+const photo = new Photo()
+photo.name = "Me and Bears"
+photo.description = "I am near polar bears"
+photo.filename = "photo-with-bears.jpg"
+photo.isPublished = true
+
+// create photo metadata object
+const metadata = new PhotoMetadata()
+metadata.height = 640
+metadata.width = 480
+metadata.compressed = true
+metadata.comment = "cybershoot"
+metadata.orientation = "portrait"
+
+photo.metadata = metadata // this way we connect them
+
+// get repository
+const photoRepository = AppDataSource.getRepository(Photo)
+
+// saving a photo also save the metadata
+await photoRepository.save(photo)
+
+console.log("Photo is saved, photo metadata is saved too.")
+```
+
+Notice that we now set the photo's `metadata` property, instead of the metadata's `photo` property as before. The `cascade` feature only works if you connect the photo to its metadata from the photo's side. If you set the metadata side, the metadata would not be saved automatically.
+
+### Creating a many-to-one / one-to-many relation
+
+Let's create a many-to-one/one-to-many relation.
+Let's say a photo has one author, and each author can have many photos.
+First, let's create an `Author` class:
+
+```typescript
+import {
+ Entity,
+ Column,
+ PrimaryGeneratedColumn,
+ OneToMany,
+ JoinColumn,
+} from "typeorm"
+import { Photo } from "./Photo"
+
+@Entity()
+export class Author {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column()
+ name: string
+
+ @OneToMany(() => Photo, (photo) => photo.author) // note: we will create author property in the Photo class below
+ photos: Photo[]
+}
+```
+
+`Author` contains an inverse side of a relation.
+`OneToMany` is always an inverse side of the relation, and it can't exist without `ManyToOne` on the other side of the relation.
+
+Now let's add the owner side of the relation into the Photo entity:
+
+```typescript
+import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from "typeorm"
+import { PhotoMetadata } from "./PhotoMetadata"
+import { Author } from "./Author"
+
+@Entity()
+export class Photo {
+ /* ... other columns */
+
+ @ManyToOne(() => Author, (author) => author.photos)
+ author: Author
+}
+```
+
+In many-to-one / one-to-many relations, the owner side is always many-to-one.
+It means that the class that uses `@ManyToOne` will store the id of the related object.
+
+After you run the application, the ORM will create the `author` table:
+
+```shell
++-------------+--------------+----------------------------+
+| author |
++-------------+--------------+----------------------------+
+| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
+| name | varchar(255) | |
++-------------+--------------+----------------------------+
+```
+
+It will also modify the `photo` table, adding a new `author` column and creating a foreign key for it:
+
+```shell
++-------------+--------------+----------------------------+
+| photo |
++-------------+--------------+----------------------------+
+| id | int(11) | PRIMARY KEY AUTO_INCREMENT |
+| name | varchar(255) | |
+| description | varchar(255) | |
+| filename | varchar(255) | |
+| isPublished | boolean | |
+| authorId | int(11) | FOREIGN KEY |
++-------------+--------------+----------------------------+
+```
+
+### Creating a many-to-many relation
+
+Let's create a many-to-many relation.
+Let's say a photo can be in many albums, and each album can contain many photos.
+Let's create an `Album` class:
+
+```typescript
+import {
+ Entity,
+ PrimaryGeneratedColumn,
+ Column,
+ ManyToMany,
+ JoinTable,
+} from "typeorm"
+
+@Entity()
+export class Album {
+ @PrimaryGeneratedColumn()
+ id: number
+
+ @Column()
+ name: string
+
+ @ManyToMany(() => Photo, (photo) => photo.albums)
+ @JoinTable()
+ photos: Photo[]
+}
+```
+
+`@JoinTable` is required to specify that this is the owner side of the relationship.
+
+Now let's add the inverse side of our relation to the `Photo` class:
+
+```typescript
+export class Photo {
+ // ... other columns
+
+ @ManyToMany(() => Album, (album) => album.photos)
+ albums: Album[]
+}
+```
+
+After you run the application, the ORM will create a **album_photos_photo_albums** _junction table_:
+
+```shell
++-------------+--------------+----------------------------+
+| album_photos_photo_albums |
++-------------+--------------+----------------------------+
+| album_id | int(11) | PRIMARY KEY FOREIGN KEY |
+| photo_id | int(11) | PRIMARY KEY FOREIGN KEY |
++-------------+--------------+----------------------------+
+```
+
+Don't forget to register the `Album` class with your connection in the ORM:
+
+```typescript
+const options: DataSourceOptions = {
+ // ... other options
+ entities: [Photo, PhotoMetadata, Author, Album],
+}
+```
+
+Now let's insert albums and photos into our database:
+
+```typescript
+import { AppDataSource } from "./index"
+
+// create a few albums
+const album1 = new Album()
+album1.name = "Bears"
+await AppDataSource.manager.save(album1)
+
+const album2 = new Album()
+album2.name = "Me"
+await AppDataSource.manager.save(album2)
+
+// create a few photos
+const photo = new Photo()
+photo.name = "Me and Bears"
+photo.description = "I am near polar bears"
+photo.filename = "photo-with-bears.jpg"
+photo.views = 1
+photo.isPublished = true
+photo.albums = [album1, album2]
+await AppDataSource.manager.save(photo)
+
+// now our photo is saved and albums are attached to it
+// now lets load them:
+const loadedPhoto = await AppDataSource.getRepository(Photo).findOne({
+ where: {
+ id: 1,
+ },
+ relations: {
+ albums: true,
+ },
+})
+```
+
+`loadedPhoto` will be equal to:
+
+```typescript
+{
+ id: 1,
+ name: "Me and Bears",
+ description: "I am near polar bears",
+ filename: "photo-with-bears.jpg",
+ albums: [{
+ id: 1,
+ name: "Bears"
+ }, {
+ id: 2,
+ name: "Me"
+ }]
+}
+```
+
+### Using QueryBuilder
+
+You can use QueryBuilder to build SQL queries of almost any complexity. For example, you can do this:
+
+```typescript
+const photos = await AppDataSource.getRepository(Photo)
+ .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it.
+ .innerJoinAndSelect("photo.metadata", "metadata")
+ .leftJoinAndSelect("photo.albums", "album")
+ .where("photo.isPublished = true")
+ .andWhere("(photo.name = :photoName OR photo.name = :bearName)")
+ .orderBy("photo.id", "DESC")
+ .skip(5)
+ .take(10)
+ .setParameters({ photoName: "My", bearName: "Mishka" })
+ .getMany()
+```
+
+This query selects all published photos with "My" or "Mishka" names.
+It will select results from position 5 (pagination offset)
+and will select only 10 results (pagination limit).
+The selection result will be ordered by id in descending order.
+The photo albums will be left joined and their metadata will be inner joined.
+
+You'll use the query builder in your application a lot.
+Learn more about QueryBuilder [here](./docs/select-query-builder.md).
+
+## Samples
+
+Take a look at the samples in [sample](https://github.com/typeorm/typeorm/tree/master/sample) for examples of usage.
+
+There are a few repositories that you can clone and start with:
+
+- [Example how to use TypeORM with TypeScript](https://github.com/typeorm/typescript-example)
+- [Example how to use TypeORM with JavaScript](https://github.com/typeorm/javascript-example)
+- [Example how to use TypeORM with JavaScript and Babel](https://github.com/typeorm/babel-example)
+- [Example how to use Express and TypeORM](https://github.com/typeorm/typescript-express-example)
+- [Example how to use Koa and TypeORM](https://github.com/typeorm/typescript-koa-example)
+
+## Extensions
+
+There are several extensions that simplify working with TypeORM and integrating it with other modules:
+
+- [TypeORM + GraphQL framework](https://github.com/vesper-framework/vesper)
+- [TypeORM integration](https://github.com/typeorm/typeorm-typedi-extensions) with [TypeDI](https://github.com/pleerock/typedi)
+- [TypeORM integration](https://github.com/typeorm/typeorm-routing-controllers-extensions) with [routing-controllers](https://github.com/pleerock/routing-controllers)
+- Models generation from the existing database - [typeorm-model-generator](https://github.com/Kononnable/typeorm-model-generator)
+- Fixtures loader - [typeorm-fixtures-cli](https://github.com/RobinCK/typeorm-fixtures)
+- ER Diagram generator - [typeorm-uml](https://github.com/eugene-manuilov/typeorm-uml/)
+- another ER Diagram generator - [erdia](https://www.npmjs.com/package/erdia/)
+- Create, drop & seed database - [typeorm-extension](https://github.com/tada5hi/typeorm-extension)
+- Automatically update `data-source.ts` after generating migrations/entities - [typeorm-codebase-sync](https://www.npmjs.com/package/typeorm-codebase-sync)
+- Easy manipulation of `relations` objects - [typeorm-relations](https://npmjs.com/package/typeorm-relations)
+- Automatically generate `relations` based on a GraphQL query - [typeorm-relations-graphql](https://npmjs.com/package/typeorm-relations-graphql)
+
+## Contributing
+
+Learn about contribution [here](https://github.com/typeorm/typeorm/blob/master/CONTRIBUTING.md) and how to set up your development environment [here](https://github.com/typeorm/typeorm/blob/master/DEVELOPER.md).
+
+This project exists thanks to all the people who contribute:
+
+
+
+## Sponsors
+
+Open source is hard and time-consuming. If you want to invest in TypeORM's future you can become a sponsor and allow our core team to spend more time on TypeORM's improvements and new features. [Become a sponsor](https://opencollective.com/typeorm)
+
+
+
+## Gold Sponsors
+
+Become a gold sponsor and get premium technical support from our core contributors. [Become a gold sponsor](https://opencollective.com/typeorm)
+
+
diff --git a/packages/@n8n/typeorm/package.json b/packages/@n8n/typeorm/package.json
new file mode 100644
index 00000000000..e36897e2468
--- /dev/null
+++ b/packages/@n8n/typeorm/package.json
@@ -0,0 +1,155 @@
+{
+ "name": "@n8n/typeorm",
+ "version": "0.3.20-18",
+ "description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports PostgreSQL and SQLite databases.",
+ "license": "MIT",
+ "readmeFilename": "README.md",
+ "author": {
+ "name": "Umed Khudoiberdiev",
+ "email": "pleerock.me@gmail.com"
+ },
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "require": "./dist/index.js",
+ "import": "./dist/index.js"
+ },
+ "./*.js": "./dist/*.js",
+ "./*": {
+ "types": "./dist/*.d.ts",
+ "require": "./dist/*.js",
+ "import": "./dist/*.js"
+ }
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "typesVersions": {
+ "*": {
+ "dist/*": [
+ "./dist/*"
+ ],
+ "*": [
+ "./dist/*"
+ ]
+ }
+ },
+ "files": [
+ "dist/**/*"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/typeorm/typeorm.git"
+ },
+ "bugs": {
+ "url": "https://github.com/typeorm/typeorm/issues"
+ },
+ "homepage": "https://typeorm.io",
+ "tags": [
+ "orm",
+ "typescript",
+ "typescript-orm",
+ "postgresql",
+ "postgresql-orm",
+ "sqlite",
+ "sqlite-orm"
+ ],
+ "devDependencies": {
+ "@tsconfig/node16": "^16.1.1",
+ "@types/app-root-path": "^1.2.4",
+ "@types/chai": "^4.3.4",
+ "@types/chai-as-promised": "^7.1.5",
+ "@types/debug": "^4.1.12",
+ "@types/mkdirp": "^1.0.2",
+ "@types/mocha": "^10.0.1",
+ "@types/node": "^18.13.0",
+ "@types/pg": "^8.15.6",
+ "@types/sha.js": "^2.4.0",
+ "@types/sinon": "^10.0.13",
+ "@types/source-map-support": "^0.5.6",
+ "@types/uuid": "^9.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.35.0",
+ "chai": "^4.3.7",
+ "chai-as-promised": "^7.1.1",
+ "eslint": "^8.44.0",
+ "mocha": "^10.2.0",
+ "pg": "^8.17.0",
+ "pg-query-stream": "^4.10.3",
+ "prettier": "^2.8.3",
+ "rimraf": "^4.1.2",
+ "sinon": "^15.0.1",
+ "sinon-chai": "^3.7.0",
+ "source-map-support": "^0.5.21",
+ "sqlite3": "5.1.7",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.3.3"
+ },
+ "peerDependencies": {
+ "pg": "^8.17.0",
+ "pg-native": "^3.5.2",
+ "pg-query-stream": "^4.10.3",
+ "sqlite3": "5.1.7"
+ },
+ "peerDependenciesMeta": {
+ "pg": {
+ "optional": true
+ },
+ "pg-native": {
+ "optional": true
+ },
+ "pg-query-stream": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
+ },
+ "dependencies": {
+ "app-root-path": "^3.1.0",
+ "async-mutex": "^0.5.0",
+ "chalk": "^4.1.2",
+ "dayjs": "^1.11.9",
+ "debug": "^4.3.4",
+ "dotenv": "catalog:",
+ "glob": "^10.0.0",
+ "mkdirp": "^2.1.3",
+ "reflect-metadata": "^0.2.2",
+ "sha.js": "^2.4.12",
+ "tarn": "3.0.2",
+ "tslib": "^2.5.0",
+ "uuid": "^9.0.0"
+ },
+ "scripts": {
+ "test": "tsc && mocha --file ./build/compiled/test/utils/test-setup.js --recursive --timeout 90000 ./build/compiled/test",
+ "test:unit": "echo \"No unit tests for @n8n/typeorm (vendored)\" && exit 0",
+ "test-fast": "mocha --file ./build/compiled/test/utils/test-setup.js --bail --recursive --timeout 90000 ./build/compiled/test",
+ "typecheck": "tsc --noEmit",
+ "build": "pnpm compile",
+ "build:unchecked": "tsc -p tsconfig.build.json --noCheck",
+ "compile": "tsc -p tsconfig.build.json",
+ "watch": "tsc -w -p tsconfig.build.json"
+ },
+ "funding": "https://opencollective.com/typeorm",
+ "collective": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typeorm",
+ "logo": "https://opencollective.com/opencollective/logo.txt"
+ },
+ "nyc": {
+ "all": true,
+ "cache": false,
+ "exclude": [
+ "**/*.d.ts"
+ ],
+ "extension": [
+ ".ts"
+ ],
+ "include": [
+ "build/compiled/src/**",
+ "src/**"
+ ],
+ "reporter": "json"
+ }
+}
diff --git a/packages/@n8n/typeorm/resources/logo_big.png b/packages/@n8n/typeorm/resources/logo_big.png
new file mode 100644
index 00000000000..1da97498a05
Binary files /dev/null and b/packages/@n8n/typeorm/resources/logo_big.png differ
diff --git a/packages/@n8n/typeorm/sample/sample1-simple-entity/entity/Post.ts b/packages/@n8n/typeorm/sample/sample1-simple-entity/entity/Post.ts
new file mode 100644
index 00000000000..b7c710d8f3a
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample1-simple-entity/entity/Post.ts
@@ -0,0 +1,19 @@
+import { Column, Entity } from '../../../src/index';
+import { PrimaryColumn } from '../../../src/decorator/columns/PrimaryColumn';
+import { Generated } from '../../../src/decorator/Generated';
+
+@Entity('sample01_post')
+export class Post {
+ @PrimaryColumn()
+ @Generated()
+ id: number;
+
+ @Column()
+ title: string;
+
+ @Column()
+ text: string;
+
+ @Column({ nullable: false })
+ likesCount: number;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/Post.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/Post.ts
new file mode 100644
index 00000000000..246e8f7b79d
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/Post.ts
@@ -0,0 +1,79 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { PostDetails } from './PostDetails';
+import { PostCategory } from './PostCategory';
+import { PostAuthor } from './PostAuthor';
+import { PostInformation } from './PostInformation';
+import { PostImage } from './PostImage';
+import { PostMetadata } from './PostMetadata';
+import { JoinColumn } from '../../../src/decorator/relations/JoinColumn';
+
+@Entity('sample2_post')
+export class Post {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ title: string;
+
+ @Column()
+ text: string;
+
+ // post has relation with category, however inverse relation is not set (category does not have relation with post set)
+ @OneToOne((type) => PostCategory, {
+ cascade: true,
+ })
+ @JoinColumn()
+ category: PostCategory;
+
+ // post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
+ // relation it will be inserted automatically to the db when you save this Post entity
+ @OneToOne(
+ (type) => PostDetails,
+ (details) => details.post,
+ {
+ cascade: ['insert'],
+ },
+ )
+ @JoinColumn()
+ details?: PostDetails;
+
+ // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
+ // it will be inserted automatically to the db when you save this Post entity
+ @OneToOne(
+ (type) => PostImage,
+ (image) => image.post,
+ {
+ cascade: ['update'],
+ },
+ )
+ @JoinColumn()
+ image: PostImage;
+
+ // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
+ // it will be inserted automatically to the db when you save this Post entity
+ @OneToOne(
+ (type) => PostMetadata,
+ (metadata) => metadata.post,
+ )
+ @JoinColumn()
+ metadata: PostMetadata | null;
+
+ // post has relation with details. full cascades here
+ @OneToOne(
+ (type) => PostInformation,
+ (information) => information.post,
+ {
+ cascade: true,
+ },
+ )
+ @JoinColumn()
+ information: PostInformation;
+
+ // post has relation with details. not cascades here. means cannot be persisted, updated or removed
+ @OneToOne(
+ (type) => PostAuthor,
+ (author) => author.post,
+ )
+ @JoinColumn()
+ author: PostAuthor;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostAuthor.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostAuthor.ts
new file mode 100644
index 00000000000..2e1c8208e5f
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostAuthor.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample2_post_author')
+export class PostAuthor {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+
+ @OneToOne(
+ (type) => Post,
+ (post) => post.author,
+ )
+ post: Post;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostCategory.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostCategory.ts
new file mode 100644
index 00000000000..6a3ebf69cc0
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostCategory.ts
@@ -0,0 +1,10 @@
+import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
+
+@Entity('sample2_post_category')
+export class PostCategory {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostDetails.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostDetails.ts
new file mode 100644
index 00000000000..9144382b1e2
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostDetails.ts
@@ -0,0 +1,26 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample2_post_details')
+export class PostDetails {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ authorName: string;
+
+ @Column()
+ comment: string;
+
+ @Column()
+ metadata: string;
+
+ @OneToOne(
+ (type) => Post,
+ (post) => post.details,
+ {
+ cascade: true,
+ },
+ )
+ post: Post;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostImage.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostImage.ts
new file mode 100644
index 00000000000..cff94484052
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostImage.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample2_post_image')
+export class PostImage {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ url: string;
+
+ @OneToOne(
+ (type) => Post,
+ (post) => post.image,
+ )
+ post: Post;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostInformation.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostInformation.ts
new file mode 100644
index 00000000000..c5c0a986c33
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostInformation.ts
@@ -0,0 +1,20 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample2_post_information')
+export class PostInformation {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ text: string;
+
+ @OneToOne(
+ (type) => Post,
+ (post) => post.information,
+ {
+ cascade: ['update'],
+ },
+ )
+ post: Post;
+}
diff --git a/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostMetadata.ts b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostMetadata.ts
new file mode 100644
index 00000000000..f04c036818f
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample2-one-to-one/entity/PostMetadata.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample2_post_metadata')
+export class PostMetadata {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ description: string;
+
+ @OneToOne(
+ (type) => Post,
+ (post) => post.metadata,
+ )
+ post: Post;
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/Post.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/Post.ts
new file mode 100644
index 00000000000..91199281eb4
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/Post.ts
@@ -0,0 +1,72 @@
+import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from '../../../src/index';
+import { PostDetails } from './PostDetails';
+import { PostCategory } from './PostCategory';
+import { PostAuthor } from './PostAuthor';
+import { PostInformation } from './PostInformation';
+import { PostImage } from './PostImage';
+import { PostMetadata } from './PostMetadata';
+
+@Entity('sample3_post')
+export class Post {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ title: string;
+
+ @Column()
+ text: string;
+
+ // post has relation with category, however inverse relation is not set (category does not have relation with post set)
+ @ManyToOne((type) => PostCategory, {
+ cascade: true,
+ })
+ category: PostCategory;
+
+ // post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
+ // relation it will be inserted automatically to the db when you save this Post entity
+ @ManyToOne(
+ (type) => PostDetails,
+ (details) => details.posts,
+ {
+ cascade: ['insert'],
+ },
+ )
+ details?: PostDetails;
+
+ // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
+ // it will be inserted automatically to the db when you save this Post entity
+ @ManyToOne(
+ (type) => PostImage,
+ (image) => image.posts,
+ {
+ cascade: ['update'],
+ },
+ )
+ image: PostImage;
+
+ // post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
+ // it will be inserted automatically to the db when you save this Post entity
+ @ManyToOne(
+ (type) => PostMetadata,
+ (metadata) => metadata.posts,
+ )
+ metadata: PostMetadata | null;
+
+ // post has relation with details. full cascades here
+ @ManyToOne(
+ (type) => PostInformation,
+ (information) => information.posts,
+ {
+ cascade: true,
+ },
+ )
+ information: PostInformation;
+
+ // post has relation with details. not cascades here. means cannot be persisted, updated or removed
+ @ManyToOne(
+ (type) => PostAuthor,
+ (author) => author.posts,
+ )
+ author: PostAuthor;
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostAuthor.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostAuthor.ts
new file mode 100644
index 00000000000..cfb5292023e
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostAuthor.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample3_post_author')
+export class PostAuthor {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+
+ @OneToMany(
+ (type) => Post,
+ (post) => post.author,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostCategory.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostCategory.ts
new file mode 100644
index 00000000000..5573dd6df61
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostCategory.ts
@@ -0,0 +1,10 @@
+import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
+
+@Entity('sample3_post_category')
+export class PostCategory {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostDetails.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostDetails.ts
new file mode 100644
index 00000000000..2a0653ad911
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostDetails.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample3_post_details')
+export class PostDetails {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ authorName: string | null;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ comment: string | null;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ metadata: string | null;
+
+ @OneToMany(
+ (type) => Post,
+ (post) => post.details,
+ {
+ cascade: true,
+ },
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostImage.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostImage.ts
new file mode 100644
index 00000000000..436d240cae7
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostImage.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample3_post_image')
+export class PostImage {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ url: string;
+
+ @OneToMany(
+ (type) => Post,
+ (post) => post.image,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostInformation.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostInformation.ts
new file mode 100644
index 00000000000..aa1c799c3f7
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostInformation.ts
@@ -0,0 +1,20 @@
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample3_post_information')
+export class PostInformation {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ text: string;
+
+ @OneToMany(
+ (type) => Post,
+ (post) => post.information,
+ {
+ cascade: ['update'],
+ },
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostMetadata.ts b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostMetadata.ts
new file mode 100644
index 00000000000..65071d770fc
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample3-many-to-one/entity/PostMetadata.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample3_post_metadata')
+export class PostMetadata {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ description: string;
+
+ @OneToMany(
+ (type) => Post,
+ (post) => post.metadata,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/Post.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/Post.ts
new file mode 100644
index 00000000000..780f19b9b16
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/Post.ts
@@ -0,0 +1,81 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { PostDetails } from './PostDetails';
+import { PostCategory } from './PostCategory';
+import { PostAuthor } from './PostAuthor';
+import { PostInformation } from './PostInformation';
+import { PostImage } from './PostImage';
+import { PostMetadata } from './PostMetadata';
+import { JoinTable } from '../../../src/decorator/relations/JoinTable';
+
+@Entity('sample4_post')
+export class Post {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ title: string;
+
+ @Column()
+ text: string;
+
+ // Post has relation with PostCategory, however inverse relation is not set
+ // (category does not have relation with post set)
+ @ManyToMany((type) => PostCategory, {
+ cascade: true,
+ })
+ @JoinTable()
+ categories: PostCategory[];
+
+ // Post has relation with PostDetails. Cascade insert here means if there is a new PostDetails instance set
+ // on this relation, it will be inserted automatically to the db when you save this Post entity
+ @ManyToMany(
+ (type) => PostDetails,
+ (details) => details.posts,
+ {
+ cascade: ['insert'],
+ },
+ )
+ @JoinTable()
+ details: PostDetails[];
+
+ // Post has relation with PostImage. Cascade update here means if there are changes to an existing PostImage, it
+ // will be updated automatically to the db when you save this Post entity
+ @ManyToMany(
+ (type) => PostImage,
+ (image) => image.posts,
+ {
+ cascade: ['update'],
+ },
+ )
+ @JoinTable()
+ images: PostImage[];
+
+ // Post has relation with PostMetadata. No cascades here means that when saving a Post entity, there will be
+ // no creating/updating/destroying PostMetadata.
+ @ManyToMany(
+ (type) => PostMetadata,
+ (metadata) => metadata.posts,
+ )
+ @JoinTable()
+ metadatas: PostMetadata[];
+
+ // Post has relation with PostInformation. Full cascades here
+ @ManyToMany(
+ (type) => PostInformation,
+ (information) => information.posts,
+ {
+ cascade: true,
+ },
+ )
+ @JoinTable()
+ informations: PostInformation[];
+
+ // Post has relation with author. No cascades here means that when saving a Post entity, there will be
+ // no creating/updating/destroying PostAuthor.
+ @ManyToMany(
+ (type) => PostAuthor,
+ (author) => author.posts,
+ )
+ @JoinTable()
+ authors: PostAuthor[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostAuthor.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostAuthor.ts
new file mode 100644
index 00000000000..b476eb396e4
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostAuthor.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample4_post_author')
+export class PostAuthor {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+
+ @ManyToMany(
+ (type) => Post,
+ (post) => post.authors,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostCategory.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostCategory.ts
new file mode 100644
index 00000000000..afb90d75485
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostCategory.ts
@@ -0,0 +1,10 @@
+import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
+
+@Entity('sample4_post_category')
+export class PostCategory {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ name: string;
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostDetails.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostDetails.ts
new file mode 100644
index 00000000000..d9c6f95074f
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostDetails.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample4_post_details')
+export class PostDetails {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ authorName: string | null;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ comment: string | null;
+
+ @Column({
+ type: String,
+ nullable: true,
+ })
+ metadata: string | null;
+
+ @ManyToMany(
+ (type) => Post,
+ (post) => post.details,
+ {
+ cascade: true,
+ },
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostImage.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostImage.ts
new file mode 100644
index 00000000000..7c73a0bf5e6
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostImage.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample4_post_image')
+export class PostImage {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ url: string;
+
+ @ManyToMany(
+ (type) => Post,
+ (post) => post.images,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostInformation.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostInformation.ts
new file mode 100644
index 00000000000..6b6759dc3ce
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostInformation.ts
@@ -0,0 +1,20 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample4_post_information')
+export class PostInformation {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ text: string;
+
+ @ManyToMany(
+ (type) => Post,
+ (post) => post.informations,
+ {
+ cascade: ['update'],
+ },
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostMetadata.ts b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostMetadata.ts
new file mode 100644
index 00000000000..9b80b7edb51
--- /dev/null
+++ b/packages/@n8n/typeorm/sample/sample4-many-to-many/entity/PostMetadata.ts
@@ -0,0 +1,17 @@
+import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
+import { Post } from './Post';
+
+@Entity('sample4_post_metadata')
+export class PostMetadata {
+ @PrimaryGeneratedColumn()
+ id: number;
+
+ @Column()
+ description: string;
+
+ @ManyToMany(
+ (type) => Post,
+ (post) => post.metadatas,
+ )
+ posts: Post[];
+}
diff --git a/packages/@n8n/typeorm/src/cache/DbQueryResultCache.ts b/packages/@n8n/typeorm/src/cache/DbQueryResultCache.ts
new file mode 100644
index 00000000000..73d849f5793
--- /dev/null
+++ b/packages/@n8n/typeorm/src/cache/DbQueryResultCache.ts
@@ -0,0 +1,270 @@
+import { ObjectLiteral } from '../common/ObjectLiteral';
+import { DataSource } from '../data-source/DataSource';
+import { QueryRunner } from '../query-runner/QueryRunner';
+import { Table } from '../schema-builder/table/Table';
+import { QueryResultCache } from './QueryResultCache';
+import { QueryResultCacheOptions } from './QueryResultCacheOptions';
+
+/**
+ * Caches query result into current database, into separate table called "query-result-cache".
+ */
+export class DbQueryResultCache implements QueryResultCache {
+ // -------------------------------------------------------------------------
+ // Private properties
+ // -------------------------------------------------------------------------
+
+ private queryResultCacheTable: string;
+
+ private queryResultCacheDatabase?: string;
+
+ private queryResultCacheSchema?: string;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(protected connection: DataSource) {
+ const { schema } = this.connection.driver.options as any;
+ const database = this.connection.driver.database;
+ const cacheOptions =
+ typeof this.connection.options.cache === 'object' ? this.connection.options.cache : {};
+ const cacheTableName = cacheOptions.tableName || 'query-result-cache';
+
+ this.queryResultCacheDatabase = database;
+ this.queryResultCacheSchema = schema;
+ this.queryResultCacheTable = this.connection.driver.buildTableName(
+ cacheTableName,
+ schema,
+ database,
+ );
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates a connection with given cache provider.
+ */
+ async connect(): Promise {}
+
+ /**
+ * Disconnects with given cache provider.
+ */
+ async disconnect(): Promise {}
+
+ /**
+ * Creates table for storing cache if it does not exist yet.
+ */
+ async synchronize(queryRunner?: QueryRunner): Promise {
+ queryRunner = this.getQueryRunner(queryRunner);
+ const driver = this.connection.driver;
+ const tableExist = await queryRunner.hasTable(this.queryResultCacheTable); // todo: table name should be configurable
+ if (tableExist) return;
+
+ await queryRunner.createTable(
+ new Table({
+ database: this.queryResultCacheDatabase,
+ schema: this.queryResultCacheSchema,
+ name: this.queryResultCacheTable,
+ columns: [
+ {
+ name: 'id',
+ isPrimary: true,
+ isNullable: false,
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheId,
+ }),
+ generationStrategy: 'increment',
+ isGenerated: true,
+ },
+ {
+ name: 'identifier',
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheIdentifier,
+ }),
+ isNullable: true,
+ },
+ {
+ name: 'time',
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheTime,
+ }),
+ isPrimary: false,
+ isNullable: false,
+ },
+ {
+ name: 'duration',
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheDuration,
+ }),
+ isPrimary: false,
+ isNullable: false,
+ },
+ {
+ name: 'query',
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheQuery,
+ }),
+ isPrimary: false,
+ isNullable: false,
+ },
+ {
+ name: 'result',
+ type: driver.normalizeType({
+ type: driver.mappedDataTypes.cacheResult,
+ }),
+ isNullable: false,
+ },
+ ],
+ }),
+ );
+ }
+
+ /**
+ * Get data from cache.
+ * Returns cache result if found.
+ * Returns undefined if result is not cached.
+ */
+ getFromCache(
+ options: QueryResultCacheOptions,
+ queryRunner?: QueryRunner,
+ ): Promise {
+ queryRunner = this.getQueryRunner(queryRunner);
+ const qb = this.connection
+ .createQueryBuilder(queryRunner)
+ .select()
+ .from(this.queryResultCacheTable, 'cache');
+
+ if (options.identifier) {
+ return qb
+ .where(`${qb.escape('cache')}.${qb.escape('identifier')} = :identifier`)
+ .setParameters({
+ identifier: options.identifier,
+ })
+ .cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
+ .getRawOne();
+ } else if (options.query) {
+ return qb
+ .where(`${qb.escape('cache')}.${qb.escape('query')} = :query`)
+ .setParameters({
+ query: options.query,
+ })
+ .cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
+ .getRawOne();
+ }
+
+ return Promise.resolve(undefined);
+ }
+
+ /**
+ * Checks if cache is expired or not.
+ */
+ isExpired(savedCache: QueryResultCacheOptions): boolean {
+ const duration =
+ typeof savedCache.duration === 'string' ? parseInt(savedCache.duration) : savedCache.duration;
+ return (
+ (typeof savedCache.time === 'string' ? parseInt(savedCache.time as any) : savedCache.time)! +
+ duration <
+ new Date().getTime()
+ );
+ }
+
+ /**
+ * Stores given query result in the cache.
+ */
+ async storeInCache(
+ options: QueryResultCacheOptions,
+ savedCache: QueryResultCacheOptions | undefined,
+ queryRunner?: QueryRunner,
+ ): Promise {
+ const shouldCreateQueryRunner =
+ queryRunner === undefined || queryRunner?.getReplicationMode() === 'slave';
+
+ if (queryRunner === undefined || shouldCreateQueryRunner) {
+ queryRunner = this.connection.createQueryRunner('master');
+ }
+
+ let insertedValues: ObjectLiteral = options;
+
+ if (savedCache && savedCache.identifier) {
+ // if exist then update
+ const qb = queryRunner.manager
+ .createQueryBuilder()
+ .update(this.queryResultCacheTable)
+ .set(insertedValues);
+
+ qb.where(`${qb.escape('identifier')} = :condition`, {
+ condition: insertedValues.identifier,
+ });
+ await qb.execute();
+ } else if (savedCache && savedCache.query) {
+ // if exist then update
+ const qb = queryRunner.manager
+ .createQueryBuilder()
+ .update(this.queryResultCacheTable)
+ .set(insertedValues);
+
+ qb.where(`${qb.escape('query')} = :condition`, {
+ condition: insertedValues.query,
+ });
+
+ await qb.execute();
+ } else {
+ // otherwise insert
+ await queryRunner.manager
+ .createQueryBuilder()
+ .insert()
+ .into(this.queryResultCacheTable)
+ .values(insertedValues)
+ .execute();
+ }
+
+ if (shouldCreateQueryRunner) {
+ await queryRunner.release();
+ }
+ }
+
+ /**
+ * Clears everything stored in the cache.
+ */
+ async clear(queryRunner: QueryRunner): Promise {
+ return this.getQueryRunner(queryRunner).clearTable(this.queryResultCacheTable);
+ }
+
+ /**
+ * Removes all cached results by given identifiers from cache.
+ */
+ async remove(identifiers: string[], queryRunner?: QueryRunner): Promise {
+ let _queryRunner: QueryRunner = queryRunner || this.getQueryRunner();
+ await Promise.all(
+ identifiers.map((identifier) => {
+ const qb = _queryRunner.manager.createQueryBuilder();
+ return qb
+ .delete()
+ .from(this.queryResultCacheTable)
+ .where(`${qb.escape('identifier')} = :identifier`, {
+ identifier,
+ })
+ .execute();
+ }),
+ );
+
+ if (!queryRunner) {
+ await _queryRunner.release();
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Gets a query runner to work with.
+ */
+ protected getQueryRunner(queryRunner?: QueryRunner): QueryRunner {
+ if (queryRunner) return queryRunner;
+
+ return this.connection.createQueryRunner();
+ }
+}
diff --git a/packages/@n8n/typeorm/src/cache/QueryResultCache.ts b/packages/@n8n/typeorm/src/cache/QueryResultCache.ts
new file mode 100644
index 00000000000..b409f98ab24
--- /dev/null
+++ b/packages/@n8n/typeorm/src/cache/QueryResultCache.ts
@@ -0,0 +1,54 @@
+import { QueryResultCacheOptions } from './QueryResultCacheOptions';
+import { QueryRunner } from '../query-runner/QueryRunner';
+
+/**
+ * Implementations of this interface provide different strategies to cache query builder results.
+ */
+export interface QueryResultCache {
+ /**
+ * Creates a connection with given cache provider.
+ */
+ connect(): Promise;
+
+ /**
+ * Closes a connection with given cache provider.
+ */
+ disconnect(): Promise;
+
+ /**
+ * Performs operations needs to be created during schema synchronization.
+ */
+ synchronize(queryRunner?: QueryRunner): Promise;
+
+ /**
+ * Caches given query result.
+ */
+ getFromCache(
+ options: QueryResultCacheOptions,
+ queryRunner?: QueryRunner,
+ ): Promise;
+
+ /**
+ * Stores given query result in the cache.
+ */
+ storeInCache(
+ options: QueryResultCacheOptions,
+ savedCache: QueryResultCacheOptions | undefined,
+ queryRunner?: QueryRunner,
+ ): Promise;
+
+ /**
+ * Checks if cache is expired or not.
+ */
+ isExpired(savedCache: QueryResultCacheOptions): boolean;
+
+ /**
+ * Clears everything stored in the cache.
+ */
+ clear(queryRunner?: QueryRunner): Promise;
+
+ /**
+ * Removes all cached results by given identifiers from cache.
+ */
+ remove(identifiers: string[], queryRunner?: QueryRunner): Promise;
+}
diff --git a/packages/@n8n/typeorm/src/cache/QueryResultCacheFactory.ts b/packages/@n8n/typeorm/src/cache/QueryResultCacheFactory.ts
new file mode 100644
index 00000000000..be983e643d1
--- /dev/null
+++ b/packages/@n8n/typeorm/src/cache/QueryResultCacheFactory.ts
@@ -0,0 +1,37 @@
+import { DbQueryResultCache } from './DbQueryResultCache';
+import { QueryResultCache } from './QueryResultCache';
+import { DataSource } from '../data-source/DataSource';
+import { TypeORMError } from '../error/TypeORMError';
+
+/**
+ * Caches query result into the database.
+ */
+export class QueryResultCacheFactory {
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(protected connection: DataSource) {}
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates a new query result cache based on connection options.
+ */
+ create(): QueryResultCache {
+ if (!this.connection.options.cache)
+ throw new TypeORMError(
+ `To use cache you need to enable it in connection options by setting cache: true or providing some caching options. Example: { host: ..., username: ..., cache: true }`,
+ );
+
+ const cache: any = this.connection.options.cache;
+
+ if (cache.provider && typeof cache.provider === 'function') {
+ return cache.provider(this.connection);
+ }
+
+ return new DbQueryResultCache(this.connection);
+ }
+}
diff --git a/packages/@n8n/typeorm/src/cache/QueryResultCacheOptions.ts b/packages/@n8n/typeorm/src/cache/QueryResultCacheOptions.ts
new file mode 100644
index 00000000000..75986c311c9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/cache/QueryResultCacheOptions.ts
@@ -0,0 +1,30 @@
+/**
+ * Options passed to QueryResultCache class.
+ */
+export interface QueryResultCacheOptions {
+ /**
+ * Cache identifier set by user.
+ * Can be empty.
+ */
+ identifier?: string;
+
+ /**
+ * Time, when cache was created.
+ */
+ time?: number;
+
+ /**
+ * Duration in milliseconds during which results will be returned from cache.
+ */
+ duration: number;
+
+ /**
+ * Cached query.
+ */
+ query?: string;
+
+ /**
+ * Query result that will be cached.
+ */
+ result?: any;
+}
diff --git a/packages/@n8n/typeorm/src/common/DeepPartial.ts b/packages/@n8n/typeorm/src/common/DeepPartial.ts
new file mode 100644
index 00000000000..58b13316130
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/DeepPartial.ts
@@ -0,0 +1,14 @@
+/**
+ * Same as Partial but goes deeper and makes Partial all its properties and sub-properties.
+ */
+export type DeepPartial =
+ | T
+ | (T extends Array
+ ? DeepPartial[]
+ : T extends Map
+ ? Map, DeepPartial>
+ : T extends Set
+ ? Set>
+ : T extends object
+ ? { [K in keyof T]?: DeepPartial }
+ : T);
diff --git a/packages/@n8n/typeorm/src/common/EntityTarget.ts b/packages/@n8n/typeorm/src/common/EntityTarget.ts
new file mode 100644
index 00000000000..b438c90ed74
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/EntityTarget.ts
@@ -0,0 +1,11 @@
+import { ObjectType } from './ObjectType';
+import { EntitySchema } from '..';
+
+/**
+ * Entity target.
+ */
+export type EntityTarget =
+ | ObjectType
+ | EntitySchema
+ | string
+ | { type: Entity; name: string };
diff --git a/packages/@n8n/typeorm/src/common/MixedList.ts b/packages/@n8n/typeorm/src/common/MixedList.ts
new file mode 100644
index 00000000000..85c23481c70
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/MixedList.ts
@@ -0,0 +1,6 @@
+/**
+ * List of T-s passed as an array or object map.
+ *
+ * Example usage: entities as an array of imported using import * as syntax.
+ */
+export type MixedList = T[] | { [key: string]: T };
diff --git a/packages/@n8n/typeorm/src/common/ObjectLiteral.ts b/packages/@n8n/typeorm/src/common/ObjectLiteral.ts
new file mode 100644
index 00000000000..bcfcc946caa
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/ObjectLiteral.ts
@@ -0,0 +1,6 @@
+/**
+ * Interface of the simple literal object with any string keys.
+ */
+export interface ObjectLiteral {
+ [key: string]: any;
+}
diff --git a/packages/@n8n/typeorm/src/common/ObjectType.ts b/packages/@n8n/typeorm/src/common/ObjectType.ts
new file mode 100644
index 00000000000..31f9592875a
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/ObjectType.ts
@@ -0,0 +1,4 @@
+/**
+ * Represents some Type of the Object.
+ */
+export type ObjectType = { new (): T } | Function;
diff --git a/packages/@n8n/typeorm/src/common/PickKeysByType.ts b/packages/@n8n/typeorm/src/common/PickKeysByType.ts
new file mode 100644
index 00000000000..ab753f27e54
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/PickKeysByType.ts
@@ -0,0 +1,7 @@
+/**
+ * Pick only the keys that match the Type `U`
+ */
+export type PickKeysByType = string &
+ keyof {
+ [P in keyof T as T[P] extends U ? P : never]: T[P];
+ };
diff --git a/packages/@n8n/typeorm/src/common/RelationType.ts b/packages/@n8n/typeorm/src/common/RelationType.ts
new file mode 100644
index 00000000000..5845e9c1325
--- /dev/null
+++ b/packages/@n8n/typeorm/src/common/RelationType.ts
@@ -0,0 +1,14 @@
+/**
+ * Wrapper type for relation type definitions in entities.
+ * Used to circumvent ESM modules circular dependency issue caused by reflection metadata saving the type of the property.
+ *
+ * Usage example:
+ * @Entity()
+ * export default class User {
+ *
+ * @OneToOne(() => Profile, profile => profile.user)
+ * profile: Relation;
+ *
+ * }
+ */
+export type Relation = T;
diff --git a/packages/@n8n/typeorm/src/connection/Connection.ts b/packages/@n8n/typeorm/src/connection/Connection.ts
new file mode 100644
index 00000000000..705133553f8
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/Connection.ts
@@ -0,0 +1,10 @@
+import { DataSource } from '../data-source/DataSource';
+
+/**
+ * Connection is a single database ORM connection to a specific database.
+ * Its not required to be a database connection, depend on database type it can create connection pool.
+ * You can have multiple connections to multiple databases in your application.
+ *
+ * @deprecated
+ */
+export class Connection extends DataSource {}
diff --git a/packages/@n8n/typeorm/src/connection/ConnectionManager.ts b/packages/@n8n/typeorm/src/connection/ConnectionManager.ts
new file mode 100644
index 00000000000..4058baec7d1
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/ConnectionManager.ts
@@ -0,0 +1,66 @@
+import { DataSource } from '../data-source/DataSource';
+import { ConnectionNotFoundError } from '../error/ConnectionNotFoundError';
+import { DataSourceOptions } from '../data-source/DataSourceOptions';
+import { AlreadyHasActiveConnectionError } from '../error/AlreadyHasActiveConnectionError';
+
+/**
+ * ConnectionManager is used to store and manage multiple orm connections.
+ * It also provides useful factory methods to simplify connection creation.
+ *
+ * @deprecated
+ */
+export class ConnectionManager {
+ /**
+ * List of connections registered in this connection manager.
+ */
+ get connections(): DataSource[] {
+ return Array.from(this.connectionMap.values());
+ }
+
+ /**
+ * Internal lookup to quickly get from a connection name to the Connection object.
+ */
+ private readonly connectionMap: Map = new Map();
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Checks if connection with the given name exist in the manager.
+ */
+ has(name: string): boolean {
+ return this.connectionMap.has(name);
+ }
+
+ /**
+ * Gets registered connection with the given name.
+ * If connection name is not given then it will get a default connection.
+ * Throws error if connection with the given name was not found.
+ */
+ get(name: string = 'default'): DataSource {
+ const connection = this.connectionMap.get(name);
+ if (!connection) throw new ConnectionNotFoundError(name);
+
+ return connection;
+ }
+
+ /**
+ * Creates a new connection based on the given connection options and registers it in the manager.
+ * Connection won't be established, you'll need to manually call connect method to establish connection.
+ */
+ create(options: DataSourceOptions): DataSource {
+ // check if such connection is already registered
+ const existConnection = this.connectionMap.get(options.name || 'default');
+ if (existConnection) {
+ // if connection is registered and its not closed then throw an error
+ if (existConnection.isInitialized)
+ throw new AlreadyHasActiveConnectionError(options.name || 'default');
+ }
+
+ // create a new connection
+ const connection = new DataSource(options);
+ this.connectionMap.set(connection.name, connection);
+ return connection;
+ }
+}
diff --git a/packages/@n8n/typeorm/src/connection/ConnectionMetadataBuilder.ts b/packages/@n8n/typeorm/src/connection/ConnectionMetadataBuilder.ts
new file mode 100644
index 00000000000..2b0e8e1944c
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/ConnectionMetadataBuilder.ts
@@ -0,0 +1,101 @@
+import { importClassesFromDirectories } from '../util/DirectoryExportedClassesLoader';
+import { OrmUtils } from '../util/OrmUtils';
+import { getFromContainer } from '../container';
+import { MigrationInterface } from '../migration/MigrationInterface';
+import { getMetadataArgsStorage } from '../globals';
+import { EntityMetadataBuilder } from '../metadata-builder/EntityMetadataBuilder';
+import { EntitySchemaTransformer } from '../entity-schema/EntitySchemaTransformer';
+import { DataSource } from '../data-source/DataSource';
+import { EntitySchema } from '../entity-schema/EntitySchema';
+import { EntityMetadata } from '../metadata/EntityMetadata';
+import { EntitySubscriberInterface } from '../subscriber/EntitySubscriberInterface';
+import { InstanceChecker } from '../util/InstanceChecker';
+
+/**
+ * Builds migration instances, subscriber instances and entity metadatas for the given classes.
+ */
+export class ConnectionMetadataBuilder {
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(protected connection: DataSource) {}
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Builds migration instances for the given classes or directories.
+ */
+ async buildMigrations(migrations: (Function | string)[]): Promise {
+ const [migrationClasses, migrationDirectories] = OrmUtils.splitClassesAndStrings(migrations);
+ const allMigrationClasses = [
+ ...migrationClasses,
+ ...(await importClassesFromDirectories(this.connection.logger, migrationDirectories)),
+ ];
+ return allMigrationClasses.map((migrationClass) =>
+ getFromContainer(migrationClass),
+ );
+ }
+
+ /**
+ * Builds subscriber instances for the given classes or directories.
+ */
+ async buildSubscribers(
+ subscribers: (Function | string)[],
+ ): Promise[]> {
+ const [subscriberClasses, subscriberDirectories] = OrmUtils.splitClassesAndStrings(
+ subscribers || [],
+ );
+ const allSubscriberClasses = [
+ ...subscriberClasses,
+ ...(await importClassesFromDirectories(this.connection.logger, subscriberDirectories)),
+ ];
+ return getMetadataArgsStorage()
+ .filterSubscribers(allSubscriberClasses)
+ .map((metadata) => getFromContainer>(metadata.target));
+ }
+
+ /**
+ * Builds entity metadatas for the given classes or directories.
+ */
+ async buildEntityMetadatas(
+ entities: (Function | EntitySchema | string)[],
+ ): Promise {
+ // todo: instead we need to merge multiple metadata args storages
+
+ const [entityClassesOrSchemas, entityDirectories] = OrmUtils.splitClassesAndStrings(
+ entities || [],
+ );
+ const entityClasses: Function[] = entityClassesOrSchemas.filter(
+ (entityClass) => !InstanceChecker.isEntitySchema(entityClass),
+ ) as any;
+ const entitySchemas: EntitySchema[] = entityClassesOrSchemas.filter((entityClass) =>
+ InstanceChecker.isEntitySchema(entityClass),
+ ) as any;
+
+ const allEntityClasses = [
+ ...entityClasses,
+ ...(await importClassesFromDirectories(this.connection.logger, entityDirectories)),
+ ];
+ allEntityClasses.forEach((entityClass) => {
+ // if we have entity schemas loaded from directories
+ if (InstanceChecker.isEntitySchema(entityClass)) {
+ entitySchemas.push(entityClass);
+ }
+ });
+ const decoratorEntityMetadatas = new EntityMetadataBuilder(
+ this.connection,
+ getMetadataArgsStorage(),
+ ).build(allEntityClasses);
+
+ const metadataArgsStorageFromSchema = new EntitySchemaTransformer().transform(entitySchemas);
+ const schemaEntityMetadatas = new EntityMetadataBuilder(
+ this.connection,
+ metadataArgsStorageFromSchema,
+ ).build();
+
+ return [...decoratorEntityMetadatas, ...schemaEntityMetadatas];
+ }
+}
diff --git a/packages/@n8n/typeorm/src/connection/ConnectionOptions.ts b/packages/@n8n/typeorm/src/connection/ConnectionOptions.ts
new file mode 100644
index 00000000000..cab2fb298c7
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/ConnectionOptions.ts
@@ -0,0 +1,10 @@
+import { DataSourceOptions } from '../data-source/DataSourceOptions';
+
+/**
+ * ConnectionOptions is an interface with settings and options for specific connection.
+ * Options contain database and other connection-related settings.
+ * Consumer must provide connection options for each of your connections.
+ *
+ * @deprecated use DataSourceOptions instead
+ */
+export type ConnectionOptions = DataSourceOptions;
diff --git a/packages/@n8n/typeorm/src/connection/ConnectionOptionsReader.ts b/packages/@n8n/typeorm/src/connection/ConnectionOptionsReader.ts
new file mode 100644
index 00000000000..ae5e22f0c17
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/ConnectionOptionsReader.ts
@@ -0,0 +1,233 @@
+import appRootPath from 'app-root-path';
+import path from 'path';
+import { DataSourceOptions } from '../data-source/DataSourceOptions';
+import { PlatformTools } from '../platform/PlatformTools';
+import { ConnectionOptionsEnvReader } from './options-reader/ConnectionOptionsEnvReader';
+import { TypeORMError } from '../error';
+import { isAbsolute } from '../util/PathUtils';
+import { importOrRequireFile } from '../util/ImportUtils';
+
+/**
+ * Reads connection options from the ormconfig.
+ */
+export class ConnectionOptionsReader {
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(
+ protected options?: {
+ /**
+ * Directory where ormconfig should be read from.
+ * By default its your application root (where your app package.json is located).
+ */
+ root?: string;
+
+ /**
+ * Filename of the ormconfig configuration. By default its equal to "ormconfig".
+ */
+ configName?: string;
+ },
+ ) {}
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Returns all connection options read from the ormconfig.
+ */
+ async all(): Promise {
+ const options = await this.load();
+ if (!options)
+ throw new TypeORMError(`No connection options were found in any orm configuration files.`);
+
+ return options;
+ }
+
+ /**
+ * Gets a connection with a given name read from ormconfig.
+ * If connection with such name would not be found then it throw error.
+ */
+ async get(name: string): Promise {
+ const allOptions = await this.all();
+ const targetOptions = allOptions.find(
+ (options) => options.name === name || (name === 'default' && !options.name),
+ );
+ if (!targetOptions)
+ throw new TypeORMError(
+ `Cannot find connection ${name} because its not defined in any orm configuration files.`,
+ );
+
+ return targetOptions;
+ }
+
+ /**
+ * Checks if there is a TypeORM configuration file.
+ */
+ async has(name: string): Promise {
+ const allOptions = await this.load();
+ if (!allOptions) return false;
+
+ const targetOptions = allOptions.find(
+ (options) => options.name === name || (name === 'default' && !options.name),
+ );
+ return !!targetOptions;
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Loads all connection options from a configuration file.
+ *
+ * todo: get in count NODE_ENV somehow
+ */
+ protected async load(): Promise {
+ let connectionOptions: DataSourceOptions | DataSourceOptions[] | undefined = undefined;
+
+ const fileFormats = ['env', 'js', 'mjs', 'cjs', 'ts', 'mts', 'cts', 'json'];
+
+ // Detect if baseFilePath contains file extension
+ const possibleExtension = this.baseFilePath.substr(this.baseFilePath.lastIndexOf('.'));
+ const fileExtension = fileFormats.find((extension) => `.${extension}` === possibleExtension);
+
+ // try to find any of following configuration formats
+ const foundFileFormat =
+ fileExtension ||
+ fileFormats.find((format) => {
+ return PlatformTools.fileExist(this.baseFilePath + '.' + format);
+ });
+
+ // Determine config file name
+ const configFile = fileExtension
+ ? this.baseFilePath
+ : this.baseFilePath + '.' + foundFileFormat;
+
+ // if .env file found then load all its variables into process.env using dotenv package
+ if (foundFileFormat === 'env') {
+ PlatformTools.dotenv(configFile);
+ } else if (PlatformTools.fileExist(this.baseDirectory + '/.env')) {
+ PlatformTools.dotenv(this.baseDirectory + '/.env');
+ }
+
+ // try to find connection options from any of available sources of configuration
+ if (
+ PlatformTools.getEnvVariable('TYPEORM_CONNECTION') ||
+ PlatformTools.getEnvVariable('TYPEORM_URL')
+ ) {
+ connectionOptions = await new ConnectionOptionsEnvReader().read();
+ } else if (
+ foundFileFormat === 'js' ||
+ foundFileFormat === 'mjs' ||
+ foundFileFormat === 'cjs' ||
+ foundFileFormat === 'ts' ||
+ foundFileFormat === 'mts' ||
+ foundFileFormat === 'cts'
+ ) {
+ const [importOrRequireResult, moduleSystem] = await importOrRequireFile(configFile);
+ const configModule = await importOrRequireResult;
+
+ if (
+ moduleSystem === 'esm' ||
+ (configModule && '__esModule' in configModule && 'default' in configModule)
+ ) {
+ connectionOptions = configModule.default;
+ } else {
+ connectionOptions = configModule;
+ }
+ } else if (foundFileFormat === 'json') {
+ connectionOptions = require(configFile);
+ }
+
+ // normalize and return connection options
+ if (connectionOptions) {
+ return this.normalizeConnectionOptions(connectionOptions);
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Normalize connection options.
+ */
+ protected normalizeConnectionOptions(
+ connectionOptions: DataSourceOptions | DataSourceOptions[],
+ ): DataSourceOptions[] {
+ if (!Array.isArray(connectionOptions)) connectionOptions = [connectionOptions];
+
+ connectionOptions.forEach((options) => {
+ options.baseDirectory = this.baseDirectory;
+ if (options.entities) {
+ const entities = (options.entities as any[]).map((entity) => {
+ if (typeof entity === 'string' && entity.substr(0, 1) !== '/')
+ return this.baseDirectory + '/' + entity;
+
+ return entity;
+ });
+ Object.assign(connectionOptions, { entities: entities });
+ }
+ if (options.subscribers) {
+ const subscribers = (options.subscribers as any[]).map((subscriber) => {
+ if (typeof subscriber === 'string' && subscriber.substr(0, 1) !== '/')
+ return this.baseDirectory + '/' + subscriber;
+
+ return subscriber;
+ });
+ Object.assign(connectionOptions, { subscribers: subscribers });
+ }
+ if (options.migrations) {
+ const migrations = (options.migrations as any[]).map((migration) => {
+ if (typeof migration === 'string' && migration.substr(0, 1) !== '/')
+ return this.baseDirectory + '/' + migration;
+
+ return migration;
+ });
+ Object.assign(connectionOptions, { migrations: migrations });
+ }
+
+ // make database path file in sqlite relative to package.json
+ if (options.type === 'sqlite' || options.type === 'sqlite-pooled') {
+ if (
+ typeof options.database === 'string' &&
+ !isAbsolute(options.database) &&
+ options.database.substr(0, 1) !== '/' && // unix absolute
+ options.database.substr(1, 2) !== ':\\' && // windows absolute
+ options.database !== ':memory:'
+ ) {
+ Object.assign(options, {
+ database: this.baseDirectory + '/' + options.database,
+ });
+ }
+ }
+ });
+
+ return connectionOptions;
+ }
+
+ /**
+ * Gets directory where configuration file should be located and configuration file name.
+ */
+ protected get baseFilePath(): string {
+ return path.resolve(this.baseDirectory, this.baseConfigName);
+ }
+
+ /**
+ * Gets directory where configuration file should be located.
+ */
+ protected get baseDirectory(): string {
+ if (this.options && this.options.root) return this.options.root;
+
+ return appRootPath.path;
+ }
+
+ /**
+ * Gets configuration file name.
+ */
+ protected get baseConfigName(): string {
+ if (this.options && this.options.configName) return this.options.configName;
+
+ return 'ormconfig';
+ }
+}
diff --git a/packages/@n8n/typeorm/src/connection/options-reader/ConnectionOptionsEnvReader.ts b/packages/@n8n/typeorm/src/connection/options-reader/ConnectionOptionsEnvReader.ts
new file mode 100644
index 00000000000..7312ac53fd8
--- /dev/null
+++ b/packages/@n8n/typeorm/src/connection/options-reader/ConnectionOptionsEnvReader.ts
@@ -0,0 +1,108 @@
+import { DataSourceOptions } from '../../data-source/DataSourceOptions';
+import { PlatformTools } from '../../platform/PlatformTools';
+import { OrmUtils } from '../../util/OrmUtils';
+
+/**
+ * Reads connection options from environment variables.
+ * Environment variables can have only a single connection.
+ * Its strongly required to define TYPEORM_CONNECTION env variable.
+ *
+ * @deprecated
+ */
+export class ConnectionOptionsEnvReader {
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Reads connection options from environment variables.
+ */
+ async read(): Promise {
+ return [
+ {
+ type:
+ PlatformTools.getEnvVariable('TYPEORM_CONNECTION') ||
+ (PlatformTools.getEnvVariable('TYPEORM_URL')
+ ? PlatformTools.getEnvVariable('TYPEORM_URL').split('://')[0]
+ : undefined),
+ url: PlatformTools.getEnvVariable('TYPEORM_URL'),
+ host: PlatformTools.getEnvVariable('TYPEORM_HOST'),
+ port: this.stringToNumber(PlatformTools.getEnvVariable('TYPEORM_PORT')),
+ username: PlatformTools.getEnvVariable('TYPEORM_USERNAME'),
+ password: PlatformTools.getEnvVariable('TYPEORM_PASSWORD'),
+ database: PlatformTools.getEnvVariable('TYPEORM_DATABASE'),
+ schema: PlatformTools.getEnvVariable('TYPEORM_SCHEMA'),
+ extra: PlatformTools.getEnvVariable('TYPEORM_DRIVER_EXTRA')
+ ? JSON.parse(PlatformTools.getEnvVariable('TYPEORM_DRIVER_EXTRA'))
+ : undefined,
+ synchronize: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_SYNCHRONIZE')),
+ dropSchema: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_DROP_SCHEMA')),
+ migrationsRun: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS_RUN')),
+ entities: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_ENTITIES')),
+ migrations: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS')),
+ migrationsTableName: PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS_TABLE_NAME'),
+ metadataTableName: PlatformTools.getEnvVariable('TYPEORM_METADATA_TABLE_NAME'),
+ subscribers: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_SUBSCRIBERS')),
+ logging: this.transformLogging(PlatformTools.getEnvVariable('TYPEORM_LOGGING')),
+ logger: PlatformTools.getEnvVariable('TYPEORM_LOGGER'),
+ entityPrefix: PlatformTools.getEnvVariable('TYPEORM_ENTITY_PREFIX'),
+ maxQueryExecutionTime: PlatformTools.getEnvVariable('TYPEORM_MAX_QUERY_EXECUTION_TIME'),
+ cache: this.transformCaching(),
+ uuidExtension: PlatformTools.getEnvVariable('TYPEORM_UUID_EXTENSION'),
+ },
+ ];
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Transforms logging string into real logging value connection requires.
+ */
+ protected transformLogging(logging: string): any {
+ if (logging === 'true' || logging === 'TRUE' || logging === '1') return true;
+ if (logging === 'all') return 'all';
+
+ return this.stringToArray(logging);
+ }
+
+ /**
+ * Transforms caching option into real caching value option requires.
+ */
+ protected transformCaching(): boolean | object | undefined {
+ const caching = PlatformTools.getEnvVariable('TYPEORM_CACHE');
+ if (caching === 'true' || caching === 'TRUE' || caching === '1') return true;
+ if (caching === 'false' || caching === 'FALSE' || caching === '0') return false;
+ if (caching === 'database')
+ return {
+ type: caching,
+ options: PlatformTools.getEnvVariable('TYPEORM_CACHE_OPTIONS')
+ ? JSON.parse(PlatformTools.getEnvVariable('TYPEORM_CACHE_OPTIONS'))
+ : undefined,
+ alwaysEnabled: PlatformTools.getEnvVariable('TYPEORM_CACHE_ALWAYS_ENABLED'),
+ duration: parseInt(PlatformTools.getEnvVariable('TYPEORM_CACHE_DURATION')),
+ };
+
+ return undefined;
+ }
+
+ /**
+ * Converts a string which contains multiple elements split by comma into a string array of strings.
+ */
+ protected stringToArray(variable?: string) {
+ if (!variable) return [];
+ return variable.split(',').map((str) => str.trim());
+ }
+
+ /**
+ * Converts a string which contains a number into a javascript number
+ */
+ private stringToNumber(value: any): number | undefined {
+ if (!value) {
+ return undefined;
+ }
+
+ return parseInt(value);
+ }
+}
diff --git a/packages/@n8n/typeorm/src/container.ts b/packages/@n8n/typeorm/src/container.ts
new file mode 100644
index 00000000000..875eaf29ad6
--- /dev/null
+++ b/packages/@n8n/typeorm/src/container.ts
@@ -0,0 +1,83 @@
+/**
+ * Container options.
+ *
+ * @deprecated
+ */
+export interface UseContainerOptions {
+ /**
+ * If set to true, then default container will be used in the case if given container haven't returned anything.
+ */
+ fallback?: boolean;
+
+ /**
+ * If set to true, then default container will be used in the case if given container thrown an exception.
+ */
+ fallbackOnErrors?: boolean;
+}
+
+/**
+ * @deprecated
+ */
+export type ContainedType = { new (...args: any[]): T } | Function;
+
+/**
+ * @deprecated
+ */
+export interface ContainerInterface {
+ get(someClass: ContainedType): T;
+}
+
+/**
+ * Container to be used by this library for inversion control. If container was not implicitly set then by default
+ * container simply creates a new instance of the given class.
+ *
+ * @deprecated
+ */
+const defaultContainer: ContainerInterface = new (class implements ContainerInterface {
+ private instances: { type: Function; object: any }[] = [];
+
+ get(someClass: ContainedType): T {
+ let instance = this.instances.find((i) => i.type === someClass);
+ if (!instance) {
+ instance = {
+ type: someClass,
+ object: new (someClass as new () => T)(),
+ };
+ this.instances.push(instance);
+ }
+
+ return instance.object;
+ }
+})();
+
+let userContainer: ContainerInterface;
+let userContainerOptions: UseContainerOptions | undefined;
+
+/**
+ * Sets container to be used by this library.
+ *
+ * @deprecated
+ */
+export function useContainer(iocContainer: ContainerInterface, options?: UseContainerOptions) {
+ userContainer = iocContainer;
+ userContainerOptions = options;
+}
+
+/**
+ * Gets the IOC container used by this library.
+ *
+ * @deprecated
+ */
+export function getFromContainer(someClass: ContainedType): T {
+ if (userContainer) {
+ try {
+ const instance = userContainer.get(someClass);
+ if (instance) return instance;
+
+ if (!userContainerOptions || !userContainerOptions.fallback) return instance;
+ } catch (error) {
+ if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error;
+ }
+ }
+ return defaultContainer.get(someClass);
+}
diff --git a/packages/@n8n/typeorm/src/data-source/BaseDataSourceOptions.ts b/packages/@n8n/typeorm/src/data-source/BaseDataSourceOptions.ts
new file mode 100644
index 00000000000..44fd98c490e
--- /dev/null
+++ b/packages/@n8n/typeorm/src/data-source/BaseDataSourceOptions.ts
@@ -0,0 +1,201 @@
+import { EntitySchema } from '../entity-schema/EntitySchema';
+import { LoggerOptions } from '../logger/LoggerOptions';
+import { NamingStrategyInterface } from '../naming-strategy/NamingStrategyInterface';
+import { DatabaseType } from '../driver/types/DatabaseType';
+import { Logger } from '../logger/Logger';
+import { DataSource } from '../data-source/DataSource';
+import { QueryResultCache } from '../cache/QueryResultCache';
+import { MixedList } from '../common/MixedList';
+
+/**
+ * BaseDataSourceOptions is set of DataSourceOptions shared by all database types.
+ */
+export interface BaseDataSourceOptions {
+ /**
+ * Database type. This value is required.
+ */
+ readonly type: DatabaseType;
+
+ /**
+ * Connection name. If connection name is not given then it will be called "default".
+ * Different connections must have different names.
+ *
+ * @deprecated
+ */
+ readonly name?: string;
+
+ /**
+ * Entities to be loaded for this connection.
+ * Accepts both entity classes and directories where from entities need to be loaded.
+ * Directories support glob patterns.
+ */
+ readonly entities?: MixedList;
+
+ /**
+ * Subscribers to be loaded for this connection.
+ * Accepts both subscriber classes and directories where from subscribers need to be loaded.
+ * Directories support glob patterns.
+ */
+ readonly subscribers?: MixedList;
+
+ /**
+ * Migrations to be loaded for this connection.
+ * Accepts both migration classes and glob patterns representing migration files.
+ */
+ readonly migrations?: MixedList;
+
+ /**
+ * Migrations table name, in case of different name from "migrations".
+ * Accepts single string name.
+ */
+ readonly migrationsTableName?: string;
+
+ /**
+ * Transaction mode for migrations to run in
+ */
+ readonly migrationsTransactionMode?: 'all' | 'none' | 'each';
+
+ /**
+ * Typeorm metadata table name, in case of different name from "typeorm_metadata".
+ * Accepts single string name.
+ */
+ readonly metadataTableName?: string;
+
+ /**
+ * Naming strategy to be used to name tables and columns in the database.
+ */
+ readonly namingStrategy?: NamingStrategyInterface;
+
+ /**
+ * Logging options.
+ */
+ readonly logging?: LoggerOptions;
+
+ /**
+ * Logger instance used to log queries and events in the ORM.
+ */
+ readonly logger?: 'advanced-console' | 'simple-console' | 'file' | 'debug' | Logger;
+
+ /**
+ * Maximum number of milliseconds query should be executed before logger log a warning.
+ */
+ readonly maxQueryExecutionTime?: number;
+
+ /**
+ * Maximum number of clients the pool should contain.
+ */
+ readonly poolSize?: number;
+
+ /**
+ * Indicates if database schema should be auto created on every application launch.
+ * Be careful with this option and don't use this in production - otherwise you can lose production data.
+ * This option is useful during debug and development.
+ * Alternative to it, you can use CLI and run schema:sync command.
+ *
+ * Note that for MongoDB database it does not create schema, because MongoDB is schemaless.
+ * Instead, it syncs just by creating indices.
+ */
+ readonly synchronize?: boolean;
+
+ /**
+ * Indicates if migrations should be auto run on every application launch.
+ * Alternative to it, you can use CLI and run migrations:run command.
+ */
+ readonly migrationsRun?: boolean;
+
+ /**
+ * Drops the schema each time connection is being established.
+ * Be careful with this option and don't use this in production - otherwise you'll lose all production data.
+ * This option is useful during debug and development.
+ */
+ readonly dropSchema?: boolean;
+
+ /**
+ * Prefix to use on all tables (collections) of this connection in the database.
+ */
+ readonly entityPrefix?: string;
+
+ /**
+ * When creating new Entity instances, skip all constructors when true.
+ */
+ readonly entitySkipConstructor?: boolean;
+
+ /**
+ * Extra connection options to be passed to the underlying driver.
+ *
+ * todo: deprecate this and move all database-specific types into hts own connection options object.
+ */
+ readonly extra?: any;
+
+ /**
+ * Specifies how relations must be loaded - using "joins" or separate queries.
+ * If you are loading too much data with nested joins it's better to load relations
+ * using separate queries.
+ *
+ * Default strategy is "join", but this default can be changed here.
+ * Also, strategy can be set per-query in FindOptions and QueryBuilder.
+ */
+ readonly relationLoadStrategy?: 'join' | 'query';
+
+ /**
+ * Optionally applied "typename" to the model.
+ * If set, then each hydrated model will have this property with the target model / entity name inside.
+ *
+ * (works like a discriminator property).
+ */
+ readonly typename?: string;
+
+ /**
+ * Holds reference to the baseDirectory where configuration file are expected.
+ *
+ * @internal
+ */
+ baseDirectory?: string;
+
+ /**
+ * Allows to setup cache options.
+ */
+ readonly cache?:
+ | boolean
+ | {
+ /**
+ * Type of caching.
+ *
+ * - "database" means cached values will be stored in the separate table in database. This is default value.
+ */
+ readonly type?: 'database';
+
+ /**
+ * Factory function for custom cache providers that implement QueryResultCache.
+ */
+ readonly provider?: (connection: DataSource) => QueryResultCache;
+
+ /**
+ * Configurable table name for "database" type cache.
+ * Default value is "query-result-cache"
+ */
+ readonly tableName?: string;
+
+ /**
+ * If set to true then queries (using find methods and QueryBuilder's methods) will always be cached.
+ */
+ readonly alwaysEnabled?: boolean;
+
+ /**
+ * Time in milliseconds in which cache will expire.
+ * This can be setup per-query.
+ * Default value is 1000 which is equivalent to 1 second.
+ */
+ readonly duration?: number;
+
+ /**
+ * Used to specify if cache errors should be ignored, and pass through the call to the Database.
+ */
+ readonly ignoreErrors?: boolean;
+ };
+
+ /**
+ * Allows automatic isolation of where clauses
+ */
+ readonly isolateWhereStatements?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/data-source/DataSource.ts b/packages/@n8n/typeorm/src/data-source/DataSource.ts
new file mode 100644
index 00000000000..5772b1f59d2
--- /dev/null
+++ b/packages/@n8n/typeorm/src/data-source/DataSource.ts
@@ -0,0 +1,632 @@
+import { Driver } from '../driver/Driver';
+import { registerQueryBuilders } from '../query-builder';
+import { Repository } from '../repository/Repository';
+import { EntitySubscriberInterface } from '../subscriber/EntitySubscriberInterface';
+import { EntityTarget } from '../common/EntityTarget';
+import { ObjectType } from '../common/ObjectType';
+import { EntityManager } from '../entity-manager/EntityManager';
+import { DefaultNamingStrategy } from '../naming-strategy/DefaultNamingStrategy';
+import {
+ CannotConnectAlreadyConnectedError,
+ CannotExecuteNotConnectedError,
+ EntityMetadataNotFoundError,
+ QueryRunnerProviderAlreadyReleasedError,
+ TypeORMError,
+} from '../error';
+import { TreeRepository } from '../repository/TreeRepository';
+import { NamingStrategyInterface } from '../naming-strategy/NamingStrategyInterface';
+import { EntityMetadata } from '../metadata/EntityMetadata';
+import { Logger } from '../logger/Logger';
+import { MigrationInterface } from '../migration/MigrationInterface';
+import { MigrationExecutor } from '../migration/MigrationExecutor';
+import { Migration } from '../migration/Migration';
+import { EntityMetadataValidator } from '../metadata-builder/EntityMetadataValidator';
+import { DataSourceOptions } from './DataSourceOptions';
+import { EntityManagerFactory } from '../entity-manager/EntityManagerFactory';
+import { DriverFactory } from '../driver/DriverFactory';
+import { ConnectionMetadataBuilder } from '../connection/ConnectionMetadataBuilder';
+import { QueryRunner } from '../query-runner/QueryRunner';
+import { SelectQueryBuilder } from '../query-builder/SelectQueryBuilder';
+import { LoggerFactory } from '../logger/LoggerFactory';
+import { QueryResultCacheFactory } from '../cache/QueryResultCacheFactory';
+import { QueryResultCache } from '../cache/QueryResultCache';
+import { RelationLoader } from '../query-builder/RelationLoader';
+import { ObjectUtils } from '../util/ObjectUtils';
+import { IsolationLevel } from '../driver/types/IsolationLevel';
+import { ReplicationMode } from '../driver/types/ReplicationMode';
+import { RelationIdLoader } from '../query-builder/RelationIdLoader';
+import { DriverUtils } from '../driver/DriverUtils';
+import { InstanceChecker } from '../util/InstanceChecker';
+import { ObjectLiteral } from '../common/ObjectLiteral';
+
+registerQueryBuilders();
+
+/**
+ * DataSource is a pre-defined connection configuration to a specific database.
+ * You can have multiple data sources connected (with multiple connections in it),
+ * connected to multiple databases in your application.
+ *
+ * Before, it was called `Connection`, but now `Connection` is deprecated
+ * because `Connection` isn't the best name for what it's actually is.
+ */
+export class DataSource {
+ readonly '@instanceof' = Symbol.for('DataSource');
+
+ // -------------------------------------------------------------------------
+ // Public Readonly Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connection name.
+ *
+ * @deprecated we don't need names anymore since we are going to drop all related methods relying on this property.
+ */
+ readonly name: string;
+
+ /**
+ * Connection options.
+ */
+ readonly options: DataSourceOptions;
+
+ /**
+ * Indicates if DataSource is initialized or not.
+ */
+ readonly isInitialized: boolean;
+
+ /**
+ * Database driver used by this connection.
+ */
+ driver: Driver;
+
+ /**
+ * EntityManager of this connection.
+ */
+ readonly manager: EntityManager;
+
+ /**
+ * Naming strategy used in the connection.
+ */
+ namingStrategy: NamingStrategyInterface;
+
+ /**
+ * Name for the metadata table
+ */
+ readonly metadataTableName: string;
+
+ /**
+ * Logger used to log orm events.
+ */
+ logger: Logger;
+
+ /**
+ * Migration instances that are registered for this connection.
+ */
+ readonly migrations: MigrationInterface[] = [];
+
+ /**
+ * Entity subscriber instances that are registered for this connection.
+ */
+ readonly subscribers: EntitySubscriberInterface[] = [];
+
+ /**
+ * All entity metadatas that are registered for this connection.
+ */
+ readonly entityMetadatas: EntityMetadata[] = [];
+
+ /**
+ * All entity metadatas that are registered for this connection.
+ * This is a copy of #.entityMetadatas property -> used for more performant searches.
+ */
+ readonly entityMetadatasMap = new Map, EntityMetadata>();
+
+ /**
+ * Used to work with query result cache.
+ */
+ queryResultCache?: QueryResultCache;
+
+ /**
+ * Used to load relations and work with lazy relations.
+ */
+ readonly relationLoader: RelationLoader;
+
+ readonly relationIdLoader: RelationIdLoader;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(options: DataSourceOptions) {
+ registerQueryBuilders();
+ this.name = options.name || 'default';
+ this.options = options;
+ this.logger = new LoggerFactory().create(this.options.logger, this.options.logging);
+ this.manager = this.createEntityManager();
+ this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy();
+ this.metadataTableName = options.metadataTableName || 'typeorm_metadata';
+ this.relationLoader = new RelationLoader(this);
+ this.relationIdLoader = new RelationIdLoader(this);
+ this.isInitialized = false;
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Accessors
+ // -------------------------------------------------------------------------
+
+ /**
+ Indicates if DataSource is initialized or not.
+ *
+ * @deprecated use .isInitialized instead
+ */
+ get isConnected() {
+ return this.isInitialized;
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+ /**
+ * Updates current connection options with provided options.
+ */
+ setOptions(options: Partial): this {
+ Object.assign(this.options, options);
+
+ if (options.logger || options.logging) {
+ this.logger = new LoggerFactory().create(
+ options.logger || this.options.logger,
+ options.logging || this.options.logging,
+ );
+ }
+
+ if (options.namingStrategy) {
+ this.namingStrategy = options.namingStrategy;
+ }
+
+ if (options.cache) {
+ this.queryResultCache = new QueryResultCacheFactory(this).create();
+ }
+
+ // todo: we must update the database in the driver as well, if it was set by setOptions method
+ // in the future we need to refactor the code and remove "database" from the driver, and instead
+ // use database (and options) from a single place - data source.
+ if (options.database) {
+ this.driver.database = DriverUtils.buildDriverOptions(this.options).database;
+ }
+
+ // todo: need to take a look if we need to update schema and other "poor" properties
+
+ return this;
+ }
+
+ /**
+ * Performs connection to the database.
+ * This method should be called once on application bootstrap.
+ * This method not necessarily creates database connection (depend on database type),
+ * but it also can setup a connection pool with database to use.
+ */
+ async initialize(): Promise {
+ if (this.isInitialized) throw new CannotConnectAlreadyConnectedError(this.name);
+
+ this.driver = await DriverFactory.create(this);
+ this.queryResultCache = this.options.cache
+ ? new QueryResultCacheFactory(this).create()
+ : undefined;
+
+ // connect to the database via its driver
+ await this.driver.connect();
+
+ // connect to the cache-specific database if cache is enabled
+ if (this.queryResultCache) await this.queryResultCache.connect();
+
+ // set connected status for the current connection
+ ObjectUtils.assign(this, { isInitialized: true });
+
+ try {
+ // build all metadatas registered in the current connection
+ await this.buildMetadatas();
+
+ await this.driver.afterConnect();
+
+ // if option is set - drop schema once connection is done
+ if (this.options.dropSchema) await this.dropDatabase();
+
+ // if option is set - automatically synchronize a schema
+ if (this.options.migrationsRun)
+ await this.runMigrations({
+ transaction: this.options.migrationsTransactionMode,
+ });
+
+ // if option is set - automatically synchronize a schema
+ if (this.options.synchronize) await this.synchronize();
+ } catch (error) {
+ // if for some reason build metadata fail (for example validation error during entity metadata check)
+ // connection needs to be closed
+ await this.destroy();
+ throw error;
+ }
+
+ return this;
+ }
+
+ /**
+ * Performs connection to the database.
+ * This method should be called once on application bootstrap.
+ * This method not necessarily creates database connection (depend on database type),
+ * but it also can setup a connection pool with database to use.
+ *
+ * @deprecated use .initialize method instead
+ */
+ async connect(): Promise {
+ return this.initialize();
+ }
+
+ /**
+ * Closes connection with the database.
+ * Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
+ */
+ async destroy(): Promise {
+ if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
+
+ await this.driver.disconnect();
+
+ // disconnect from the cache-specific database if cache was enabled
+ if (this.queryResultCache) await this.queryResultCache.disconnect();
+
+ ObjectUtils.assign(this, { isInitialized: false });
+ }
+
+ /**
+ * Closes connection with the database.
+ * Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
+ *
+ * @deprecated use .destroy method instead
+ */
+ async close(): Promise {
+ return this.destroy();
+ }
+
+ /**
+ * Creates database schema for all entities registered in this connection.
+ * Can be used only after connection to the database is established.
+ *
+ * @param dropBeforeSync If set to true then it drops the database with all its tables and data
+ */
+ async synchronize(dropBeforeSync: boolean = false): Promise {
+ if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
+
+ if (dropBeforeSync) await this.dropDatabase();
+
+ const schemaBuilder = this.driver.createSchemaBuilder();
+ await schemaBuilder.build();
+ }
+
+ /**
+ * Drops the database and all its data.
+ * Be careful with this method on production since this method will erase all your database tables and their data.
+ * Can be used only after connection to the database is established.
+ */
+ // TODO rename
+ async dropDatabase(): Promise {
+ const queryRunner = this.createQueryRunner();
+ try {
+ if (DriverUtils.isSQLiteFamily(this.driver)) {
+ const databases: string[] = [];
+ this.entityMetadatas.forEach((metadata) => {
+ if (metadata.database && databases.indexOf(metadata.database) === -1)
+ databases.push(metadata.database);
+ });
+ if (databases.length === 0 && this.driver.database) {
+ databases.push(this.driver.database);
+ }
+
+ if (databases.length === 0) {
+ await queryRunner.clearDatabase();
+ } else {
+ for (const database of databases) {
+ await queryRunner.clearDatabase(database);
+ }
+ }
+ } else {
+ await queryRunner.clearDatabase();
+ }
+ } finally {
+ await queryRunner.release();
+ }
+ }
+
+ /**
+ * Runs all pending migrations.
+ * Can be used only after connection to the database is established.
+ */
+ async runMigrations(options?: {
+ transaction?: 'all' | 'none' | 'each';
+ fake?: boolean;
+ }): Promise {
+ if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
+
+ const migrationExecutor = new MigrationExecutor(this);
+ migrationExecutor.transaction =
+ options?.transaction || this.options?.migrationsTransactionMode || 'all';
+ migrationExecutor.fake = (options && options.fake) || false;
+
+ const successMigrations = await migrationExecutor.executePendingMigrations();
+ return successMigrations;
+ }
+
+ /**
+ * Reverts last executed migration.
+ * Can be used only after connection to the database is established.
+ */
+ async undoLastMigration(options?: {
+ transaction?: 'all' | 'none' | 'each';
+ fake?: boolean;
+ }): Promise {
+ if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
+
+ const migrationExecutor = new MigrationExecutor(this);
+ migrationExecutor.transaction = (options && options.transaction) || 'all';
+ migrationExecutor.fake = (options && options.fake) || false;
+
+ await migrationExecutor.undoLastMigration();
+ }
+
+ /**
+ * Lists all migrations and whether they have been run.
+ * Returns true if there are pending migrations
+ */
+ async showMigrations(): Promise {
+ if (!this.isInitialized) {
+ throw new CannotExecuteNotConnectedError(this.name);
+ }
+ const migrationExecutor = new MigrationExecutor(this);
+ return await migrationExecutor.showMigrations();
+ }
+
+ /**
+ * Checks if entity metadata exist for the given entity class, target name or table name.
+ */
+ hasMetadata(target: EntityTarget): boolean {
+ return !!this.findMetadata(target);
+ }
+
+ /**
+ * Gets entity metadata for the given entity class or schema name.
+ */
+ getMetadata(target: EntityTarget): EntityMetadata {
+ const metadata = this.findMetadata(target);
+ if (!metadata) throw new EntityMetadataNotFoundError(target);
+
+ return metadata;
+ }
+
+ /**
+ * Gets repository for the given entity.
+ */
+ getRepository(target: EntityTarget): Repository {
+ return this.manager.getRepository(target);
+ }
+
+ /**
+ * Gets tree repository for the given entity class or name.
+ * Only tree-type entities can have a TreeRepository, like ones decorated with @Tree decorator.
+ */
+ getTreeRepository(
+ target: EntityTarget,
+ ): TreeRepository {
+ return this.manager.getTreeRepository(target);
+ }
+
+ /**
+ * Gets custom entity repository marked with @EntityRepository decorator.
+ *
+ * @deprecated use Repository.extend function to create a custom repository
+ */
+ getCustomRepository(customRepository: ObjectType): T {
+ return this.manager.getCustomRepository(customRepository);
+ }
+
+ /**
+ * Wraps given function execution (and all operations made there) into a transaction.
+ * All database operations must be executed using provided entity manager.
+ */
+ async transaction(runInTransaction: (entityManager: EntityManager) => Promise): Promise;
+ async transaction(
+ isolationLevel: IsolationLevel,
+ runInTransaction: (entityManager: EntityManager) => Promise,
+ ): Promise;
+ async transaction(
+ isolationOrRunInTransaction: IsolationLevel | ((entityManager: EntityManager) => Promise),
+ runInTransactionParam?: (entityManager: EntityManager) => Promise,
+ ): Promise {
+ return this.manager.transaction(
+ isolationOrRunInTransaction as any,
+ runInTransactionParam as any,
+ );
+ }
+
+ /**
+ * Executes raw SQL query and returns raw database results.
+ */
+ async query(query: string, parameters?: any[], queryRunner?: QueryRunner): Promise {
+ if (queryRunner && queryRunner.isReleased) throw new QueryRunnerProviderAlreadyReleasedError();
+
+ const usedQueryRunner = queryRunner || this.createQueryRunner();
+
+ try {
+ return await usedQueryRunner.query(query, parameters); // await is needed here because we are using finally
+ } finally {
+ if (!queryRunner) await usedQueryRunner.release();
+ }
+ }
+
+ /**
+ * Creates a new query builder that can be used to build a SQL query.
+ */
+ createQueryBuilder(
+ entityClass: EntityTarget,
+ alias: string,
+ queryRunner?: QueryRunner,
+ ): SelectQueryBuilder;
+
+ /**
+ * Creates a new query builder that can be used to build a SQL query.
+ */
+ createQueryBuilder(queryRunner?: QueryRunner): SelectQueryBuilder;
+
+ /**
+ * Creates a new query builder that can be used to build a SQL query.
+ */
+ createQueryBuilder(
+ entityOrRunner?: EntityTarget | QueryRunner,
+ alias?: string,
+ queryRunner?: QueryRunner,
+ ): SelectQueryBuilder {
+ if (alias) {
+ alias = DriverUtils.buildAlias(this.driver, undefined, alias);
+ const metadata = this.getMetadata(entityOrRunner as EntityTarget);
+ return new SelectQueryBuilder(this, queryRunner).select(alias).from(metadata.target, alias);
+ } else {
+ return new SelectQueryBuilder(this, entityOrRunner as QueryRunner | undefined);
+ }
+ }
+
+ /**
+ * Creates a query runner used for perform queries on a single database connection.
+ * Using query runners you can control your queries to execute using single database connection and
+ * manually control your database transaction.
+ *
+ * Mode is used in replication mode and indicates whatever you want to connect
+ * to master database or any of slave databases.
+ * If you perform writes you must use master database,
+ * if you perform reads you can use slave databases.
+ */
+ createQueryRunner(mode: ReplicationMode = 'master'): QueryRunner {
+ const queryRunner = this.driver.createQueryRunner(mode);
+ const manager = this.createEntityManager(queryRunner);
+ Object.assign(queryRunner, { manager: manager });
+ return queryRunner;
+ }
+
+ /**
+ * Gets entity metadata of the junction table (many-to-many table).
+ */
+ getManyToManyMetadata(entityTarget: EntityTarget, relationPropertyPath: string) {
+ const relationMetadata =
+ this.getMetadata(entityTarget).findRelationWithPropertyPath(relationPropertyPath);
+ if (!relationMetadata)
+ throw new TypeORMError(
+ `Relation "${relationPropertyPath}" was not found in ${entityTarget} entity.`,
+ );
+ if (!relationMetadata.isManyToMany)
+ throw new TypeORMError(
+ `Relation "${entityTarget}#${relationPropertyPath}" does not have a many-to-many relationship.` +
+ `You can use this method only on many-to-many relations.`,
+ );
+
+ return relationMetadata.junctionEntityMetadata;
+ }
+
+ /**
+ * Creates an Entity Manager for the current connection with the help of the EntityManagerFactory.
+ */
+ createEntityManager(queryRunner?: QueryRunner): EntityManager {
+ return new EntityManagerFactory().create(this, queryRunner);
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Finds exist entity metadata by the given entity class, target name or table name.
+ */
+ protected findMetadata(target: EntityTarget): EntityMetadata | undefined {
+ const metadataFromMap = this.entityMetadatasMap.get(target);
+ if (metadataFromMap) return metadataFromMap;
+
+ for (let [_, metadata] of this.entityMetadatasMap) {
+ if (InstanceChecker.isEntitySchema(target) && metadata.name === target.options.name) {
+ return metadata;
+ }
+ if (typeof target === 'string') {
+ if (target.indexOf('.') !== -1) {
+ if (metadata.tablePath === target) {
+ return metadata;
+ }
+ } else {
+ if (metadata.name === target || metadata.tableName === target) {
+ return metadata;
+ }
+ }
+ }
+ if (ObjectUtils.isObjectWithName(target) && typeof target.name === 'string') {
+ if (target.name.indexOf('.') !== -1) {
+ if (metadata.tablePath === target.name) {
+ return metadata;
+ }
+ } else {
+ if (metadata.name === target.name || metadata.tableName === target.name) {
+ return metadata;
+ }
+ }
+ }
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Builds metadatas for all registered classes inside this connection.
+ */
+ protected async buildMetadatas(): Promise {
+ const connectionMetadataBuilder = new ConnectionMetadataBuilder(this);
+ const entityMetadataValidator = new EntityMetadataValidator();
+
+ // create subscribers instances if they are not disallowed from high-level (for example they can disallowed from migrations run process)
+ const flattenedSubscribers = ObjectUtils.mixedListToArray(this.options.subscribers || []);
+ const subscribers = await connectionMetadataBuilder.buildSubscribers(flattenedSubscribers);
+ ObjectUtils.assign(this, { subscribers: subscribers });
+
+ // build entity metadatas
+ const flattenedEntities = ObjectUtils.mixedListToArray(this.options.entities || []);
+ const entityMetadatas = await connectionMetadataBuilder.buildEntityMetadatas(flattenedEntities);
+ ObjectUtils.assign(this, {
+ entityMetadatas: entityMetadatas,
+ entityMetadatasMap: new Map(entityMetadatas.map((metadata) => [metadata.target, metadata])),
+ });
+
+ // create migration instances
+ const flattenedMigrations = ObjectUtils.mixedListToArray(this.options.migrations || []);
+ const migrations = await connectionMetadataBuilder.buildMigrations(flattenedMigrations);
+ ObjectUtils.assign(this, { migrations: migrations });
+
+ // validate all created entity metadatas to make sure user created entities are valid and correct
+ entityMetadataValidator.validateMany(
+ this.entityMetadatas.filter((metadata) => metadata.tableType !== 'view'),
+ this.driver,
+ );
+
+ // set current data source to the entities
+ for (let entityMetadata of entityMetadatas) {
+ if (InstanceChecker.isBaseEntityConstructor(entityMetadata.target)) {
+ entityMetadata.target.useDataSource(this);
+ }
+ }
+ }
+
+ /**
+ * Get the replication mode SELECT queries should use for this datasource by default
+ */
+ defaultReplicationModeForReads(): ReplicationMode {
+ if ('replication' in this.driver.options) {
+ const value = (
+ this.driver.options.replication as {
+ defaultMode?: ReplicationMode;
+ }
+ ).defaultMode;
+ if (value) {
+ return value;
+ }
+ }
+ return 'slave';
+ }
+}
diff --git a/packages/@n8n/typeorm/src/data-source/DataSourceOptions.ts b/packages/@n8n/typeorm/src/data-source/DataSourceOptions.ts
new file mode 100644
index 00000000000..60cd28fadf8
--- /dev/null
+++ b/packages/@n8n/typeorm/src/data-source/DataSourceOptions.ts
@@ -0,0 +1,11 @@
+import { PostgresConnectionOptions } from '../driver/postgres/PostgresConnectionOptions';
+import { SqliteConnectionOptions } from '../driver/sqlite/SqliteConnectionOptions';
+import { SqlitePooledConnectionOptions } from '../driver/sqlite-pooled/SqlitePooledConnectionOptions';
+
+/**
+ * DataSourceOptions is an interface with settings and options for specific DataSource.
+ */
+export type DataSourceOptions =
+ | PostgresConnectionOptions
+ | SqliteConnectionOptions
+ | SqlitePooledConnectionOptions;
diff --git a/packages/@n8n/typeorm/src/data-source/index.ts b/packages/@n8n/typeorm/src/data-source/index.ts
new file mode 100644
index 00000000000..b6c0a3c5c2a
--- /dev/null
+++ b/packages/@n8n/typeorm/src/data-source/index.ts
@@ -0,0 +1,2 @@
+export * from './DataSource';
+export * from './DataSourceOptions';
diff --git a/packages/@n8n/typeorm/src/decorator/Check.ts b/packages/@n8n/typeorm/src/decorator/Check.ts
new file mode 100644
index 00000000000..c7c27511aaa
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/Check.ts
@@ -0,0 +1,40 @@
+import { getMetadataArgsStorage } from '../globals';
+import { CheckMetadataArgs } from '../metadata-args/CheckMetadataArgs';
+import { TypeORMError } from '../error';
+
+/**
+ * Creates a database check.
+ * Can be used on entity property or on entity.
+ * Can create checks with composite columns when used on entity.
+ */
+export function Check(expression: string): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database check.
+ * Can be used on entity property or on entity.
+ * Can create checks with composite columns when used on entity.
+ */
+export function Check(name: string, expression: string): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database check.
+ * Can be used on entity property or on entity.
+ * Can create checks with composite columns when used on entity.
+ */
+export function Check(
+ nameOrExpression: string,
+ maybeExpression?: string,
+): ClassDecorator & PropertyDecorator {
+ const name = maybeExpression ? nameOrExpression : undefined;
+ const expression = maybeExpression ? maybeExpression : nameOrExpression;
+
+ if (!expression) throw new TypeORMError(`Check expression is required`);
+
+ return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
+ getMetadataArgsStorage().checks.push({
+ target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
+ name: name,
+ expression: expression,
+ } as CheckMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/EntityRepository.ts b/packages/@n8n/typeorm/src/decorator/EntityRepository.ts
new file mode 100644
index 00000000000..fb3b6bc7aad
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/EntityRepository.ts
@@ -0,0 +1,19 @@
+import { getMetadataArgsStorage } from '../globals';
+import { EntityRepositoryMetadataArgs } from '../metadata-args/EntityRepositoryMetadataArgs';
+import { EntitySchema } from '../entity-schema/EntitySchema';
+
+/**
+ * Used to declare a class as a custom repository.
+ * Custom repository can manage some specific entity or just be generic.
+ * Custom repository optionally can extend AbstractRepository, Repository or TreeRepository.
+ *
+ * @deprecated use Repository.extend function to create a custom repository
+ */
+export function EntityRepository(entity?: Function | EntitySchema): ClassDecorator {
+ return function (target: Function) {
+ getMetadataArgsStorage().entityRepositories.push({
+ target: target,
+ entity: entity,
+ } as EntityRepositoryMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/Exclusion.ts b/packages/@n8n/typeorm/src/decorator/Exclusion.ts
new file mode 100644
index 00000000000..f8a810fd0d0
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/Exclusion.ts
@@ -0,0 +1,40 @@
+import { getMetadataArgsStorage } from '../globals';
+import { ExclusionMetadataArgs } from '../metadata-args/ExclusionMetadataArgs';
+import { TypeORMError } from '../error';
+
+/**
+ * Creates a database exclusion.
+ * Can be used on entity.
+ * Can create exclusions with composite columns when used on entity.
+ */
+export function Exclusion(expression: string): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database exclusion.
+ * Can be used on entity.
+ * Can create exclusions with composite columns when used on entity.
+ */
+export function Exclusion(name: string, expression: string): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database exclusion.
+ * Can be used on entity.
+ * Can create exclusions with composite columns when used on entity.
+ */
+export function Exclusion(
+ nameOrExpression: string,
+ maybeExpression?: string,
+): ClassDecorator & PropertyDecorator {
+ const name = maybeExpression ? nameOrExpression : undefined;
+ const expression = maybeExpression ? maybeExpression : nameOrExpression;
+
+ if (!expression) throw new TypeORMError(`Exclusion expression is required`);
+
+ return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
+ getMetadataArgsStorage().exclusions.push({
+ target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
+ name: name,
+ expression: expression,
+ } as ExclusionMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/Generated.ts b/packages/@n8n/typeorm/src/decorator/Generated.ts
new file mode 100644
index 00000000000..b9c3faa37fb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/Generated.ts
@@ -0,0 +1,23 @@
+import { getMetadataArgsStorage } from '../globals';
+import { GeneratedMetadataArgs } from '../metadata-args/GeneratedMetadataArgs';
+
+/**
+ * Marks a column to generate a value on entity insertion.
+ * There are three types of generation strategy - increment, uuid and rowid (cockroachdb only).
+ * Increment uses a number which increases by one on each insertion.
+ * Uuid generates a special UUID token.
+ * Rowid supports only in CockroachDB and uses `unique_rowid()` function
+ *
+ * Note, some databases do not support non-primary generation columns.
+ */
+export function Generated(
+ strategy: 'increment' | 'uuid' | 'rowid' = 'increment',
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().generations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ strategy: strategy,
+ } as GeneratedMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/Index.ts b/packages/@n8n/typeorm/src/decorator/Index.ts
new file mode 100644
index 00000000000..9268fd99d86
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/Index.ts
@@ -0,0 +1,122 @@
+import { getMetadataArgsStorage } from '../globals';
+import { IndexMetadataArgs } from '../metadata-args/IndexMetadataArgs';
+import { IndexOptions } from './options/IndexOptions';
+import { ObjectUtils } from '../util/ObjectUtils';
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(options?: IndexOptions): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(name: string, options?: IndexOptions): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(
+ name: string,
+ options: { synchronize: false },
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(
+ name: string,
+ fields: string[],
+ options?: IndexOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(fields: string[], options?: IndexOptions): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(
+ fields: (object?: any) => any[] | { [key: string]: number },
+ options?: IndexOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(
+ name: string,
+ fields: (object?: any) => any[] | { [key: string]: number },
+ options?: IndexOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Creates a database index.
+ * Can be used on entity property or on entity.
+ * Can create indices with composite columns when used on entity.
+ */
+export function Index(
+ nameOrFieldsOrOptions?:
+ | string
+ | string[]
+ | ((object: any) => any[] | { [key: string]: number })
+ | IndexOptions,
+ maybeFieldsOrOptions?:
+ | ((object?: any) => any[] | { [key: string]: number })
+ | IndexOptions
+ | string[]
+ | { synchronize: false },
+ maybeOptions?: IndexOptions,
+): ClassDecorator & PropertyDecorator {
+ // normalize parameters
+ const name = typeof nameOrFieldsOrOptions === 'string' ? nameOrFieldsOrOptions : undefined;
+ const fields =
+ typeof nameOrFieldsOrOptions === 'string'
+ ? <((object?: any) => any[] | { [key: string]: number }) | string[]>maybeFieldsOrOptions
+ : (nameOrFieldsOrOptions as string[]);
+ let options =
+ ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(nameOrFieldsOrOptions)
+ ? (nameOrFieldsOrOptions as IndexOptions)
+ : maybeOptions;
+ if (!options)
+ options =
+ ObjectUtils.isObject(maybeFieldsOrOptions) && !Array.isArray(maybeFieldsOrOptions)
+ ? (maybeFieldsOrOptions as IndexOptions)
+ : maybeOptions;
+
+ return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
+ getMetadataArgsStorage().indices.push({
+ target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
+ name: name,
+ columns: propertyName ? [propertyName] : fields,
+ synchronize:
+ options && (options as { synchronize: false }).synchronize === false ? false : true,
+ where: options ? options.where : undefined,
+ unique: options && options.unique ? true : false,
+ spatial: options && options.spatial ? true : false,
+ fulltext: options && options.fulltext ? true : false,
+ nullFiltered: options && options.nullFiltered ? true : false,
+ parser: options ? options.parser : undefined,
+ sparse: options && options.sparse ? true : false,
+ background: options && options.background ? true : false,
+ concurrent: options && options.concurrent ? true : false,
+ expireAfterSeconds: options ? options.expireAfterSeconds : undefined,
+ } as IndexMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/Unique.ts b/packages/@n8n/typeorm/src/decorator/Unique.ts
new file mode 100644
index 00000000000..779af9e78d8
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/Unique.ts
@@ -0,0 +1,93 @@
+import { getMetadataArgsStorage } from '../globals';
+import { UniqueMetadataArgs } from '../metadata-args/UniqueMetadataArgs';
+import { UniqueOptions } from './options/UniqueOptions';
+import { ObjectUtils } from '../util/ObjectUtils';
+
+/**
+ * Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
+ */
+export function Unique(
+ name: string,
+ fields: string[],
+ options?: UniqueOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
+ */
+export function Unique(
+ fields: string[],
+ options?: UniqueOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
+ */
+export function Unique(
+ fields: (object?: any) => any[] | { [key: string]: number },
+ options?: UniqueOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
+ */
+export function Unique(
+ name: string,
+ fields: (object?: any) => any[] | { [key: string]: number },
+ options?: UniqueOptions,
+): ClassDecorator & PropertyDecorator;
+
+/**
+ * Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
+ */
+export function Unique(
+ nameOrFieldsOrOptions?:
+ | string
+ | string[]
+ | ((object: any) => any[] | { [key: string]: number })
+ | UniqueOptions,
+ maybeFieldsOrOptions?:
+ | ((object?: any) => any[] | { [key: string]: number })
+ | string[]
+ | UniqueOptions,
+ maybeOptions?: UniqueOptions,
+): ClassDecorator & PropertyDecorator {
+ const name = typeof nameOrFieldsOrOptions === 'string' ? nameOrFieldsOrOptions : undefined;
+ const fields =
+ typeof nameOrFieldsOrOptions === 'string'
+ ? <((object?: any) => any[] | { [key: string]: number }) | string[]>maybeFieldsOrOptions
+ : (nameOrFieldsOrOptions as string[]);
+ let options =
+ ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(nameOrFieldsOrOptions)
+ ? (nameOrFieldsOrOptions as UniqueOptions)
+ : maybeOptions;
+ if (!options)
+ options =
+ ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(maybeFieldsOrOptions)
+ ? (maybeFieldsOrOptions as UniqueOptions)
+ : maybeOptions;
+
+ return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
+ let columns = fields;
+
+ if (propertyName !== undefined) {
+ switch (typeof propertyName) {
+ case 'string':
+ columns = [propertyName];
+ break;
+
+ case 'symbol':
+ columns = [propertyName.toString()];
+ break;
+ }
+ }
+
+ const args: UniqueMetadataArgs = {
+ target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
+ name: name,
+ columns,
+ deferrable: options ? options.deferrable : undefined,
+ };
+ getMetadataArgsStorage().uniques.push(args);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/Column.ts b/packages/@n8n/typeorm/src/decorator/columns/Column.ts
new file mode 100644
index 00000000000..ab837f68588
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/Column.ts
@@ -0,0 +1,200 @@
+import { getMetadataArgsStorage } from '../../globals';
+import {
+ ColumnType,
+ SimpleColumnType,
+ SpatialColumnType,
+ WithLengthColumnType,
+ WithPrecisionColumnType,
+ WithWidthColumnType,
+} from '../../driver/types/ColumnTypes';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnCommonOptions } from '../options/ColumnCommonOptions';
+import { SpatialColumnOptions } from '../options/SpatialColumnOptions';
+import { ColumnWithLengthOptions } from '../options/ColumnWithLengthOptions';
+import { ColumnNumericOptions } from '../options/ColumnNumericOptions';
+import { ColumnEnumOptions } from '../options/ColumnEnumOptions';
+import { ColumnEmbeddedOptions } from '../options/ColumnEmbeddedOptions';
+import { EmbeddedMetadataArgs } from '../../metadata-args/EmbeddedMetadataArgs';
+import { ColumnTypeUndefinedError } from '../../error/ColumnTypeUndefinedError';
+import { ColumnHstoreOptions } from '../options/ColumnHstoreOptions';
+import { ColumnWithWidthOptions } from '../options/ColumnWithWidthOptions';
+import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * 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.
+ */
+export function Column(): 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.
+ */
+export function Column(options: ColumnOptions): 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.
+ */
+export function Column(type: SimpleColumnType, options?: ColumnCommonOptions): 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.
+ */
+export function Column(
+ type: SpatialColumnType,
+ options?: ColumnCommonOptions & SpatialColumnOptions,
+): 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.
+ */
+export function Column(
+ type: WithLengthColumnType,
+ options?: ColumnCommonOptions & ColumnWithLengthOptions,
+): 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.
+ */
+export function Column(
+ type: WithWidthColumnType,
+ options?: ColumnCommonOptions & ColumnWithWidthOptions,
+): 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.
+ */
+export function Column(
+ type: WithPrecisionColumnType,
+ options?: ColumnCommonOptions & ColumnNumericOptions,
+): 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.
+ */
+export function Column(
+ type: 'enum',
+ options?: ColumnCommonOptions & ColumnEnumOptions,
+): 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.
+ */
+export function Column(
+ type: 'simple-enum',
+ options?: ColumnCommonOptions & ColumnEnumOptions,
+): 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.
+ */
+export function Column(
+ type: 'set',
+ options?: ColumnCommonOptions & ColumnEnumOptions,
+): 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.
+ */
+export function Column(
+ type: 'hstore',
+ options?: ColumnCommonOptions & ColumnHstoreOptions,
+): 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.
+ *
+ * Property in entity can be marked as Embedded, and on persist all columns from the embedded are mapped to the
+ * single table of the entity where Embedded is used. And on hydration all columns which supposed to be in the
+ * embedded will be mapped to it from the single table.
+ */
+export function Column(
+ type: (type?: any) => Function,
+ options?: ColumnEmbeddedOptions,
+): 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.
+ */
+export function Column(
+ typeOrOptions?: ((type?: any) => Function) | ColumnType | (ColumnOptions & ColumnEmbeddedOptions),
+ options?: ColumnOptions & ColumnEmbeddedOptions,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ // normalize parameters
+ let type: ColumnType | undefined;
+ if (typeof typeOrOptions === 'string' || typeof typeOrOptions === 'function') {
+ type = typeOrOptions;
+ } else if (typeOrOptions) {
+ options = typeOrOptions;
+ type = typeOrOptions.type;
+ }
+ if (!options) options = {} as ColumnOptions;
+
+ // if type is not given explicitly then try to guess it
+ const reflectMetadataType =
+ Reflect && (Reflect as any).getMetadata
+ ? (Reflect as any).getMetadata('design:type', object, propertyName)
+ : undefined;
+ if (!type && reflectMetadataType)
+ // if type is not given explicitly then try to guess it
+ type = reflectMetadataType;
+
+ // check if there is no type in column options then set type from first function argument, or guessed one
+ if (!options.type && type) options.type = type;
+
+ // specify HSTORE type if column is HSTORE
+ if (options.type === 'hstore' && !options.hstoreType)
+ options.hstoreType = reflectMetadataType === Object ? 'object' : 'string';
+
+ if (typeof typeOrOptions === 'function') {
+ // register an embedded
+ getMetadataArgsStorage().embeddeds.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ isArray: reflectMetadataType === Array || options.array === true,
+ prefix: options.prefix !== undefined ? options.prefix : undefined,
+ type: typeOrOptions as (type?: any) => Function,
+ } as EmbeddedMetadataArgs);
+ } else {
+ // register a regular column
+
+ // if we still don't have a type then we need to give error to user that type is required
+ if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
+
+ // create unique
+ if (options.unique === true)
+ getMetadataArgsStorage().uniques.push({
+ target: object.constructor,
+ columns: [propertyName],
+ });
+
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'regular',
+ options: options,
+ } as ColumnMetadataArgs);
+
+ if (options.generated) {
+ getMetadataArgsStorage().generations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ strategy: typeof options.generated === 'string' ? options.generated : 'increment',
+ } as GeneratedMetadataArgs);
+ }
+ }
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/CreateDateColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/CreateDateColumn.ts
new file mode 100644
index 00000000000..3495c59dce0
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/CreateDateColumn.ts
@@ -0,0 +1,19 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * This column will store a creation date of the inserted object.
+ * Creation date is generated and inserted only once,
+ * at the first time when you create an object, the value is inserted into the table, and is never touched again.
+ */
+export function CreateDateColumn(options?: ColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'createDate',
+ options: options || {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/DeleteDateColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/DeleteDateColumn.ts
new file mode 100644
index 00000000000..a28dfe8b40f
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/DeleteDateColumn.ts
@@ -0,0 +1,18 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * This column will store a delete date of the soft-deleted object.
+ * This date is being updated each time you soft-delete the object.
+ */
+export function DeleteDateColumn(options?: ColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'deleteDate',
+ options: options || {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/ObjectIdColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/ObjectIdColumn.ts
new file mode 100644
index 00000000000..b1d84762b70
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/ObjectIdColumn.ts
@@ -0,0 +1,24 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * Special type of column that is available only for MongoDB database.
+ * Marks your entity's column to be an object id.
+ */
+export function ObjectIdColumn(options?: ColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ // if column options are not given then create a new empty options
+ if (!options) options = {} as ColumnOptions;
+ options.primary = true;
+ if (!options.name) options.name = '_id';
+
+ // create and register a new column metadata
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'objectId',
+ options: options,
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/PrimaryColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/PrimaryColumn.ts
new file mode 100644
index 00000000000..fcf56b146c6
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/PrimaryColumn.ts
@@ -0,0 +1,88 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnTypeUndefinedError } from '../../error/ColumnTypeUndefinedError';
+import { PrimaryColumnCannotBeNullableError } from '../../error/PrimaryColumnCannotBeNullableError';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+import { ColumnType } from '../../driver/types/ColumnTypes';
+
+/**
+ * Describes all primary key column's options.
+ * If specified, the nullable field must be set to false.
+ */
+export type PrimaryColumnOptions = ColumnOptions & { nullable?: false };
+
+/**
+ * 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.
+ * Primary columns also creates a PRIMARY KEY for this column in a db.
+ */
+export function PrimaryColumn(options?: PrimaryColumnOptions): 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.
+ * Primary columns also creates a PRIMARY KEY for this column in a db.
+ */
+export function PrimaryColumn(type?: ColumnType, options?: PrimaryColumnOptions): 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.
+ * Primary columns also creates a PRIMARY KEY for this column in a db.
+ */
+export function PrimaryColumn(
+ typeOrOptions?: ColumnType | PrimaryColumnOptions,
+ options?: PrimaryColumnOptions,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ // normalize parameters
+ let type: ColumnType | undefined;
+ if (
+ typeof typeOrOptions === 'string' ||
+ typeOrOptions === String ||
+ typeOrOptions === Boolean ||
+ typeOrOptions === Number
+ ) {
+ type = typeOrOptions as ColumnType;
+ } else {
+ options = Object.assign({}, typeOrOptions);
+ }
+ if (!options) options = {} as PrimaryColumnOptions;
+
+ // if type is not given explicitly then try to guess it
+ const reflectMetadataType =
+ Reflect && (Reflect as any).getMetadata
+ ? (Reflect as any).getMetadata('design:type', object, propertyName)
+ : undefined;
+ if (!type && reflectMetadataType) type = reflectMetadataType;
+
+ // check if there is no type in column options then set type from first function argument, or guessed one
+ if (!options.type && type) options.type = type;
+
+ // if we still don't have a type then we need to give error to user that type is required
+ if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
+
+ // check if column is not nullable, because we cannot allow a primary key to be nullable
+ if (options.nullable) throw new PrimaryColumnCannotBeNullableError(object, propertyName);
+
+ // explicitly set a primary to column options
+ options.primary = true;
+
+ // create and register a new column metadata
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'regular',
+ options: options,
+ } as ColumnMetadataArgs);
+
+ if (options.generated) {
+ getMetadataArgsStorage().generations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ strategy: typeof options.generated === 'string' ? options.generated : 'increment',
+ } as GeneratedMetadataArgs);
+ }
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/PrimaryGeneratedColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/PrimaryGeneratedColumn.ts
new file mode 100644
index 00000000000..f45ff2ba917
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/PrimaryGeneratedColumn.ts
@@ -0,0 +1,115 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { PrimaryGeneratedColumnNumericOptions } from '../options/PrimaryGeneratedColumnNumericOptions';
+import { PrimaryGeneratedColumnUUIDOptions } from '../options/PrimaryGeneratedColumnUUIDOptions';
+import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+import { PrimaryGeneratedColumnIdentityOptions } from '../options/PrimaryGeneratedColumnIdentityOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * Column decorator is used to mark a specific class property as a table column.
+ */
+export function PrimaryGeneratedColumn(): PropertyDecorator;
+
+/**
+ * Column decorator is used to mark a specific class property as a table column.
+ */
+export function PrimaryGeneratedColumn(
+ options: PrimaryGeneratedColumnNumericOptions,
+): PropertyDecorator;
+
+/**
+ * Column decorator is used to mark a specific class property as a table column.
+ */
+export function PrimaryGeneratedColumn(
+ strategy: 'increment',
+ options?: PrimaryGeneratedColumnNumericOptions,
+): PropertyDecorator;
+
+/**
+ * Column decorator is used to mark a specific class property as a table column.
+ */
+export function PrimaryGeneratedColumn(
+ strategy: 'uuid',
+ options?: PrimaryGeneratedColumnUUIDOptions,
+): PropertyDecorator;
+
+/**
+ * Column decorator is used to mark a specific class property as a table column.
+ */
+export function PrimaryGeneratedColumn(
+ strategy: 'rowid',
+ 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
+ | PrimaryGeneratedColumnIdentityOptions,
+ maybeOptions?:
+ | PrimaryGeneratedColumnNumericOptions
+ | PrimaryGeneratedColumnUUIDOptions
+ | PrimaryGeneratedColumnIdentityOptions,
+): PropertyDecorator {
+ // normalize parameters
+ const options: ColumnOptions = {};
+ let strategy: 'increment' | 'uuid' | 'rowid' | 'identity';
+ if (strategyOrOptions) {
+ if (typeof strategyOrOptions === 'string')
+ strategy = strategyOrOptions as 'increment' | 'uuid' | 'rowid' | 'identity';
+
+ if (ObjectUtils.isObject(strategyOrOptions)) {
+ strategy = 'increment';
+ Object.assign(options, strategyOrOptions);
+ }
+ } else {
+ strategy = 'increment';
+ }
+ if (ObjectUtils.isObject(maybeOptions)) Object.assign(options, maybeOptions);
+
+ return function (object: Object, propertyName: string) {
+ // if column type is not explicitly set then determine it based on generation strategy
+ if (!options.type) {
+ if (strategy === 'increment' || strategy === 'identity') {
+ options.type = Number;
+ } else if (strategy === 'uuid') {
+ options.type = 'uuid';
+ } else if (strategy === 'rowid') {
+ options.type = 'int';
+ }
+ }
+
+ // explicitly set a primary and generated to column options
+ options.primary = true;
+
+ // register column metadata args
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'regular',
+ options: options,
+ });
+
+ // register generated metadata args
+ getMetadataArgsStorage().generations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ strategy: strategy,
+ } as GeneratedMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/UpdateDateColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/UpdateDateColumn.ts
new file mode 100644
index 00000000000..153d62733e2
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/UpdateDateColumn.ts
@@ -0,0 +1,18 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * This column will store an update date of the updated object.
+ * This date is being updated each time you persist the object.
+ */
+export function UpdateDateColumn(options?: ColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'updateDate',
+ options: options ? options : {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/VersionColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/VersionColumn.ts
new file mode 100644
index 00000000000..d258425c87c
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/VersionColumn.ts
@@ -0,0 +1,19 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * This column will store a number - version of the entity.
+ * Every time your entity will be persisted, this number will be increased by one -
+ * so you can organize visioning and update strategies of your entity.
+ */
+export function VersionColumn(options?: ColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'version',
+ options: options || {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/ViewColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/ViewColumn.ts
new file mode 100644
index 00000000000..69b437db08b
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/ViewColumn.ts
@@ -0,0 +1,17 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { ViewColumnOptions } from '../options/ViewColumnOptions';
+
+/**
+ * ViewColumn decorator is used to mark a specific class property as a view column.
+ */
+export function ViewColumn(options?: ViewColumnOptions): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'regular',
+ options: options || {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/columns/VirtualColumn.ts b/packages/@n8n/typeorm/src/decorator/columns/VirtualColumn.ts
new file mode 100644
index 00000000000..f30d274462a
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/columns/VirtualColumn.ts
@@ -0,0 +1,66 @@
+import { ColumnType } from '../../driver/types/ColumnTypes';
+import { ColumnTypeUndefinedError } from '../../error';
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+import { VirtualColumnOptions } from '../options/VirtualColumnOptions';
+/**
+ * VirtualColumn decorator is used to mark a specific class property as a Virtual column.
+ */
+export function VirtualColumn(options: VirtualColumnOptions): PropertyDecorator;
+
+/**
+ * VirtualColumn decorator is used to mark a specific class property as a Virtual column.
+ */
+export function VirtualColumn(
+ typeOrOptions: ColumnType,
+ options: VirtualColumnOptions,
+): PropertyDecorator;
+
+/**
+ * VirtualColumn decorator is used to mark a specific class property as a Virtual column.
+ */
+export function VirtualColumn(
+ typeOrOptions?: ColumnType | VirtualColumnOptions,
+ options?: VirtualColumnOptions,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ // normalize parameters
+ let type: ColumnType | undefined;
+ if (typeof typeOrOptions === 'string') {
+ type = typeOrOptions;
+ } else {
+ options = typeOrOptions;
+ type = options.type;
+ }
+
+ if (!options?.query) {
+ throw new Error('Column options must be defined for calculated columns.');
+ }
+
+ // if type is not given explicitly then try to guess it
+ const reflectMetadataType =
+ Reflect && (Reflect as any).getMetadata
+ ? (Reflect as any).getMetadata('design:type', object, propertyName)
+ : undefined;
+ if (!type && reflectMetadataType)
+ // if type is not given explicitly then try to guess it
+ type = reflectMetadataType;
+
+ // check if there is no type in column options then set type from first function argument, or guessed one
+ if (type) options.type = type;
+
+ // specify HSTORE type if column is HSTORE
+ if (options.type === 'hstore' && !options.hstoreType)
+ options.hstoreType = reflectMetadataType === Object ? 'object' : 'string';
+
+ // if we still don't have a type then we need to give error to user that type is required
+ if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
+
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'virtual-property',
+ options: options || {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/entity-view/ViewEntity.ts b/packages/@n8n/typeorm/src/decorator/entity-view/ViewEntity.ts
new file mode 100644
index 00000000000..1fc81915281
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/entity-view/ViewEntity.ts
@@ -0,0 +1,44 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
+import { ViewEntityOptions } from '../options/ViewEntityOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * This decorator is used to mark classes that will be an entity view.
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function ViewEntity(options?: ViewEntityOptions): ClassDecorator;
+
+/**
+ * This decorator is used to mark classes that will be an entity view.
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function ViewEntity(name?: string, options?: ViewEntityOptions): ClassDecorator;
+
+/**
+ * This decorator is used to mark classes that will be an entity view.
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function ViewEntity(
+ nameOrOptions?: string | ViewEntityOptions,
+ maybeOptions?: ViewEntityOptions,
+): ClassDecorator {
+ const options =
+ (ObjectUtils.isObject(nameOrOptions) ? (nameOrOptions as ViewEntityOptions) : maybeOptions) ||
+ {};
+ const name = typeof nameOrOptions === 'string' ? nameOrOptions : options.name;
+
+ return function (target: Function) {
+ getMetadataArgsStorage().tables.push({
+ target: target,
+ name: name,
+ expression: options.expression,
+ dependsOn: options.dependsOn ? new Set(options.dependsOn) : undefined,
+ type: 'view',
+ database: options.database ? options.database : undefined,
+ schema: options.schema ? options.schema : undefined,
+ synchronize: options.synchronize === false ? false : true,
+ materialized: !!options.materialized,
+ } as TableMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/entity/ChildEntity.ts b/packages/@n8n/typeorm/src/decorator/entity/ChildEntity.ts
new file mode 100644
index 00000000000..dafc6652f43
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/entity/ChildEntity.ts
@@ -0,0 +1,24 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
+import { DiscriminatorValueMetadataArgs } from '../../metadata-args/DiscriminatorValueMetadataArgs';
+
+/**
+ * Special type of the table used in the single-table inherited tables.
+ */
+export function ChildEntity(discriminatorValue?: any): ClassDecorator {
+ return function (target: Function) {
+ // register a table metadata
+ getMetadataArgsStorage().tables.push({
+ target: target,
+ type: 'entity-child',
+ } as TableMetadataArgs);
+
+ // register discriminator value if it was provided
+ if (typeof discriminatorValue !== 'undefined') {
+ getMetadataArgsStorage().discriminatorValues.push({
+ target: target,
+ value: discriminatorValue,
+ } as DiscriminatorValueMetadataArgs);
+ }
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/entity/Entity.ts b/packages/@n8n/typeorm/src/decorator/entity/Entity.ts
new file mode 100644
index 00000000000..e22a57ff176
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/entity/Entity.ts
@@ -0,0 +1,44 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
+import { EntityOptions } from '../options/EntityOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * This decorator is used to mark classes that will be an entity (table or document depend on database type).
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function Entity(options?: EntityOptions): ClassDecorator;
+
+/**
+ * This decorator is used to mark classes that will be an entity (table or document depend on database type).
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function Entity(name?: string, options?: EntityOptions): ClassDecorator;
+
+/**
+ * This decorator is used to mark classes that will be an entity (table or document depend on database type).
+ * Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
+ */
+export function Entity(
+ nameOrOptions?: string | EntityOptions,
+ maybeOptions?: EntityOptions,
+): ClassDecorator {
+ const options =
+ (ObjectUtils.isObject(nameOrOptions) ? (nameOrOptions as EntityOptions) : maybeOptions) || {};
+ const name = typeof nameOrOptions === 'string' ? nameOrOptions : options.name;
+
+ return function (target) {
+ getMetadataArgsStorage().tables.push({
+ target: target,
+ name: name,
+ type: 'regular',
+ orderBy: options.orderBy ? options.orderBy : undefined,
+ engine: options.engine ? options.engine : undefined,
+ database: options.database ? options.database : undefined,
+ schema: options.schema ? options.schema : undefined,
+ synchronize: options.synchronize,
+ withoutRowid: options.withoutRowid,
+ comment: options.comment ? options.comment : undefined,
+ } as TableMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/entity/TableInheritance.ts b/packages/@n8n/typeorm/src/decorator/entity/TableInheritance.ts
new file mode 100644
index 00000000000..37585839437
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/entity/TableInheritance.ts
@@ -0,0 +1,24 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { InheritanceMetadataArgs } from '../../metadata-args/InheritanceMetadataArgs';
+import { ColumnOptions } from '../options/ColumnOptions';
+
+/**
+ * Sets for entity to use table inheritance pattern.
+ */
+export function TableInheritance(options?: {
+ pattern?: 'STI' /*|"CTI"*/;
+ column?: string | ColumnOptions;
+}): ClassDecorator {
+ return function (target: Function) {
+ getMetadataArgsStorage().inheritances.push({
+ target: target,
+ pattern: options && options.pattern ? options.pattern : 'STI',
+ column:
+ options && options.column
+ ? typeof options.column === 'string'
+ ? { name: options.column }
+ : options.column
+ : undefined,
+ } as InheritanceMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterInsert.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterInsert.ts
new file mode 100644
index 00000000000..0d66e6fe78b
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterInsert.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied after this entity insertion.
+ */
+export function AfterInsert(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_INSERT,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterLoad.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterLoad.ts
new file mode 100644
index 00000000000..0c668f819cb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterLoad.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied after entity is loaded.
+ */
+export function AfterLoad(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_LOAD,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterRecover.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterRecover.ts
new file mode 100644
index 00000000000..38090c5e412
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterRecover.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity soft removal.
+ */
+export function AfterRecover(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_RECOVER,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterRemove.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterRemove.ts
new file mode 100644
index 00000000000..0dbce0ac275
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterRemove.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied after this entity removal.
+ */
+export function AfterRemove(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_REMOVE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterSoftRemove.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterSoftRemove.ts
new file mode 100644
index 00000000000..89e804c125d
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterSoftRemove.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity soft removal.
+ */
+export function AfterSoftRemove(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_SOFT_REMOVE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/AfterUpdate.ts b/packages/@n8n/typeorm/src/decorator/listeners/AfterUpdate.ts
new file mode 100644
index 00000000000..21ec5cb7482
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/AfterUpdate.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied after this entity update.
+ */
+export function AfterUpdate(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.AFTER_UPDATE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/BeforeInsert.ts b/packages/@n8n/typeorm/src/decorator/listeners/BeforeInsert.ts
new file mode 100644
index 00000000000..14369de27b3
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/BeforeInsert.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity insertion.
+ */
+export function BeforeInsert(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.BEFORE_INSERT,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/BeforeRecover.ts b/packages/@n8n/typeorm/src/decorator/listeners/BeforeRecover.ts
new file mode 100644
index 00000000000..e232e9327fb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/BeforeRecover.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity soft removal.
+ */
+export function BeforeRecover(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.BEFORE_RECOVER,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/BeforeRemove.ts b/packages/@n8n/typeorm/src/decorator/listeners/BeforeRemove.ts
new file mode 100644
index 00000000000..23fe31eb53b
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/BeforeRemove.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity removal.
+ */
+export function BeforeRemove(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.BEFORE_REMOVE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/BeforeSoftRemove.ts b/packages/@n8n/typeorm/src/decorator/listeners/BeforeSoftRemove.ts
new file mode 100644
index 00000000000..ef1ea64cb9e
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/BeforeSoftRemove.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity soft removal.
+ */
+export function BeforeSoftRemove(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.BEFORE_SOFT_REMOVE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/BeforeUpdate.ts b/packages/@n8n/typeorm/src/decorator/listeners/BeforeUpdate.ts
new file mode 100644
index 00000000000..45f5789d4dd
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/BeforeUpdate.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
+import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
+
+/**
+ * Calls a method on which this decorator is applied before this entity update.
+ */
+export function BeforeUpdate(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().entityListeners.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ type: EventListenerTypes.BEFORE_UPDATE,
+ } as EntityListenerMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/listeners/EventSubscriber.ts b/packages/@n8n/typeorm/src/decorator/listeners/EventSubscriber.ts
new file mode 100644
index 00000000000..98173066770
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/listeners/EventSubscriber.ts
@@ -0,0 +1,14 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { EntitySubscriberMetadataArgs } from '../../metadata-args/EntitySubscriberMetadataArgs';
+
+/**
+ * Classes decorated with this decorator will listen to ORM events and their methods will be triggered when event
+ * occurs. Those classes must implement EventSubscriberInterface interface.
+ */
+export function EventSubscriber(): ClassDecorator {
+ return function (target: Function) {
+ getMetadataArgsStorage().entitySubscribers.push({
+ target: target,
+ } as EntitySubscriberMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnCommonOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnCommonOptions.ts
new file mode 100644
index 00000000000..59929bc660f
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnCommonOptions.ts
@@ -0,0 +1,68 @@
+import { ValueTransformer } from './ValueTransformer';
+
+/**
+ * Column options specific to all column types.
+ */
+export interface ColumnCommonOptions {
+ /**
+ * Indicates if column is always selected by QueryBuilder and find operations.
+ * Default value is "true".
+ */
+ select?: boolean;
+
+ /**
+ * Column name in the database.
+ */
+ name?: string;
+
+ /**
+ * Indicates if this column is a primary key.
+ * Same can be achieved when @PrimaryColumn decorator is used.
+ */
+ primary?: boolean;
+
+ /**
+ * 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' | 'identity';
+
+ /**
+ * Specifies if column's value must be unique or not.
+ */
+ unique?: boolean;
+
+ /**
+ * Indicates if column's value can be set to NULL.
+ */
+ nullable?: boolean;
+
+ /**
+ * Default database value.
+ * Note that default value is not supported when column type is 'json' of mysql.
+ */
+ default?: any;
+
+ /**
+ * ON UPDATE trigger. Works only for MySQL.
+ */
+ onUpdate?: string;
+
+ /**
+ * Column comment. Not supported by all database types.
+ */
+ comment?: string;
+
+ /**
+ * Indicates if this column is an array.
+ * Can be simply set to true or array length can be specified.
+ * Supported only by postgres.
+ */
+ array?: boolean;
+
+ /**
+ * Specifies a value transformer that is to be used to (un)marshal
+ * this column when reading or writing to the database.
+ */
+ transformer?: ValueTransformer | ValueTransformer[];
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnEmbeddedOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnEmbeddedOptions.ts
new file mode 100644
index 00000000000..cde3deadfe2
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnEmbeddedOptions.ts
@@ -0,0 +1,17 @@
+/**
+ * Column options specific to embedded column.
+ */
+export interface ColumnEmbeddedOptions {
+ /**
+ * Embedded column prefix.
+ * If set to empty string or false, then prefix is not set at all.
+ */
+ prefix?: string | boolean;
+
+ /**
+ * Indicates if this embedded is in array mode.
+ *
+ * This option works only in mongodb.
+ */
+ array?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnEnumOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnEnumOptions.ts
new file mode 100644
index 00000000000..ef327a12923
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnEnumOptions.ts
@@ -0,0 +1,13 @@
+/**
+ * Column options for enum-typed columns.
+ */
+export interface ColumnEnumOptions {
+ /**
+ * Array of possible enumerated values.
+ */
+ enum?: any[] | Object;
+ /**
+ * Exact name of enum
+ */
+ enumName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnHstoreOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnHstoreOptions.ts
new file mode 100644
index 00000000000..b8a1c3713cb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnHstoreOptions.ts
@@ -0,0 +1,10 @@
+/**
+ * Column options for enum-typed columns.
+ */
+export interface ColumnHstoreOptions {
+ /**
+ * Return type of HSTORE column.
+ * Returns value as string or as object.
+ */
+ hstoreType?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnNumericOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnNumericOptions.ts
new file mode 100644
index 00000000000..0dc4ca97e6e
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnNumericOptions.ts
@@ -0,0 +1,27 @@
+/**
+ * Options for numeric column types where user can specify scale and precision.
+ */
+export interface ColumnNumericOptions {
+ /**
+ * The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
+ * number of digits that are stored for the values.
+ */
+ precision?: number;
+
+ /**
+ * The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
+ * of digits to the right of the decimal point and must not be greater than precision.
+ */
+ scale?: number;
+
+ /**
+ * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
+ * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
+ */
+ zerofill?: boolean;
+
+ /**
+ * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
+ */
+ unsigned?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnOptions.ts
new file mode 100644
index 00000000000..486887f6fac
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnOptions.ts
@@ -0,0 +1,190 @@
+import { ColumnType } from '../../driver/types/ColumnTypes';
+import { ValueTransformer } from './ValueTransformer';
+import { ColumnCommonOptions } from './ColumnCommonOptions';
+
+/**
+ * Describes all column's options.
+ */
+export interface ColumnOptions extends ColumnCommonOptions {
+ /**
+ * Column type. Must be one of the value from the ColumnTypes class.
+ */
+ type?: ColumnType;
+
+ /**
+ * Column name in the database.
+ */
+ name?: string;
+
+ /**
+ * Column type's length. Used only on some column types.
+ * For example type = "string" and length = "100" means that ORM will create a column with type varchar(100).
+ */
+ length?: string | number;
+
+ /**
+ * Column type's display width. Used only on some column types in MySQL.
+ * For example, INT(4) specifies an INT with a display width of four digits.
+ */
+ width?: number;
+
+ /**
+ * Indicates if column's value can be set to NULL.
+ * Default value is "false".
+ */
+ nullable?: boolean;
+
+ /**
+ * Indicates if column value is not updated by "save" operation.
+ * It means you'll be able to write this value only when you first time insert the object.
+ * Default value is "false".
+ *
+ * @deprecated Please use the `update` option instead. Careful, it takes
+ * the opposite value to readonly.
+ *
+ */
+ readonly?: boolean;
+
+ /**
+ * Indicates if column value is updated by "save" operation.
+ * If false, you'll be able to write this value only when you first time insert the object.
+ * Default value is "true".
+ */
+ update?: boolean;
+
+ /**
+ * Indicates if column is always selected by QueryBuilder and find operations.
+ * Default value is "true".
+ */
+ select?: boolean;
+
+ /**
+ * Indicates if column is inserted by default.
+ * Default value is "true".
+ */
+ insert?: boolean;
+
+ /**
+ * Default database value.
+ */
+ default?: any;
+
+ /**
+ * ON UPDATE trigger. Works only for MySQL.
+ */
+ onUpdate?: string;
+
+ /**
+ * Indicates if this column is a primary key.
+ * Same can be achieved when @PrimaryColumn decorator is used.
+ */
+ primary?: boolean;
+
+ /**
+ * Specifies if column's value must be unique or not.
+ */
+ unique?: boolean;
+
+ /**
+ * Column comment. Not supported by all database types.
+ */
+ comment?: string;
+
+ /**
+ * The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
+ * number of digits that are stored for the values.
+ */
+ precision?: number | null;
+
+ /**
+ * The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
+ * of digits to the right of the decimal point and must not be greater than precision.
+ */
+ scale?: number;
+
+ /**
+ * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
+ * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to this column
+ */
+ zerofill?: boolean;
+
+ /**
+ * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
+ */
+ unsigned?: boolean;
+
+ /**
+ * Defines a column character set.
+ * Not supported by all database types.
+ */
+ charset?: string;
+
+ /**
+ * Defines a column collation.
+ */
+ collation?: string;
+
+ /**
+ * Array of possible enumerated values.
+ */
+ enum?: (string | number)[] | Object;
+
+ /**
+ * Exact name of enum
+ */
+ enumName?: string;
+
+ /**
+ * If this column is primary key then this specifies the name for it.
+ */
+ primaryKeyConstraintName?: string;
+
+ /**
+ * If this column is foreign key then this specifies the name for it.
+ */
+ foreignKeyConstraintName?: string;
+
+ /**
+ * Generated column expression.
+ */
+ asExpression?: string;
+
+ /**
+ * Generated column type.
+ */
+ generatedType?: 'VIRTUAL' | 'STORED';
+
+ /**
+ * Identity column type. Supports only in Postgres 10+.
+ */
+ generatedIdentity?: 'ALWAYS' | 'BY DEFAULT';
+
+ /**
+ * Return type of HSTORE column.
+ * Returns value as string or as object.
+ */
+ hstoreType?: 'object' | 'string';
+
+ /**
+ * Indicates if this column is an array.
+ * Can be simply set to true or array length can be specified.
+ * Supported only by postgres.
+ */
+ array?: boolean;
+
+ /**
+ * Specifies a value transformer that is to be used to (un)marshal
+ * this column when reading or writing to the database.
+ */
+ transformer?: ValueTransformer | ValueTransformer[];
+
+ /**
+ * Spatial Feature Type (Geometry, Point, Polygon, etc.)
+ */
+ spatialFeatureType?: string;
+
+ /**
+ * SRID (Spatial Reference ID (EPSG code))
+ */
+ srid?: number;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnWithLengthOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnWithLengthOptions.ts
new file mode 100644
index 00000000000..d93fe8290f6
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnWithLengthOptions.ts
@@ -0,0 +1,10 @@
+/**
+ * Options for columns that can define a length of the column type.
+ */
+export interface ColumnWithLengthOptions {
+ /**
+ * Column type's length.
+ * For example type = "varchar" and length = "100" means ORM will create a column with type varchar(100).
+ */
+ length?: string | number;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ColumnWithWidthOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ColumnWithWidthOptions.ts
new file mode 100644
index 00000000000..9a4cc25a72c
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ColumnWithWidthOptions.ts
@@ -0,0 +1,21 @@
+/**
+ * Options for columns that can define a length of the column type.
+ */
+export interface ColumnWithWidthOptions {
+ /**
+ * Column type's display width. Used only on some column types in MySQL.
+ * For example, INT(4) specifies an INT with a display width of four digits.
+ */
+ width?: number;
+
+ /**
+ * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
+ * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to this column
+ */
+ zerofill?: boolean;
+
+ /**
+ * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
+ */
+ unsigned?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/EntityOptions.ts b/packages/@n8n/typeorm/src/decorator/options/EntityOptions.ts
new file mode 100644
index 00000000000..cef6a34fcda
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/EntityOptions.ts
@@ -0,0 +1,54 @@
+import { OrderByCondition } from '../../find-options/OrderByCondition';
+
+/**
+ * Describes all entity's options.
+ */
+export interface EntityOptions {
+ /**
+ * Table name.
+ * If not specified then naming strategy will generate table name from entity name.
+ */
+ name?: string;
+
+ /**
+ * Specifies a default order by used for queries from this table when no explicit order by is specified.
+ */
+ orderBy?: OrderByCondition | ((object: any) => OrderByCondition | any);
+
+ /**
+ * Table's database engine type (like "InnoDB", "MyISAM", etc).
+ * It is used only during table creation.
+ * If you update this value and table is already created, it will not change table's engine type.
+ * Note that not all databases support this option.
+ */
+ engine?: string;
+
+ /**
+ * Database name. Used in Mysql and Sql Server.
+ */
+ database?: string;
+
+ /**
+ * Schema name. Used in Postgres and Sql Server.
+ */
+ schema?: string;
+
+ /**
+ * Indicates if schema synchronization is enabled or disabled for this entity.
+ * If it will be set to false then schema sync will and migrations ignore this entity.
+ * By default schema synchronization is enabled for all entities.
+ */
+ synchronize?: boolean;
+
+ /**
+ * If set to 'true' this option disables Sqlite's default behaviour of secretly creating
+ * an integer primary key column named 'rowid' on table creation.
+ * @see https://www.sqlite.org/withoutrowid.html.
+ */
+ withoutRowid?: boolean;
+
+ /**
+ * Table comment. Not supported by all database types.
+ */
+ comment?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/IndexOptions.ts b/packages/@n8n/typeorm/src/decorator/options/IndexOptions.ts
new file mode 100644
index 00000000000..bb09573e18d
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/IndexOptions.ts
@@ -0,0 +1,66 @@
+/**
+ * Describes all index options.
+ */
+export interface IndexOptions {
+ /**
+ * Indicates if this composite index must be unique or not.
+ */
+ unique?: boolean;
+
+ /**
+ * The SPATIAL modifier indexes the entire column and does not allow indexed columns to contain NULL values.
+ * Works only in MySQL and PostgreSQL.
+ */
+ spatial?: boolean;
+
+ /**
+ * The FULLTEXT modifier indexes the entire column and does not allow prefixing.
+ * Works only in MySQL.
+ */
+ fulltext?: boolean;
+
+ /**
+ * NULL_FILTERED indexes are particularly useful for indexing sparse columns, where most rows contain a NULL value.
+ * In these cases, the NULL_FILTERED index can be considerably smaller and more efficient to maintain than
+ * a normal index that includes NULL values.
+ *
+ * Works only in Spanner.
+ */
+ nullFiltered?: boolean;
+
+ /**
+ * Fulltext parser.
+ * Works only in MySQL.
+ */
+ parser?: string;
+
+ /**
+ * Index filter condition.
+ */
+ where?: string;
+
+ /**
+ * If true, the index only references documents with the specified field.
+ * These indexes use less space but behave differently in some situations (particularly sorts).
+ * This option is only supported for mongodb database.
+ */
+ sparse?: boolean;
+
+ /**
+ * Builds the index in the background so that building an index an does not block other database activities.
+ * This option is only supported for mongodb database.
+ */
+ background?: boolean;
+
+ /**
+ * Create the index using the CONCURRENTLY modifier
+ * Works only in postgres.
+ */
+ concurrent?: boolean;
+
+ /**
+ * Specifies a time to live, in seconds.
+ * This option is only supported for mongodb database.
+ */
+ expireAfterSeconds?: number;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/JoinColumnOptions.ts b/packages/@n8n/typeorm/src/decorator/options/JoinColumnOptions.ts
new file mode 100644
index 00000000000..c1453fca98c
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/JoinColumnOptions.ts
@@ -0,0 +1,19 @@
+/**
+ * Describes join column options.
+ */
+export interface JoinColumnOptions {
+ /**
+ * Name of the column.
+ */
+ name?: string;
+
+ /**
+ * Name of the column in the entity to which this column is referenced.
+ */
+ referencedColumnName?: string; // TODO rename to referencedColumn
+
+ /**
+ * Name of the foreign key constraint.
+ */
+ foreignKeyConstraintName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/JoinTableMultipleColumnsOptions.ts b/packages/@n8n/typeorm/src/decorator/options/JoinTableMultipleColumnsOptions.ts
new file mode 100644
index 00000000000..c335faf3f29
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/JoinTableMultipleColumnsOptions.ts
@@ -0,0 +1,41 @@
+import { JoinColumnOptions } from './JoinColumnOptions';
+
+/**
+ * Describes all join table with multiple column options.
+ */
+export interface JoinTableMultipleColumnsOptions {
+ /**
+ * Name of the table that will be created to store values of the both tables (join table).
+ * By default is auto generated.
+ */
+ name?: string;
+
+ /**
+ * First column of the join table.
+ */
+ joinColumns?: JoinColumnOptions[];
+
+ /**
+ * Second (inverse) column of the join table.
+ */
+ inverseJoinColumns?: JoinColumnOptions[];
+
+ /**
+ * Database where join table will be created.
+ * Works only in some databases (like mysql).
+ */
+ database?: string;
+
+ /**
+ * Schema where join table will be created.
+ * Works only in some databases (like postgres).
+ */
+ schema?: string;
+
+ /**
+ * Indicates if schema synchronization is enabled or disabled junction table.
+ * If it will be set to false then schema sync will and migrations ignores junction table.
+ * By default schema synchronization is enabled.
+ */
+ readonly synchronize?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/JoinTableOptions.ts b/packages/@n8n/typeorm/src/decorator/options/JoinTableOptions.ts
new file mode 100644
index 00000000000..472a49d6038
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/JoinTableOptions.ts
@@ -0,0 +1,41 @@
+import { JoinColumnOptions } from './JoinColumnOptions';
+
+/**
+ * Describes join table options.
+ */
+export interface JoinTableOptions {
+ /**
+ * Name of the table that will be created to store values of the both tables (join table).
+ * By default is auto generated.
+ */
+ name?: string;
+
+ /**
+ * First column of the join table.
+ */
+ joinColumn?: JoinColumnOptions;
+
+ /**
+ * Second (inverse) column of the join table.
+ */
+ inverseJoinColumn?: JoinColumnOptions;
+
+ /**
+ * Database where join table will be created.
+ * Works only in some databases (like mysql).
+ */
+ database?: string;
+
+ /**
+ * Schema where join table will be created.
+ * Works only in some databases (like postgres).
+ */
+ schema?: string;
+
+ /**
+ * Indicates if schema synchronization is enabled or disabled junction table.
+ * If it will be set to false then schema sync will and migrations ignores junction table.
+ * By default schema synchronization is enabled.
+ */
+ synchronize?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts
new file mode 100644
index 00000000000..6dcab776079
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts
@@ -0,0 +1,31 @@
+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';
+
+ /**
+ * Name of the primary key constraint.
+ */
+ primaryKeyConstraintName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts
new file mode 100644
index 00000000000..1e93766d343
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts
@@ -0,0 +1,37 @@
+import { PrimaryGeneratedColumnType } from '../../driver/types/ColumnTypes';
+
+/**
+ * Describes all options for PrimaryGeneratedColumn decorator with numeric generation strategy.
+ */
+export interface PrimaryGeneratedColumnNumericOptions {
+ /**
+ * 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;
+
+ /**
+ * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
+ * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
+ */
+ zerofill?: boolean;
+
+ /**
+ * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
+ */
+ unsigned?: boolean;
+
+ /**
+ * Name of the primary key constraint.
+ */
+ primaryKeyConstraintName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts
new file mode 100644
index 00000000000..31ecf35a914
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts
@@ -0,0 +1,19 @@
+/**
+ * Describes all options for PrimaryGeneratedColumn decorator with numeric uuid strategy.
+ */
+export interface PrimaryGeneratedColumnUUIDOptions {
+ /**
+ * Column name in the database.
+ */
+ name?: string;
+
+ /**
+ * Column comment. Not supported by all database types.
+ */
+ comment?: string;
+
+ /**
+ * Name of the primary key constraint.
+ */
+ primaryKeyConstraintName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/RelationOptions.ts b/packages/@n8n/typeorm/src/decorator/options/RelationOptions.ts
new file mode 100644
index 00000000000..11704d99fbb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/RelationOptions.ts
@@ -0,0 +1,74 @@
+import { DeferrableType } from '../../metadata/types/DeferrableType';
+import { OnDeleteType } from '../../metadata/types/OnDeleteType';
+import { OnUpdateType } from '../../metadata/types/OnUpdateType';
+
+/**
+ * Describes all relation's options.
+ */
+export interface RelationOptions {
+ /**
+ * Sets cascades options for the given relation.
+ * If set to true then it means that related object can be allowed to be inserted or updated in the database.
+ * You can separately restrict cascades to insertion or updation using following syntax:
+ *
+ * cascade: ["insert", "update", "remove", "soft-remove", "recover"] // include or exclude one of them
+ */
+ cascade?: boolean | ('insert' | 'update' | 'remove' | 'soft-remove' | 'recover')[];
+
+ /**
+ * Indicates if relation column value can be nullable or not.
+ */
+ nullable?: boolean;
+
+ /**
+ * Database cascade action on delete.
+ */
+ onDelete?: OnDeleteType;
+
+ /**
+ * Database cascade action on update.
+ */
+ onUpdate?: OnUpdateType;
+
+ /**
+ * Indicate if foreign key constraints can be deferred.
+ */
+ deferrable?: DeferrableType;
+
+ /**
+ * Indicates whether foreign key constraints will be created for join columns.
+ * Can be used only for many-to-one and owner one-to-one relations.
+ * Defaults to true.
+ */
+ createForeignKeyConstraints?: boolean;
+
+ /**
+ * Set this relation to be lazy. Note: lazy relations are promises. When you call them they return promise
+ * which resolve relation result then. If your property's type is Promise then this relation is set to lazy automatically.
+ */
+ lazy?: boolean;
+
+ /**
+ * Set this relation to be eager.
+ * Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods.
+ * Only using QueryBuilder prevents loading eager relations.
+ * Eager flag cannot be set from both sides of relation - you can eager load only one side of the relationship.
+ */
+ eager?: boolean;
+
+ /**
+ * Indicates if persistence is enabled for the relation.
+ * By default its enabled, but if you want to avoid any changes in the relation to be reflected in the database you can disable it.
+ * If its disabled you can only change a relation from inverse side of a relation or using relation query builder functionality.
+ * This is useful for performance optimization since its disabling avoid multiple extra queries during entity save.
+ */
+ persistence?: boolean;
+
+ /**
+ * When a parent is saved (with cascading but) without a child row that still exists in database, this will control what shall happen to them.
+ * delete will remove these rows from database.
+ * nullify will remove the relation key.
+ * disable will keep the relation intact. Removal of related item is only possible through its own repo.
+ */
+ orphanedRowAction?: 'nullify' | 'delete' | 'soft-delete' | 'disable';
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/SpatialColumnOptions.ts b/packages/@n8n/typeorm/src/decorator/options/SpatialColumnOptions.ts
new file mode 100644
index 00000000000..3b94bf186e7
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/SpatialColumnOptions.ts
@@ -0,0 +1,18 @@
+import { Geometry } from '../../driver/types/GeoJsonTypes';
+
+/**
+ * Options for spatial columns.
+ */
+export interface SpatialColumnOptions {
+ /**
+ * Column type's feature type.
+ * Geometry, Point, Polygon, etc.
+ */
+ spatialFeatureType?: Geometry['type'];
+
+ /**
+ * Column type's SRID.
+ * Spatial Reference ID or EPSG code.
+ */
+ srid?: number;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/UniqueOptions.ts b/packages/@n8n/typeorm/src/decorator/options/UniqueOptions.ts
new file mode 100644
index 00000000000..a43fa1a8a32
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/UniqueOptions.ts
@@ -0,0 +1,11 @@
+import { DeferrableType } from '../../metadata/types/DeferrableType';
+
+/**
+ * Describes all unique options.
+ */
+export interface UniqueOptions {
+ /**
+ * Indicate if unique constraints can be deferred.
+ */
+ deferrable?: DeferrableType;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ValueTransformer.ts b/packages/@n8n/typeorm/src/decorator/options/ValueTransformer.ts
new file mode 100644
index 00000000000..b4f5f2bd2e9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ValueTransformer.ts
@@ -0,0 +1,14 @@
+/**
+ * Interface for objects that deal with (un)marshalling data.
+ */
+export interface ValueTransformer {
+ /**
+ * Used to marshal data when writing to the database.
+ */
+ to(value: any): any;
+
+ /**
+ * Used to unmarshal data when reading from the database.
+ */
+ from(value: any): any;
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ViewColumnOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ViewColumnOptions.ts
new file mode 100644
index 00000000000..0b33e4d1e09
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ViewColumnOptions.ts
@@ -0,0 +1,17 @@
+import { ValueTransformer } from './ValueTransformer';
+
+/**
+ * Describes all view column's options.
+ */
+export interface ViewColumnOptions {
+ /**
+ * Column name in the database.
+ */
+ name?: string;
+
+ /**
+ * Specifies a value transformer(s) that is to be used to unmarshal
+ * this column when reading from the database.
+ */
+ transformer?: ValueTransformer | ValueTransformer[];
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/ViewEntityOptions.ts b/packages/@n8n/typeorm/src/decorator/options/ViewEntityOptions.ts
new file mode 100644
index 00000000000..58af3d7ff2a
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/ViewEntityOptions.ts
@@ -0,0 +1,46 @@
+/**
+ * Describes all entity view's options.
+ */
+import { DataSource, SelectQueryBuilder } from '../..';
+
+export interface ViewEntityOptions {
+ /**
+ * View name.
+ * If not specified then naming strategy will generate view name from class name.
+ */
+ name?: string;
+
+ /**
+ * View expression.
+ */
+ expression?: string | ((connection: DataSource) => SelectQueryBuilder);
+
+ /**
+ * Database name. Used in Mysql and Sql Server.
+ */
+ database?: string;
+
+ /**
+ * Schema name. Used in Postgres and Sql Server.
+ */
+ schema?: string;
+
+ /**
+ * Indicates if schema synchronization is enabled or disabled for this entity.
+ * If it will be set to false then schema sync will and migrations ignore this entity.
+ * By default schema synchronization is enabled for all entities.
+ */
+ synchronize?: boolean;
+
+ /**
+ * Indicates if view should be materialized view.
+ * It's supported by Postgres and Oracle.
+ */
+ materialized?: boolean;
+
+ /**
+ * View dependencies. In case the view depends on another view it can be listed here
+ * to ensure correct order of view migrations.
+ */
+ dependsOn?: (Function | string)[];
+}
diff --git a/packages/@n8n/typeorm/src/decorator/options/VirtualColumnOptions.ts b/packages/@n8n/typeorm/src/decorator/options/VirtualColumnOptions.ts
new file mode 100644
index 00000000000..2cd4440a2fb
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/options/VirtualColumnOptions.ts
@@ -0,0 +1,31 @@
+import { ColumnType } from '../../driver/types/ColumnTypes';
+import { ValueTransformer } from './ValueTransformer';
+
+/**
+ * Describes all calculated column's options.
+ */
+export interface VirtualColumnOptions {
+ /**
+ * Column type. Must be one of the value from the ColumnTypes class.
+ */
+ type?: ColumnType;
+
+ /**
+ * Return type of HSTORE column.
+ * Returns value as string or as object.
+ */
+ hstoreType?: 'object' | 'string';
+
+ /**
+ * Query to be used to populate the column data. This query is used when generating the relational db script.
+ * The query function is called with the current entities alias either defined by the Entity Decorator or automatically
+ * @See https://typeorm.io/decorator-reference#virtualcolumn for more details.
+ */
+ query: (alias: string) => string;
+
+ /**
+ * Specifies a value transformer(s) that is to be used to unmarshal
+ * this column when reading from the database.
+ */
+ transformer?: ValueTransformer | ValueTransformer[];
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/JoinColumn.ts b/packages/@n8n/typeorm/src/decorator/relations/JoinColumn.ts
new file mode 100644
index 00000000000..3ca93d90b23
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/JoinColumn.ts
@@ -0,0 +1,48 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { JoinColumnMetadataArgs } from '../../metadata-args/JoinColumnMetadataArgs';
+import { JoinColumnOptions } from '../options/JoinColumnOptions';
+
+/**
+ * JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
+ * It also can be used on both one-to-one and many-to-one relations to specify custom column name
+ * or custom referenced column.
+ */
+export function JoinColumn(): PropertyDecorator;
+
+/**
+ * JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
+ * It also can be used on both one-to-one and many-to-one relations to specify custom column name
+ * or custom referenced column.
+ */
+export function JoinColumn(options: JoinColumnOptions): PropertyDecorator;
+
+/**
+ * JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
+ * It also can be used on both one-to-one and many-to-one relations to specify custom column name
+ * or custom referenced column.
+ */
+export function JoinColumn(options: JoinColumnOptions[]): PropertyDecorator;
+
+/**
+ * JoinColumn decorator used on one-to-one relations to specify owner side of relationship.
+ * It also can be used on both one-to-one and many-to-one relations to specify custom column name
+ * or custom referenced column.
+ */
+export function JoinColumn(
+ optionsOrOptionsArray?: JoinColumnOptions | JoinColumnOptions[],
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ const options = Array.isArray(optionsOrOptionsArray)
+ ? optionsOrOptionsArray
+ : [optionsOrOptionsArray || {}];
+ options.forEach((options) => {
+ getMetadataArgsStorage().joinColumns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ name: options.name,
+ referencedColumnName: options.referencedColumnName,
+ foreignKeyConstraintName: options.foreignKeyConstraintName,
+ } as JoinColumnMetadataArgs);
+ });
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/JoinTable.ts b/packages/@n8n/typeorm/src/decorator/relations/JoinTable.ts
new file mode 100644
index 00000000000..fa875313746
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/JoinTable.ts
@@ -0,0 +1,48 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { JoinTableMetadataArgs } from '../../metadata-args/JoinTableMetadataArgs';
+import { JoinTableMultipleColumnsOptions } from '../options/JoinTableMultipleColumnsOptions';
+import { JoinTableOptions } from '../options/JoinTableOptions';
+
+/**
+ * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship.
+ * Its also used to set a custom junction table's name, column names and referenced columns.
+ */
+export function JoinTable(): PropertyDecorator;
+
+/**
+ * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship.
+ * Its also used to set a custom junction table's name, column names and referenced columns.
+ */
+export function JoinTable(options: JoinTableOptions): PropertyDecorator;
+
+/**
+ * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship.
+ * Its also used to set a custom junction table's name, column names and referenced columns.
+ */
+export function JoinTable(options: JoinTableMultipleColumnsOptions): PropertyDecorator;
+
+/**
+ * JoinTable decorator is used in many-to-many relationship to specify owner side of relationship.
+ * Its also used to set a custom junction table's name, column names and referenced columns.
+ */
+export function JoinTable(
+ options?: JoinTableOptions | JoinTableMultipleColumnsOptions,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ options = options || ({} as JoinTableOptions | JoinTableMultipleColumnsOptions);
+ getMetadataArgsStorage().joinTables.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ name: options.name,
+ joinColumns: (options && (options as JoinTableOptions).joinColumn
+ ? [(options as JoinTableOptions).joinColumn!]
+ : (options as JoinTableMultipleColumnsOptions).joinColumns) as any,
+ inverseJoinColumns: (options && (options as JoinTableOptions).inverseJoinColumn
+ ? [(options as JoinTableOptions).inverseJoinColumn!]
+ : (options as JoinTableMultipleColumnsOptions).inverseJoinColumns) as any,
+ schema: options && options.schema ? options.schema : undefined,
+ database: options && options.database ? options.database : undefined,
+ synchronize: !(options && options.synchronize === false),
+ } as JoinTableMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/ManyToMany.ts b/packages/@n8n/typeorm/src/decorator/relations/ManyToMany.ts
new file mode 100644
index 00000000000..7d373d238e9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/ManyToMany.ts
@@ -0,0 +1,73 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { ObjectType } from '../../common/ObjectType';
+import { RelationOptions } from '../options/RelationOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have
+ * multiple instances of Entity1. To achieve it, this type of relation creates a junction table, where it storage
+ * entity1 and entity2 ids. This is owner side of the relationship.
+ */
+export function ManyToMany(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have
+ * multiple instances of Entity1. To achieve it, this type of relation creates a junction table, where it storage
+ * entity1 and entity2 ids. This is owner side of the relationship.
+ */
+export function ManyToMany(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSide?: string | ((object: T) => any),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have
+ * multiple instances of Entity1. To achieve it, this type of relation creates a junction table, where it storage
+ * entity1 and entity2 ids. This is owner side of the relationship.
+ */
+export function ManyToMany(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSideOrOptions?: string | ((object: T) => any) | RelationOptions,
+ options?: RelationOptions,
+): PropertyDecorator {
+ // normalize parameters
+ let inverseSideProperty: string | ((object: T) => any);
+ if (ObjectUtils.isObject(inverseSideOrOptions)) {
+ options = inverseSideOrOptions;
+ } else {
+ inverseSideProperty = inverseSideOrOptions as any;
+ }
+
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // now try to determine it its lazy relation
+ let isLazy = options.lazy === true;
+ if (!isLazy && Reflect && (Reflect as any).getMetadata) {
+ // automatic determination
+ const reflectedType = (Reflect as any).getMetadata('design:type', object, propertyName);
+ if (
+ reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise'
+ )
+ isLazy = true;
+ }
+
+ getMetadataArgsStorage().relations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ // propertyType: reflectedType,
+ relationType: 'many-to-many',
+ isLazy: isLazy,
+ type: typeFunctionOrTarget,
+ inverseSideProperty: inverseSideProperty,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/ManyToOne.ts b/packages/@n8n/typeorm/src/decorator/relations/ManyToOne.ts
new file mode 100644
index 00000000000..12de43b24c6
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/ManyToOne.ts
@@ -0,0 +1,73 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { ObjectType } from '../../common/ObjectType';
+import { RelationOptions } from '../options/RelationOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * A many-to-one relation allows creating the type of relation where Entity1 can have a single instance of Entity2, but
+ * Entity2 can have multiple instances of Entity1. Entity1 is the owner of the relationship, and stores the id of
+ * Entity2 on its side of the relation.
+ */
+export function ManyToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * A many-to-one relation allows creating the type of relation where Entity1 can have a single instance of Entity2, but
+ * Entity2 can have multiple instances of Entity1. Entity1 is the owner of the relationship, and stores the id of
+ * Entity2 on its side of the relation.
+ */
+export function ManyToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSide?: string | ((object: T) => any),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * A many-to-one relation allows creating the type of relation where Entity1 can have a single instance of Entity2, but
+ * Entity2 can have multiple instances of Entity1. Entity1 is the owner of the relationship, and stores the id of
+ * Entity2 on its side of the relation.
+ */
+export function ManyToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSideOrOptions?: string | ((object: T) => any) | RelationOptions,
+ options?: RelationOptions,
+): PropertyDecorator {
+ // Normalize parameters.
+ let inverseSideProperty: string | ((object: T) => any);
+ if (ObjectUtils.isObject(inverseSideOrOptions)) {
+ options = inverseSideOrOptions;
+ } else {
+ inverseSideProperty = inverseSideOrOptions as any;
+ }
+
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // Now try to determine if it is a lazy relation.
+ let isLazy = options && options.lazy === true;
+ if (!isLazy && Reflect && (Reflect as any).getMetadata) {
+ // automatic determination
+ const reflectedType = (Reflect as any).getMetadata('design:type', object, propertyName);
+ if (
+ reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise'
+ )
+ isLazy = true;
+ }
+
+ getMetadataArgsStorage().relations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ // propertyType: reflectedType,
+ relationType: 'many-to-one',
+ isLazy: isLazy,
+ type: typeFunctionOrTarget,
+ inverseSideProperty: inverseSideProperty,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/OneToMany.ts b/packages/@n8n/typeorm/src/decorator/relations/OneToMany.ts
new file mode 100644
index 00000000000..34910263d69
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/OneToMany.ts
@@ -0,0 +1,43 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { ObjectType } from '../../common/ObjectType';
+import { RelationOptions } from '../options/RelationOptions';
+
+/**
+ * A one-to-many relation allows creating the type of relation where Entity1 can have multiple instances of Entity2,
+ * but Entity2 has only one Entity1. Entity2 is the owner of the relationship, and stores the id of Entity1 on its
+ * side of the relation.
+ */
+export function OneToMany(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSide: string | ((object: T) => any),
+ options?: RelationOptions,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // Now try to determine if it is a lazy relation.
+ let isLazy = options && options.lazy === true;
+ if (!isLazy && Reflect && (Reflect as any).getMetadata) {
+ // automatic determination
+ const reflectedType = (Reflect as any).getMetadata('design:type', object, propertyName);
+ if (
+ reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise'
+ )
+ isLazy = true;
+ }
+
+ getMetadataArgsStorage().relations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ // propertyType: reflectedType,
+ isLazy: isLazy,
+ relationType: 'one-to-many',
+ type: typeFunctionOrTarget,
+ inverseSideProperty: inverseSide,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/OneToOne.ts b/packages/@n8n/typeorm/src/decorator/relations/OneToOne.ts
new file mode 100644
index 00000000000..2e08f7097c9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/OneToOne.ts
@@ -0,0 +1,70 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { ObjectType } from '../../common/ObjectType';
+import { RelationOptions } from '../options/RelationOptions';
+import { ObjectUtils } from '../../util/ObjectUtils';
+
+/**
+ * One-to-one relation allows to create direct relation between two entities. Entity1 have only one Entity2.
+ * Entity1 is an owner of the relationship, and storages Entity1 id on its own side.
+ */
+export function OneToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * One-to-one relation allows to create direct relation between two entities. Entity1 have only one Entity2.
+ * Entity1 is an owner of the relationship, and storages Entity1 id on its own side.
+ */
+export function OneToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSide?: string | ((object: T) => any),
+ options?: RelationOptions,
+): PropertyDecorator;
+
+/**
+ * One-to-one relation allows to create direct relation between two entities. Entity1 have only one Entity2.
+ * Entity1 is an owner of the relationship, and storages Entity1 id on its own side.
+ */
+export function OneToOne(
+ typeFunctionOrTarget: string | ((type?: any) => ObjectType),
+ inverseSideOrOptions?: string | ((object: T) => any) | RelationOptions,
+ options?: RelationOptions,
+): PropertyDecorator {
+ // normalize parameters
+ let inverseSideProperty: string | ((object: T) => any);
+ if (ObjectUtils.isObject(inverseSideOrOptions)) {
+ options = inverseSideOrOptions;
+ } else {
+ inverseSideProperty = inverseSideOrOptions as any;
+ }
+
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // now try to determine it its lazy relation
+ let isLazy = options && options.lazy === true ? true : false;
+ if (!isLazy && Reflect && (Reflect as any).getMetadata) {
+ // automatic determination
+ const reflectedType = (Reflect as any).getMetadata('design:type', object, propertyName);
+ if (
+ reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise'
+ )
+ isLazy = true;
+ }
+
+ getMetadataArgsStorage().relations.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ // propertyType: reflectedType,
+ isLazy: isLazy,
+ relationType: 'one-to-one',
+ type: typeFunctionOrTarget,
+ inverseSideProperty: inverseSideProperty,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/RelationCount.ts b/packages/@n8n/typeorm/src/decorator/relations/RelationCount.ts
new file mode 100644
index 00000000000..318b87d75ea
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/RelationCount.ts
@@ -0,0 +1,24 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationCountMetadataArgs } from '../../metadata-args/RelationCountMetadataArgs';
+import { SelectQueryBuilder } from '../../query-builder/SelectQueryBuilder';
+
+/**
+ * Holds a number of children in the closure table of the column.
+ *
+ * @deprecated Do not use this decorator, it may be removed in the future versions
+ */
+export function RelationCount(
+ relation: string | ((object: T) => any),
+ alias?: string,
+ queryBuilderFactory?: (qb: SelectQueryBuilder) => SelectQueryBuilder,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().relationCounts.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ relation: relation,
+ alias: alias,
+ queryBuilderFactory: queryBuilderFactory,
+ } as RelationCountMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/relations/RelationId.ts b/packages/@n8n/typeorm/src/decorator/relations/RelationId.ts
new file mode 100644
index 00000000000..cee24f359f9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/relations/RelationId.ts
@@ -0,0 +1,24 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationIdMetadataArgs } from '../../metadata-args/RelationIdMetadataArgs';
+import { SelectQueryBuilder } from '../../query-builder/SelectQueryBuilder';
+
+/**
+ * Special decorator used to extract relation id into separate entity property.
+ *
+ * @experimental
+ */
+export function RelationId(
+ relation: string | ((object: T) => any),
+ alias?: string,
+ queryBuilderFactory?: (qb: SelectQueryBuilder) => SelectQueryBuilder,
+): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().relationIds.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ relation: relation,
+ alias: alias,
+ queryBuilderFactory: queryBuilderFactory,
+ } as RelationIdMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/tree/Tree.ts b/packages/@n8n/typeorm/src/decorator/tree/Tree.ts
new file mode 100644
index 00000000000..124668d64a3
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/tree/Tree.ts
@@ -0,0 +1,20 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { TreeMetadataArgs } from '../../metadata-args/TreeMetadataArgs';
+import { TreeType } from '../../metadata/types/TreeTypes';
+import { ClosureTreeOptions } from '../../metadata/types/ClosureTreeOptions';
+
+/**
+ * Marks entity to work like a tree.
+ * Tree pattern that will be used for the tree entity should be specified.
+ * @TreeParent decorator must be used in tree entities.
+ * TreeRepository can be used to manipulate with tree entities.
+ */
+export function Tree(type: TreeType, options?: ClosureTreeOptions): ClassDecorator {
+ return function (target: Function) {
+ getMetadataArgsStorage().trees.push({
+ target: target,
+ type: type,
+ options: type === 'closure-table' ? options : undefined,
+ } as TreeMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/tree/TreeChildren.ts b/packages/@n8n/typeorm/src/decorator/tree/TreeChildren.ts
new file mode 100644
index 00000000000..e3a826af155
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/tree/TreeChildren.ts
@@ -0,0 +1,37 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { RelationOptions } from '../options/RelationOptions';
+
+/**
+ * Marks a entity property as a children of the tree.
+ * "Tree children" will contain all children (bind) of this entity.
+ */
+export function TreeChildren(options?: {
+ cascade?: boolean | ('insert' | 'update' | 'remove' | 'soft-remove' | 'recover')[];
+}): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // now try to determine it its lazy relation
+ const reflectedType =
+ Reflect && (Reflect as any).getMetadata
+ ? Reflect.getMetadata('design:type', object, propertyName)
+ : undefined;
+ const isLazy =
+ (reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise') ||
+ false;
+
+ // add one-to-many relation for this
+ getMetadataArgsStorage().relations.push({
+ isTreeChildren: true,
+ target: object.constructor,
+ propertyName: propertyName,
+ isLazy: isLazy,
+ relationType: 'one-to-many',
+ type: () => object.constructor,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/tree/TreeLevelColumn.ts b/packages/@n8n/typeorm/src/decorator/tree/TreeLevelColumn.ts
new file mode 100644
index 00000000000..d9d5cee4a7f
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/tree/TreeLevelColumn.ts
@@ -0,0 +1,16 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
+
+/**
+ * Creates a "level"/"length" column to the table that holds a closure table.
+ */
+export function TreeLevelColumn(): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ getMetadataArgsStorage().columns.push({
+ target: object.constructor,
+ propertyName: propertyName,
+ mode: 'treeLevel',
+ options: {},
+ } as ColumnMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/decorator/tree/TreeParent.ts b/packages/@n8n/typeorm/src/decorator/tree/TreeParent.ts
new file mode 100644
index 00000000000..163d885eda0
--- /dev/null
+++ b/packages/@n8n/typeorm/src/decorator/tree/TreeParent.ts
@@ -0,0 +1,37 @@
+import { getMetadataArgsStorage } from '../../globals';
+import { RelationMetadataArgs } from '../../metadata-args/RelationMetadataArgs';
+import { OnDeleteType } from '../../metadata/types/OnDeleteType';
+import { RelationOptions } from '../options/RelationOptions';
+
+/**
+ * Marks a entity property as a parent of the tree.
+ * "Tree parent" indicates who owns (is a parent) of this entity in tree structure.
+ */
+export function TreeParent(options?: {
+ onDelete?: OnDeleteType;
+}): PropertyDecorator {
+ return function (object: Object, propertyName: string) {
+ if (!options) options = {} as RelationOptions;
+
+ // now try to determine it its lazy relation
+ const reflectedType =
+ Reflect && (Reflect as any).getMetadata
+ ? Reflect.getMetadata('design:type', object, propertyName)
+ : undefined;
+ const isLazy =
+ (reflectedType &&
+ typeof reflectedType.name === 'string' &&
+ reflectedType.name.toLowerCase() === 'promise') ||
+ false;
+
+ getMetadataArgsStorage().relations.push({
+ isTreeParent: true,
+ target: object.constructor,
+ propertyName: propertyName,
+ isLazy: isLazy,
+ relationType: 'many-to-one',
+ type: () => object.constructor,
+ options: options,
+ } as RelationMetadataArgs);
+ };
+}
diff --git a/packages/@n8n/typeorm/src/driver/Driver.ts b/packages/@n8n/typeorm/src/driver/Driver.ts
new file mode 100644
index 00000000000..d9b4ed6638c
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/Driver.ts
@@ -0,0 +1,288 @@
+import type { QueryRunner } from '../query-runner/QueryRunner';
+import type { ColumnMetadata } from '../metadata/ColumnMetadata';
+import type { ObjectLiteral } from '../common/ObjectLiteral';
+import type { ColumnType } from './types/ColumnTypes';
+import type { CteCapabilities } from './types/CteCapabilities';
+import type { MappedColumnTypes } from './types/MappedColumnTypes';
+import type { SchemaBuilder } from '../schema-builder/SchemaBuilder';
+import type { DataTypeDefaults } from './types/DataTypeDefaults';
+import type { BaseDataSourceOptions } from '../data-source/BaseDataSourceOptions';
+import type { TableColumn } from '../schema-builder/table/TableColumn';
+import type { EntityMetadata } from '../metadata/EntityMetadata';
+import type { ReplicationMode } from './types/ReplicationMode';
+import type { Table } from '../schema-builder/table/Table';
+import type { View } from '../schema-builder/view/View';
+import type { TableForeignKey } from '../schema-builder/table/TableForeignKey';
+import type { UpsertType } from './types/UpsertType';
+import type { OnDeleteType } from '../metadata/types/OnDeleteType';
+import type { OnUpdateType } from '../metadata/types/OnUpdateType';
+import type { DataSource } from '../data-source';
+
+export type ReturningType = 'insert' | 'update' | 'delete';
+
+/**
+ * Driver organizes TypeORM communication with specific database management system.
+ */
+export interface Driver {
+ /**
+ * Connection options.
+ */
+ options: BaseDataSourceOptions;
+
+ /**
+ * Database version/release. Often requires a SQL query to the DB, so it is not always set
+ */
+ version?: string;
+
+ /**
+ * Database name used to perform all write queries.
+ *
+ * todo: probably move into query runner.
+ */
+ database?: string;
+
+ /**
+ * Schema name used to perform all write queries.
+ */
+ schema?: string;
+
+ /**
+ * Indicates if replication is enabled.
+ */
+ isReplicated: boolean;
+
+ /**
+ * Indicates if tree tables are supported by this driver.
+ */
+ treeSupport: boolean;
+
+ /**
+ * Represent transaction support by this driver
+ */
+ transactionSupport: 'simple' | 'nested' | 'none';
+
+ /**
+ * Gets list of supported column data types by a driver.
+ */
+ supportedDataTypes: ColumnType[];
+
+ /**
+ * Returns type of upsert supported by driver if any
+ */
+ supportedUpsertTypes: UpsertType[];
+
+ /**
+ * Returns list of supported onDelete types by driver
+ */
+ supportedOnDeleteTypes?: OnDeleteType[];
+
+ /**
+ * Returns list of supported onUpdate types by driver
+ */
+ supportedOnUpdateTypes?: OnUpdateType[];
+
+ /**
+ * Default values of length, precision and scale depends on column data type.
+ * Used in the cases when length/precision/scale is not specified by user.
+ */
+ dataTypeDefaults: DataTypeDefaults;
+
+ /**
+ * Gets list of spatial column data types.
+ */
+ spatialTypes: ColumnType[];
+
+ /**
+ * Gets list of column data types that support length by a driver.
+ */
+ withLengthColumnTypes: ColumnType[];
+
+ /**
+ * Gets list of column data types that support precision by a driver.
+ */
+ withPrecisionColumnTypes: ColumnType[];
+
+ /**
+ * Gets list of column data types that support scale by a driver.
+ */
+ withScaleColumnTypes: ColumnType[];
+
+ /**
+ * Orm has special columns and we need to know what database column types should be for those types.
+ * Column types are driver dependant.
+ */
+ mappedDataTypes: MappedColumnTypes;
+
+ /**
+ * The prefix used for the parameters
+ */
+ parametersPrefix?: string;
+
+ /**
+ * Max length allowed by the DBMS for aliases (execution of queries).
+ */
+ maxAliasLength?: number;
+
+ cteCapabilities: CteCapabilities;
+
+ /**
+ * Dummy table name
+ */
+ dummyTableName?: string;
+
+ /**
+ * Performs connection to the database.
+ * Depend on driver type it may create a connection pool.
+ */
+ connect(): Promise;
+
+ /**
+ * Makes any action after connection (e.g. create extensions in Postgres driver).
+ */
+ afterConnect(): Promise;
+
+ /**
+ * Closes connection with database and releases all resources.
+ */
+ disconnect(): Promise;
+
+ /**
+ * Synchronizes database schema (creates tables, indices, etc).
+ */
+ createSchemaBuilder(): SchemaBuilder;
+
+ /**
+ * Creates a query runner used for common queries.
+ */
+ createQueryRunner(mode: ReplicationMode): QueryRunner;
+
+ /**
+ * Replaces parameters in the given sql with special escaping character
+ * and an array of parameter names to be passed to a query.
+ */
+ escapeQueryWithParameters(
+ sql: string,
+ parameters: ObjectLiteral,
+ nativeParameters: ObjectLiteral,
+ ): [string, any[]];
+
+ /**
+ * Escapes a table name, column name or an alias.
+ *
+ * todo: probably escape should be able to handle dots in the names and automatically escape them
+ */
+ escape(name: string): string;
+
+ /**
+ * Build full table name with database name, schema name and table name.
+ * E.g. myDB.mySchema.myTable
+ */
+ buildTableName(tableName: string, schema?: string, database?: string): string;
+
+ /**
+ * Parse a target table name or other types and return a normalized table definition.
+ */
+ parseTableName(target: EntityMetadata | Table | View | TableForeignKey | string): {
+ tableName: string;
+ schema?: string;
+ database?: string;
+ };
+
+ /**
+ * Prepares given value to a value to be persisted, based on its column type and metadata.
+ */
+ preparePersistentValue(value: any, column: ColumnMetadata): any;
+
+ /**
+ * Prepares given value to a value to be persisted, based on its column type.
+ */
+ prepareHydratedValue(value: any, column: ColumnMetadata): any;
+
+ /**
+ * Transforms type of the given column to a database column type.
+ */
+ normalizeType(column: {
+ type?: ColumnType | string;
+ length?: number | string;
+ precision?: number | null;
+ scale?: number;
+ isArray?: boolean;
+ }): string;
+
+ /**
+ * Normalizes "default" value of the column.
+ */
+ normalizeDefault(columnMetadata: ColumnMetadata): string | undefined;
+
+ /**
+ * Normalizes "isUnique" value of the column.
+ */
+ normalizeIsUnique(column: ColumnMetadata): boolean;
+
+ /**
+ * Calculates column length taking into account the default length values.
+ */
+ getColumnLength(column: ColumnMetadata): string;
+
+ /**
+ * Normalizes "default" value of the column.
+ */
+ createFullType(column: TableColumn): string;
+
+ /**
+ * Obtains a new database connection to a master server.
+ * Used for replication.
+ * If replication is not setup then returns default connection's database connection.
+ */
+ obtainMasterConnection(): Promise;
+
+ /**
+ * Obtains a new database connection to a slave server.
+ * Used for replication.
+ * If replication is not setup then returns master (default) connection's database connection.
+ */
+ obtainSlaveConnection(): Promise;
+
+ /**
+ * Creates generated map of values generated or returned by database after INSERT query.
+ */
+ createGeneratedMap(
+ metadata: EntityMetadata,
+ insertResult: any,
+ entityIndex?: number,
+ entityNum?: number,
+ ): ObjectLiteral | undefined;
+
+ /**
+ * Differentiate columns of this table and columns from the given column metadatas columns
+ * and returns only changed.
+ */
+ findChangedColumns(
+ tableColumns: TableColumn[],
+ columnMetadatas: ColumnMetadata[],
+ ): ColumnMetadata[];
+
+ /**
+ * Returns true if driver supports RETURNING / OUTPUT statement.
+ */
+ isReturningSqlSupported(returningType: ReturningType): boolean;
+
+ /**
+ * Returns true if driver supports uuid values generation on its own.
+ */
+ isUUIDGenerationSupported(): boolean;
+
+ /**
+ * Returns true if driver supports fulltext indices.
+ */
+ isFullTextColumnTypeSupported(): boolean;
+
+ /**
+ * Creates an escaped parameter.
+ */
+ createParameter(parameterName: string, index: number): string;
+}
+
+export interface DriverConstructor {
+ new (connection: DataSource): Driver;
+}
diff --git a/packages/@n8n/typeorm/src/driver/DriverFactory.ts b/packages/@n8n/typeorm/src/driver/DriverFactory.ts
new file mode 100644
index 00000000000..15f29c7c7ce
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/DriverFactory.ts
@@ -0,0 +1,29 @@
+import type { Driver, DriverConstructor } from './Driver';
+import type { DataSource } from '../data-source/DataSource';
+import { MissingDriverError } from '../error/MissingDriverError';
+
+const getDriver = async (type: DataSource['options']['type']): Promise => {
+ switch (type) {
+ case 'postgres':
+ return (await import('./postgres/PostgresDriver')).PostgresDriver;
+ case 'sqlite':
+ return (await import('./sqlite/SqliteDriver')).SqliteDriver;
+ case 'sqlite-pooled':
+ return (await import('./sqlite-pooled/SqliteReadWriteDriver')).SqliteReadWriteDriver;
+ default:
+ throw new MissingDriverError(type, ['postgres', 'sqlite', 'sqlite-pooled']);
+ }
+};
+
+/**
+ * Helps to create drivers.
+ */
+export class DriverFactory {
+ /**
+ * Creates a new driver depend on a given connection's driver type.
+ */
+ static async create(connection: DataSource): Promise {
+ const { type } = connection.options;
+ return new (await getDriver(type))(connection);
+ }
+}
diff --git a/packages/@n8n/typeorm/src/driver/DriverUtils.ts b/packages/@n8n/typeorm/src/driver/DriverUtils.ts
new file mode 100644
index 00000000000..e23a2a454d9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/DriverUtils.ts
@@ -0,0 +1,257 @@
+import { Driver } from './Driver';
+import { hash, shorten } from '../util/StringUtils';
+import { VersionUtils } from '../util/VersionUtils';
+
+/**
+ * Common driver utility functions.
+ */
+export class DriverUtils {
+ // -------------------------------------------------------------------------
+ // Public Static Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Returns true if given driver is SQLite-based driver.
+ */
+ static isSQLiteFamily(driver: Driver): boolean {
+ return [
+ 'sqlite',
+ 'sqlite-pooled',
+ 'cordova',
+ 'react-native',
+ 'nativescript',
+ 'sqljs',
+ 'expo',
+ 'better-sqlite3',
+ 'capacitor',
+ 'libsql',
+ ].includes(driver.options.type);
+ }
+
+ static isReleaseVersionOrGreater(driver: Driver, version: string): boolean {
+ return driver.version != null && VersionUtils.isGreaterOrEqual(driver.version, version);
+ }
+
+ static isPostgresFamily(driver: Driver): boolean {
+ return ['postgres', 'aurora-postgres', 'cockroachdb'].includes(driver.options.type);
+ }
+
+ /**
+ * Normalizes and builds a new driver options.
+ * Extracts settings from connection url and sets to a new options object.
+ */
+ static buildDriverOptions(options: any, buildOptions?: { useSid: boolean }): any {
+ if (options.url) {
+ const urlDriverOptions = this.parseConnectionUrl(options.url) as {
+ [key: string]: any;
+ };
+
+ if (buildOptions && buildOptions.useSid && urlDriverOptions.database) {
+ urlDriverOptions.sid = urlDriverOptions.database;
+ }
+
+ for (const key of Object.keys(urlDriverOptions)) {
+ if (typeof urlDriverOptions[key] === 'undefined') {
+ delete urlDriverOptions[key];
+ }
+ }
+
+ return Object.assign({}, options, urlDriverOptions);
+ }
+ return Object.assign({}, options);
+ }
+
+ /**
+ * buildDriverOptions for MongodDB only to support replica set
+ */
+ static buildMongoDBDriverOptions(options: any, buildOptions?: { useSid: boolean }): any {
+ if (options.url) {
+ const urlDriverOptions = this.parseMongoDBConnectionUrl(options.url) as {
+ [key: string]: any;
+ };
+
+ if (buildOptions && buildOptions.useSid && urlDriverOptions.database) {
+ urlDriverOptions.sid = urlDriverOptions.database;
+ }
+
+ for (const key of Object.keys(urlDriverOptions)) {
+ if (typeof urlDriverOptions[key] === 'undefined') {
+ delete urlDriverOptions[key];
+ }
+ }
+
+ return Object.assign({}, options, urlDriverOptions);
+ }
+ return Object.assign({}, options);
+ }
+
+ /**
+ * Joins and shortens alias if needed.
+ *
+ * If the alias length is greater than the limit allowed by the current
+ * driver, replaces it with a shortend string, if the shortend string
+ * is still too long, it will then hash the alias.
+ *
+ * @param driver Current `Driver`.
+ * @param buildOptions Optional settings.
+ * @param alias Alias parts.
+ *
+ * @return An alias that is no longer than the divers max alias length.
+ */
+ static buildAlias(
+ { maxAliasLength }: Driver,
+ buildOptions: { shorten?: boolean; joiner?: string } | undefined,
+ ...alias: string[]
+ ): string {
+ const joiner = buildOptions && buildOptions.joiner ? buildOptions.joiner : '_';
+
+ let newAlias = alias.length === 1 ? alias[0] : alias.join(joiner);
+
+ if (maxAliasLength && maxAliasLength > 0 && newAlias.length > maxAliasLength) {
+ if (buildOptions && buildOptions.shorten === true) {
+ const shortenedAlias = shorten(newAlias);
+ if (shortenedAlias.length < maxAliasLength) {
+ return shortenedAlias;
+ }
+ }
+
+ return hash(newAlias, { length: maxAliasLength });
+ }
+
+ return newAlias;
+ }
+
+ /**
+ * @deprecated use `buildAlias` instead.
+ */
+ static buildColumnAlias(
+ { maxAliasLength }: Driver,
+ buildOptions: { shorten?: boolean; joiner?: string } | string,
+ ...alias: string[]
+ ) {
+ if (typeof buildOptions === 'string') {
+ alias.unshift(buildOptions);
+ buildOptions = { shorten: false, joiner: '_' };
+ } else {
+ buildOptions = Object.assign({ shorten: false, joiner: '_' }, buildOptions);
+ }
+ return this.buildAlias({ maxAliasLength } as Driver, buildOptions, ...alias);
+ }
+
+ // -------------------------------------------------------------------------
+ // Private Static Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Extracts connection data from the connection url.
+ */
+ private static parseConnectionUrl(url: string) {
+ const type = url.split(':')[0];
+ const firstSlashes = url.indexOf('//');
+ const preBase = url.substr(firstSlashes + 2);
+ const secondSlash = preBase.indexOf('/');
+ const base = secondSlash !== -1 ? preBase.substr(0, secondSlash) : preBase;
+ let afterBase = secondSlash !== -1 ? preBase.substr(secondSlash + 1) : undefined;
+ // remove mongodb query params
+ if (afterBase && afterBase.indexOf('?') !== -1) {
+ afterBase = afterBase.substr(0, afterBase.indexOf('?'));
+ }
+
+ const lastAtSign = base.lastIndexOf('@');
+ const usernameAndPassword = base.substr(0, lastAtSign);
+ const hostAndPort = base.substr(lastAtSign + 1);
+
+ let username = usernameAndPassword;
+ let password = '';
+ const firstColon = usernameAndPassword.indexOf(':');
+ if (firstColon !== -1) {
+ username = usernameAndPassword.substr(0, firstColon);
+ password = usernameAndPassword.substr(firstColon + 1);
+ }
+ const [host, port] = hostAndPort.split(':');
+
+ return {
+ type: type,
+ host: host,
+ username: decodeURIComponent(username),
+ password: decodeURIComponent(password),
+ port: port ? parseInt(port) : undefined,
+ database: afterBase || undefined,
+ };
+ }
+
+ /**
+ * Extracts connection data from the connection url for MongoDB to support replica set.
+ */
+ private static parseMongoDBConnectionUrl(url: string) {
+ const type = url.split(':')[0];
+ const firstSlashes = url.indexOf('//');
+ const preBase = url.substr(firstSlashes + 2);
+ const secondSlash = preBase.indexOf('/');
+ const base = secondSlash !== -1 ? preBase.substr(0, secondSlash) : preBase;
+ let afterBase = secondSlash !== -1 ? preBase.substr(secondSlash + 1) : undefined;
+ let afterQuestionMark = '';
+ let host = undefined;
+ let port = undefined;
+ let hostReplicaSet = undefined;
+ let replicaSet = undefined;
+
+ let optionsObject: any = {};
+
+ if (afterBase && afterBase.indexOf('?') !== -1) {
+ // split params
+ afterQuestionMark = afterBase.substr(afterBase.indexOf('?') + 1, afterBase.length);
+
+ const optionsList = afterQuestionMark.split('&');
+ let optionKey: string;
+ let optionValue: string;
+
+ // create optionsObject for merge with connectionUrl object before return
+ optionsList.forEach((optionItem) => {
+ optionKey = optionItem.split('=')[0];
+ optionValue = optionItem.split('=')[1];
+ optionsObject[optionKey] = optionValue;
+ });
+
+ // specific replicaSet value to set options about hostReplicaSet
+ replicaSet = optionsObject['replicaSet'];
+ afterBase = afterBase.substr(0, afterBase.indexOf('?'));
+ }
+
+ const lastAtSign = base.lastIndexOf('@');
+ const usernameAndPassword = base.substr(0, lastAtSign);
+ const hostAndPort = base.substr(lastAtSign + 1);
+
+ let username = usernameAndPassword;
+ let password = '';
+ const firstColon = usernameAndPassword.indexOf(':');
+ if (firstColon !== -1) {
+ username = usernameAndPassword.substr(0, firstColon);
+ password = usernameAndPassword.substr(firstColon + 1);
+ }
+
+ // If replicaSet have value set It as hostlist, If not set like standalone host
+ if (replicaSet) {
+ hostReplicaSet = hostAndPort;
+ } else {
+ [host, port] = hostAndPort.split(':');
+ }
+
+ let connectionUrl: any = {
+ type: type,
+ host: host,
+ hostReplicaSet: hostReplicaSet,
+ username: decodeURIComponent(username),
+ password: decodeURIComponent(password),
+ port: port ? parseInt(port) : undefined,
+ database: afterBase || undefined,
+ };
+
+ // Loop to set every options in connectionUrl to object
+ for (const [key, value] of Object.entries(optionsObject)) {
+ connectionUrl[key] = value;
+ }
+
+ return connectionUrl;
+ }
+}
diff --git a/packages/@n8n/typeorm/src/driver/Query.ts b/packages/@n8n/typeorm/src/driver/Query.ts
new file mode 100644
index 00000000000..992a72568cd
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/Query.ts
@@ -0,0 +1,11 @@
+/**
+ * This class stores query and its parameters
+ */
+export class Query {
+ readonly '@instanceof' = Symbol.for('Query');
+
+ constructor(
+ public query: string,
+ public parameters?: any[],
+ ) {}
+}
diff --git a/packages/@n8n/typeorm/src/driver/SqlInMemory.ts b/packages/@n8n/typeorm/src/driver/SqlInMemory.ts
new file mode 100644
index 00000000000..3885fbb1d68
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/SqlInMemory.ts
@@ -0,0 +1,9 @@
+import { Query } from './Query';
+
+/**
+ * This class stores up and down queries needed for migrations functionality.
+ */
+export class SqlInMemory {
+ upQueries: Query[] = [];
+ downQueries: Query[] = [];
+}
diff --git a/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionCredentialsOptions.ts b/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionCredentialsOptions.ts
new file mode 100644
index 00000000000..20afcd98637
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionCredentialsOptions.ts
@@ -0,0 +1,47 @@
+import { TlsOptions } from 'tls';
+
+/**
+ * Postgres specific connection credential options.
+ */
+export interface PostgresConnectionCredentialsOptions {
+ /**
+ * Connection url where perform connection to.
+ */
+ readonly url?: string;
+
+ /**
+ * Database host.
+ */
+ readonly host?: string;
+
+ /**
+ * Database host port.
+ */
+ readonly port?: number;
+
+ /**
+ * Database username.
+ */
+ readonly username?: string;
+
+ /**
+ * Database password.
+ */
+ readonly password?: string | (() => string) | (() => Promise);
+
+ /**
+ * Database name to connect to.
+ */
+ readonly database?: string;
+
+ /**
+ * Object with ssl parameters
+ */
+ readonly ssl?: boolean | TlsOptions;
+
+ /**
+ * sets the application_name var to help db administrators identify
+ * the service using this connection. Defaults to 'undefined'
+ */
+ readonly applicationName?: string;
+}
diff --git a/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionOptions.ts b/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionOptions.ts
new file mode 100644
index 00000000000..d0b1a3c9b48
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/postgres/PostgresConnectionOptions.ts
@@ -0,0 +1,118 @@
+import { BaseDataSourceOptions } from '../../data-source/BaseDataSourceOptions';
+import { ReplicationMode } from '../types/ReplicationMode';
+import { PostgresConnectionCredentialsOptions } from './PostgresConnectionCredentialsOptions';
+
+/**
+ * Postgres-specific connection options.
+ */
+export interface PostgresConnectionOptions
+ extends BaseDataSourceOptions,
+ PostgresConnectionCredentialsOptions {
+ /**
+ * Database type.
+ */
+ readonly type: 'postgres';
+
+ /**
+ * Schema name.
+ */
+ readonly schema?: string;
+
+ /**
+ * The driver object
+ * This defaults to `require("pg")`.
+ */
+ readonly driver?: any;
+
+ /**
+ * The driver object
+ * This defaults to `require("pg-native")`.
+ */
+ readonly nativeDriver?: any;
+
+ /**
+ * A boolean determining whether to pass time values in UTC or local time. (default: false).
+ */
+ readonly useUTC?: boolean;
+
+ /**
+ * Replication setup.
+ */
+ readonly replication?: {
+ /**
+ * Master server used by orm to perform writes.
+ */
+ readonly master: PostgresConnectionCredentialsOptions;
+
+ /**
+ * List of read-from severs (slaves).
+ */
+ readonly slaves: PostgresConnectionCredentialsOptions[];
+
+ /**
+ * Default connection pool to use for SELECT queries
+ * @default "slave"
+ */
+ readonly defaultMode?: ReplicationMode;
+ };
+
+ /**
+ * The milliseconds before a timeout occurs during the initial connection to the postgres
+ * server. If undefined, or set to 0, there is no timeout. Defaults to undefined.
+ */
+ readonly connectTimeoutMS?: number;
+
+ /**
+ * Number of milliseconds before PostgreSQL cancels a running statement (server-side).
+ * Default is no timeout.
+ */
+ readonly statementTimeout?: number;
+
+ /**
+ * Number of milliseconds the client waits for a query response before
+ * terminating the connection (client-side). Default is no timeout.
+ */
+ readonly queryTimeout?: number;
+
+ /**
+ * 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';
+
+ /*
+ * Function handling errors thrown by drivers pool.
+ * Defaults to logging error with `warn` level.
+ */
+ readonly poolErrorHandler?: (err: any) => any;
+
+ /**
+ * Include notification messages from Postgres server in client logs.
+ * Note: This only works with the JavaScript `pg` driver.
+ * The native `pg-native` driver does not emit notice/notification events.
+ */
+ readonly logNotifications?: boolean;
+
+ /**
+ * Automatically install postgres extensions
+ */
+ readonly installExtensions?: boolean;
+
+ /**
+ * Return 64-bit integers (int8) as JavaScript integers.
+ *
+ * Because JavaScript doesn't have support for 64-bit integers node-postgres cannot confidently
+ * parse int8 data type results as numbers because if you have a huge number it will overflow
+ * and the result you'd get back from node-postgres would not be the result in the database.
+ * That would be a very bad thing so node-postgres just returns int8 results as strings and leaves the parsing up to you.
+ *
+ * Enabling parseInt8 will cause node-postgres to parse int8 results as numbers.
+ * Note: the maximum safe integer in js is: Number.MAX_SAFE_INTEGER (`+2^53`)
+ *
+ * @see [JavaScript Number objects](http://ecma262-5.com/ELS5_HTML.htm#Section_8.5)
+ * @see [node-postgres int8 explanation](https://github.com/brianc/node-pg-types#:~:text=on%20projects%3A%20return-,64%2Dbit%20integers,-(int8)%20as)
+ * @see [node-postgres defaults.parseInt8 implementation](https://github.com/brianc/node-postgres/blob/pg%408.8.0/packages/pg/lib/defaults.js#L80)
+ */
+ readonly parseInt8?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/driver/postgres/PostgresDriver.ts b/packages/@n8n/typeorm/src/driver/postgres/PostgresDriver.ts
new file mode 100644
index 00000000000..8822627204e
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/postgres/PostgresDriver.ts
@@ -0,0 +1,1478 @@
+import { ObjectLiteral } from '../../common/ObjectLiteral';
+import { DataSource } from '../../data-source/DataSource';
+import { ConnectionIsNotSetError } from '../../error/ConnectionIsNotSetError';
+import { DriverPackageNotInstalledError } from '../../error/DriverPackageNotInstalledError';
+import { ColumnMetadata } from '../../metadata/ColumnMetadata';
+import { EntityMetadata } from '../../metadata/EntityMetadata';
+import { QueryRunner } from '../../query-runner/QueryRunner';
+import { RdbmsSchemaBuilder } from '../../schema-builder/RdbmsSchemaBuilder';
+import { TableColumn } from '../../schema-builder/table/TableColumn';
+import { ApplyValueTransformers } from '../../util/ApplyValueTransformers';
+import { DateUtils } from '../../util/DateUtils';
+import { OrmUtils } from '../../util/OrmUtils';
+import { Driver } from '../Driver';
+import { ColumnType } from '../types/ColumnTypes';
+import { CteCapabilities } from '../types/CteCapabilities';
+import { DataTypeDefaults } from '../types/DataTypeDefaults';
+import { MappedColumnTypes } from '../types/MappedColumnTypes';
+import { ReplicationMode } from '../types/ReplicationMode';
+import { VersionUtils } from '../../util/VersionUtils';
+import { PostgresConnectionCredentialsOptions } from './PostgresConnectionCredentialsOptions';
+import { PostgresConnectionOptions } from './PostgresConnectionOptions';
+import { PostgresQueryRunner } from './PostgresQueryRunner';
+import { DriverUtils } from '../DriverUtils';
+import { TypeORMError } from '../../error';
+import { Table } from '../../schema-builder/table/Table';
+import { View } from '../../schema-builder/view/View';
+import { TableForeignKey } from '../../schema-builder/table/TableForeignKey';
+import { InstanceChecker } from '../../util/InstanceChecker';
+import { UpsertType } from '../types/UpsertType';
+import type pg from 'pg';
+
+/**
+ * Organizes communication with PostgreSQL DBMS.
+ */
+export class PostgresDriver implements Driver {
+ // -------------------------------------------------------------------------
+ // Public Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connection used by driver.
+ */
+ connection: DataSource;
+
+ /**
+ * Postgres underlying library.
+ */
+ postgres: typeof pg;
+
+ /**
+ * Pool for master database.
+ */
+ master: pg.Pool | undefined;
+
+ /**
+ * Pool for slave databases.
+ * Used in replication.
+ */
+ slaves: any[] = [];
+
+ /**
+ * We store all created query runners because we need to release them.
+ */
+ connectedQueryRunners: QueryRunner[] = [];
+
+ // -------------------------------------------------------------------------
+ // Public Implemented Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connection options.
+ */
+ options: PostgresConnectionOptions;
+
+ /**
+ * Version of Postgres. Requires a SQL query to the DB, so it is not always set
+ */
+ version?: string;
+
+ /**
+ * Database name used to perform all write queries.
+ */
+ database?: string;
+
+ /**
+ * Schema name used to perform all write queries.
+ */
+ schema?: string;
+
+ /**
+ * Schema that's used internally by Postgres for object resolution.
+ *
+ * Because we never set this we have to track it in separately from the `schema` so
+ * we know when we have to specify the full schema or not.
+ *
+ * In most cases this will be `public`.
+ */
+ searchSchema?: string;
+
+ /**
+ * Indicates if replication is enabled.
+ */
+ isReplicated: boolean = false;
+
+ /**
+ * Indicates if tree tables are supported by this driver.
+ */
+ treeSupport = true;
+
+ /**
+ * Represent transaction support by this driver
+ */
+ transactionSupport = 'nested' as const;
+
+ /**
+ * Gets list of supported column data types by a driver.
+ *
+ * @see https://www.tutorialspoint.com/postgresql/postgresql_data_types.htm
+ * @see https://www.postgresql.org/docs/9.2/static/datatype.html
+ */
+ supportedDataTypes: ColumnType[] = [
+ 'int',
+ 'int2',
+ 'int4',
+ 'int8',
+ 'smallint',
+ 'integer',
+ 'bigint',
+ 'decimal',
+ 'numeric',
+ 'real',
+ 'float',
+ 'float4',
+ 'float8',
+ 'double precision',
+ 'money',
+ 'character varying',
+ 'varchar',
+ 'character',
+ 'char',
+ 'text',
+ 'citext',
+ 'hstore',
+ 'bytea',
+ 'bit',
+ 'varbit',
+ 'bit varying',
+ 'timetz',
+ 'timestamptz',
+ 'timestamp',
+ 'timestamp without time zone',
+ 'timestamp with time zone',
+ 'date',
+ 'time',
+ 'time without time zone',
+ 'time with time zone',
+ 'interval',
+ 'bool',
+ 'boolean',
+ 'enum',
+ 'point',
+ 'line',
+ 'lseg',
+ 'box',
+ 'path',
+ 'polygon',
+ 'circle',
+ 'cidr',
+ 'inet',
+ 'macaddr',
+ 'tsvector',
+ 'tsquery',
+ 'uuid',
+ 'xml',
+ 'json',
+ 'jsonb',
+ 'int4range',
+ 'int8range',
+ 'numrange',
+ 'tsrange',
+ 'tstzrange',
+ 'daterange',
+ 'int4multirange',
+ 'int8multirange',
+ 'nummultirange',
+ 'tsmultirange',
+ 'tstzmultirange',
+ 'datemultirange',
+ 'geometry',
+ 'geography',
+ 'cube',
+ 'ltree',
+ ];
+
+ /**
+ * Returns type of upsert supported by driver if any
+ */
+ supportedUpsertTypes: UpsertType[] = ['on-conflict-do-update'];
+
+ /**
+ * Gets list of spatial column data types.
+ */
+ spatialTypes: ColumnType[] = ['geometry', 'geography'];
+
+ /**
+ * Gets list of column data types that support length by a driver.
+ */
+ withLengthColumnTypes: ColumnType[] = [
+ 'character varying',
+ 'varchar',
+ 'character',
+ 'char',
+ 'bit',
+ 'varbit',
+ 'bit varying',
+ ];
+
+ /**
+ * Gets list of column data types that support precision by a driver.
+ */
+ withPrecisionColumnTypes: ColumnType[] = [
+ 'numeric',
+ 'decimal',
+ 'interval',
+ 'time without time zone',
+ 'time with time zone',
+ 'timestamp without time zone',
+ 'timestamp with time zone',
+ ];
+
+ /**
+ * Gets list of column data types that support scale by a driver.
+ */
+ withScaleColumnTypes: ColumnType[] = ['numeric', 'decimal'];
+
+ /**
+ * Orm has special columns and we need to know what database column types should be for those types.
+ * Column types are driver dependant.
+ */
+ mappedDataTypes: MappedColumnTypes = {
+ createDate: 'timestamp',
+ createDateDefault: 'now()',
+ updateDate: 'timestamp',
+ updateDateDefault: 'now()',
+ deleteDate: 'timestamp',
+ deleteDateNullable: true,
+ version: 'int4',
+ treeLevel: 'int4',
+ migrationId: 'int4',
+ migrationName: 'varchar',
+ migrationTimestamp: 'int8',
+ cacheId: 'int4',
+ cacheIdentifier: 'varchar',
+ cacheTime: 'int8',
+ cacheDuration: 'int4',
+ cacheQuery: 'text',
+ cacheResult: 'text',
+ metadataType: 'varchar',
+ metadataDatabase: 'varchar',
+ metadataSchema: 'varchar',
+ metadataTable: 'varchar',
+ metadataName: 'varchar',
+ metadataValue: 'text',
+ };
+
+ /**
+ * The prefix used for the parameters
+ */
+ parametersPrefix: string = '$';
+
+ /**
+ * Default values of length, precision and scale depends on column data type.
+ * Used in the cases when length/precision/scale is not specified by user.
+ */
+ dataTypeDefaults: DataTypeDefaults = {
+ character: { length: 1 },
+ bit: { length: 1 },
+ interval: { precision: 6 },
+ 'time without time zone': { precision: 6 },
+ 'time with time zone': { precision: 6 },
+ 'timestamp without time zone': { precision: 6 },
+ 'timestamp with time zone': { precision: 6 },
+ };
+
+ /**
+ * Max length allowed by Postgres for aliases.
+ * @see https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
+ */
+ maxAliasLength = 63;
+
+ isGeneratedColumnsSupported: boolean = false;
+
+ cteCapabilities: CteCapabilities = {
+ enabled: true,
+ writable: true,
+ requiresRecursiveHint: true,
+ materializedHint: true,
+ };
+
+ /**
+ * Is true if the native driver is used. The native driver does not support
+ * streaming, so you can use this property to disable streaming in your
+ * application.
+ */
+ isNative = false;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(connection?: DataSource) {
+ if (!connection) {
+ return;
+ }
+
+ this.connection = connection;
+ this.options = connection.options as PostgresConnectionOptions;
+ this.isReplicated = this.options.replication ? true : false;
+ if (this.options.useUTC) {
+ process.env.PGTZ = 'UTC';
+ }
+ // load postgres package
+ this.loadDependencies();
+
+ this.database = DriverUtils.buildDriverOptions(
+ this.options.replication ? this.options.replication.master : this.options,
+ ).database;
+ this.schema = DriverUtils.buildDriverOptions(this.options).schema;
+
+ // ObjectUtils.assign(this.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
+ // validate options to make sure everything is set
+ // todo: revisit validation with replication in mind
+ // if (!this.options.host)
+ // throw new DriverOptionNotSetError("host");
+ // if (!this.options.username)
+ // throw new DriverOptionNotSetError("username");
+ // if (!this.options.database)
+ // throw new DriverOptionNotSetError("database");
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Implemented Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Performs connection to the database.
+ * Based on pooling options, it can either create connection immediately,
+ * either create a pool and create connection when needed.
+ */
+ async connect(): Promise {
+ if (this.options.replication) {
+ this.slaves = await Promise.all(
+ this.options.replication.slaves.map((slave) => {
+ return this.createPool(this.options, slave);
+ }),
+ );
+ this.master = await this.createPool(this.options, this.options.replication.master);
+ } else {
+ this.master = await this.createPool(this.options, this.options);
+ }
+
+ if (!this.database || !this.searchSchema) {
+ const queryRunner = await this.createQueryRunner('master');
+
+ if (!this.database) {
+ this.database = await queryRunner.getCurrentDatabase();
+ }
+
+ if (!this.searchSchema) {
+ this.searchSchema = await queryRunner.getCurrentSchema();
+ }
+
+ await queryRunner.release();
+ }
+
+ if (!this.schema) {
+ this.schema = this.searchSchema;
+ }
+ }
+
+ /**
+ * Makes any action after connection (e.g. create extensions in Postgres driver).
+ */
+ async afterConnect(): Promise {
+ const extensionsMetadata = await this.checkMetadataForExtensions();
+ const [connection, release] = await this.obtainMasterConnection();
+
+ // Create schema if it doesn't exist (only runs once during initialization)
+ const { schema } = this.options;
+ if (schema && schema !== 'public') {
+ await this.executeQuery(connection, `CREATE SCHEMA IF NOT EXISTS "${schema}"`);
+ }
+
+ const installExtensions =
+ this.options.installExtensions === undefined || this.options.installExtensions;
+ if (installExtensions && extensionsMetadata.hasExtensions) {
+ await this.enableExtensions(extensionsMetadata, connection);
+ }
+
+ const results = (await this.executeQuery(connection, 'SELECT version();')) as {
+ rows: {
+ version: string;
+ }[];
+ };
+ const versionString = results.rows[0].version.replace(/^PostgreSQL ([\d.]+) .*$/, '$1');
+ this.version = versionString;
+ this.isGeneratedColumnsSupported = VersionUtils.isGreaterOrEqual(versionString, '12.0');
+
+ await release();
+ }
+
+ protected async enableExtensions(extensionsMetadata: any, connection: any) {
+ const { logger } = this.connection;
+
+ const {
+ hasUuidColumns,
+ hasCitextColumns,
+ hasHstoreColumns,
+ hasCubeColumns,
+ hasGeometryColumns,
+ hasLtreeColumns,
+ hasExclusionConstraints,
+ } = extensionsMetadata;
+
+ if (hasUuidColumns)
+ try {
+ 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 '${
+ 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 {
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "citext"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has citext column, but the 'citext' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ if (hasHstoreColumns)
+ try {
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "hstore"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has hstore column, but the 'hstore' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ if (hasGeometryColumns)
+ try {
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "postgis"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has a geometry column, but the 'postgis' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ if (hasCubeColumns)
+ try {
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "cube"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has a cube column, but the 'cube' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ if (hasLtreeColumns)
+ try {
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "ltree"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has a ltree column, but the 'ltree' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ if (hasExclusionConstraints)
+ try {
+ // The btree_gist extension provides operator support in PostgreSQL exclusion constraints
+ await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "btree_gist"`);
+ } catch (_) {
+ logger.log(
+ 'warn',
+ "At least one of the entities has an exclusion constraint, but the 'btree_gist' extension cannot be installed automatically. Please install it manually using superuser rights",
+ );
+ }
+ }
+
+ protected async checkMetadataForExtensions() {
+ const hasUuidColumns = this.connection.entityMetadatas.some((metadata) => {
+ return (
+ metadata.generatedColumns.filter((column) => column.generationStrategy === 'uuid').length >
+ 0
+ );
+ });
+ const hasCitextColumns = this.connection.entityMetadatas.some((metadata) => {
+ return metadata.columns.filter((column) => column.type === 'citext').length > 0;
+ });
+ const hasHstoreColumns = this.connection.entityMetadatas.some((metadata) => {
+ return metadata.columns.filter((column) => column.type === 'hstore').length > 0;
+ });
+ const hasCubeColumns = this.connection.entityMetadatas.some((metadata) => {
+ return metadata.columns.filter((column) => column.type === 'cube').length > 0;
+ });
+ const hasGeometryColumns = this.connection.entityMetadatas.some((metadata) => {
+ return (
+ metadata.columns.filter((column) => this.spatialTypes.indexOf(column.type) >= 0).length > 0
+ );
+ });
+ const hasLtreeColumns = this.connection.entityMetadatas.some((metadata) => {
+ return metadata.columns.filter((column) => column.type === 'ltree').length > 0;
+ });
+ const hasExclusionConstraints = this.connection.entityMetadatas.some((metadata) => {
+ return metadata.exclusions.length > 0;
+ });
+
+ return {
+ hasUuidColumns,
+ hasCitextColumns,
+ hasHstoreColumns,
+ hasCubeColumns,
+ hasGeometryColumns,
+ hasLtreeColumns,
+ hasExclusionConstraints,
+ hasExtensions:
+ hasUuidColumns ||
+ hasCitextColumns ||
+ hasHstoreColumns ||
+ hasGeometryColumns ||
+ hasCubeColumns ||
+ hasLtreeColumns ||
+ hasExclusionConstraints,
+ };
+ }
+
+ /**
+ * Closes connection with database.
+ */
+ async disconnect(): Promise {
+ if (!this.master) return Promise.reject(new ConnectionIsNotSetError('postgres'));
+
+ await this.closePool(this.master);
+ await Promise.all(this.slaves.map((slave) => this.closePool(slave)));
+ this.master = undefined;
+ this.slaves = [];
+ }
+
+ /**
+ * Creates a schema builder used to build and sync a schema.
+ */
+ createSchemaBuilder() {
+ return new RdbmsSchemaBuilder(this.connection);
+ }
+
+ /**
+ * Creates a query runner used to execute database queries.
+ */
+ createQueryRunner(mode: ReplicationMode): QueryRunner {
+ return new PostgresQueryRunner(this, mode);
+ }
+
+ /**
+ * Prepares given value to a value to be persisted, based on its column type and metadata.
+ */
+ preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
+ if (columnMetadata.transformer)
+ value = ApplyValueTransformers.transformTo(columnMetadata.transformer, value);
+
+ if (value === null || value === undefined) return value;
+
+ if (columnMetadata.type === Boolean) {
+ return value === true ? 1 : 0;
+ } else if (columnMetadata.type === 'date') {
+ return DateUtils.mixedDateToDateString(value);
+ } else if (columnMetadata.type === 'time') {
+ return DateUtils.mixedDateToTimeString(value);
+ } else if (
+ columnMetadata.type === 'datetime' ||
+ columnMetadata.type === Date ||
+ columnMetadata.type === 'timestamp' ||
+ columnMetadata.type === 'timestamp with time zone' ||
+ columnMetadata.type === 'timestamp without time zone'
+ ) {
+ return DateUtils.mixedDateToDate(value);
+ } else if (['json', 'jsonb', ...this.spatialTypes].indexOf(columnMetadata.type) >= 0) {
+ return JSON.stringify(value);
+ } else if (columnMetadata.type === 'hstore') {
+ if (typeof value === 'string') {
+ return value;
+ } else {
+ // https://www.postgresql.org/docs/9.0/hstore.html
+ const quoteString = (value: unknown) => {
+ // If a string to be quoted is `null` or `undefined`, we return a literal unquoted NULL.
+ // This way, NULL values can be stored in the hstore object.
+ if (value === null || typeof value === 'undefined') {
+ return 'NULL';
+ }
+ // Convert non-null values to string since HStore only stores strings anyway.
+ // To include a double quote or a backslash in a key or value, escape it with a backslash.
+ return `"${`${value}`.replace(/(?=["\\])/g, '\\')}"`;
+ };
+ return Object.keys(value)
+ .map((key) => quoteString(key) + '=>' + quoteString(value[key]))
+ .join(',');
+ }
+ } else if (columnMetadata.type === 'simple-array') {
+ return DateUtils.simpleArrayToString(value);
+ } else if (columnMetadata.type === 'simple-json') {
+ return DateUtils.simpleJsonToString(value);
+ } else if (columnMetadata.type === 'cube') {
+ if (columnMetadata.isArray) {
+ return `{${value.map((cube: number[]) => `"(${cube.join(',')})"`).join(',')}}`;
+ }
+ return `(${value.join(',')})`;
+ } else if (columnMetadata.type === 'ltree') {
+ return value.split('.').filter(Boolean).join('.').replace(/[\s]+/g, '_');
+ } else if (
+ (columnMetadata.type === 'enum' || columnMetadata.type === 'simple-enum') &&
+ !columnMetadata.isArray
+ ) {
+ return '' + value;
+ }
+
+ return value;
+ }
+
+ /**
+ * Prepares given value to a value to be persisted, based on its column type or metadata.
+ */
+ prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
+ if (value === null || value === undefined)
+ return columnMetadata.transformer
+ ? ApplyValueTransformers.transformFrom(columnMetadata.transformer, value)
+ : value;
+
+ if (columnMetadata.type === Boolean) {
+ value = value ? true : false;
+ } else if (
+ columnMetadata.type === 'datetime' ||
+ columnMetadata.type === Date ||
+ columnMetadata.type === 'timestamp' ||
+ columnMetadata.type === 'timestamptz' ||
+ columnMetadata.type === 'timestamp with time zone' ||
+ columnMetadata.type === 'timestamp without time zone'
+ ) {
+ value = DateUtils.normalizeHydratedDate(value);
+ } else if (columnMetadata.type === 'date') {
+ value = DateUtils.mixedDateToDateString(value);
+ } else if (columnMetadata.type === 'time') {
+ value = DateUtils.mixedTimeToString(value);
+ } else if (columnMetadata.type === 'hstore') {
+ if (columnMetadata.hstoreType === 'object') {
+ const unescapeString = (str: string) => str.replace(/\\./g, (m) => m[1]);
+ const regexp = /"([^"\\]*(?:\\.[^"\\]*)*)"=>(?:(NULL)|"([^"\\]*(?:\\.[^"\\]*)*)")(?:,|$)/g;
+ const object: ObjectLiteral = {};
+ `${value}`.replace(regexp, (_, key, nullValue, stringValue) => {
+ object[unescapeString(key)] = nullValue ? null : unescapeString(stringValue);
+ return '';
+ });
+ value = object;
+ }
+ } else if (columnMetadata.type === 'simple-array') {
+ value = DateUtils.stringToSimpleArray(value);
+ } else if (columnMetadata.type === 'simple-json') {
+ value = DateUtils.stringToSimpleJson(value);
+ } else if (columnMetadata.type === 'cube') {
+ value = value.replace(/[()\s]+/g, ''); // remove whitespace
+ if (columnMetadata.isArray) {
+ /**
+ * Strips these groups from `{"1,2,3","",NULL}`:
+ * 1. ["1,2,3", undefined] <- cube of arity 3
+ * 2. ["", undefined] <- cube of arity 0
+ * 3. [undefined, "NULL"] <- NULL
+ */
+ const regexp = /(?:"((?:[\d\s.,])*)")|(?:(NULL))/g;
+ const unparsedArrayString = value;
+
+ value = [];
+ let cube: RegExpExecArray | null = null;
+ // Iterate through all regexp matches for cubes/null in array
+ while ((cube = regexp.exec(unparsedArrayString)) !== null) {
+ if (cube[1] !== undefined) {
+ value.push(cube[1].split(',').filter(Boolean).map(Number));
+ } else {
+ value.push(undefined);
+ }
+ }
+ } else {
+ value = value.split(',').filter(Boolean).map(Number);
+ }
+ } else if (columnMetadata.type === 'enum' || columnMetadata.type === 'simple-enum') {
+ if (columnMetadata.isArray) {
+ if (value === '{}') return [];
+
+ // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
+ value = (value as string)
+ .substr(1, (value as string).length - 2)
+ .split(',')
+ .map((val) => {
+ // replace double quotes from the beginning and from the end
+ if (val.startsWith(`"`) && val.endsWith(`"`)) val = val.slice(1, -1);
+ // replace double escaped backslash to single escaped e.g. \\\\ -> \\
+ val = val.replace(/(\\\\)/g, '\\');
+ // replace escaped double quotes to non-escaped e.g. \"asd\" -> "asd"
+ return val.replace(/(\\")/g, '"');
+ });
+
+ // convert to number if that exists in possible enum options
+ value = value.map((val: string) => {
+ return !isNaN(+val) && columnMetadata.enum!.indexOf(parseInt(val)) >= 0
+ ? parseInt(val)
+ : val;
+ });
+ } else {
+ // convert to number if that exists in possible enum options
+ value =
+ !isNaN(+value) && columnMetadata.enum!.indexOf(parseInt(value)) >= 0
+ ? parseInt(value)
+ : value;
+ }
+ } else if (columnMetadata.type === Number) {
+ // convert to number if number
+ value = !isNaN(+value) ? parseInt(value) : value;
+ }
+
+ if (columnMetadata.transformer)
+ value = ApplyValueTransformers.transformFrom(columnMetadata.transformer, value);
+ return value;
+ }
+
+ /**
+ * Replaces parameters in the given sql with special escaping character
+ * and an array of parameter names to be passed to a query.
+ */
+ escapeQueryWithParameters(
+ sql: string,
+ parameters: ObjectLiteral,
+ nativeParameters: ObjectLiteral,
+ ): [string, any[]] {
+ const escapedParameters: any[] = Object.keys(nativeParameters).map(
+ (key) => nativeParameters[key],
+ );
+ if (!parameters || !Object.keys(parameters).length) return [sql, escapedParameters];
+
+ const parameterIndexMap = new Map();
+ sql = sql.replace(
+ /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
+ (full, isArray: string, key: string): string => {
+ if (!parameters.hasOwnProperty(key)) {
+ return full;
+ }
+
+ if (parameterIndexMap.has(key)) {
+ return this.parametersPrefix + parameterIndexMap.get(key);
+ }
+
+ let value: any = parameters[key];
+
+ if (isArray) {
+ return value
+ .map((v: any) => {
+ escapedParameters.push(v);
+ return this.createParameter(key, escapedParameters.length - 1);
+ })
+ .join(', ');
+ }
+
+ if (typeof value === 'function') {
+ return value();
+ }
+
+ escapedParameters.push(value);
+ parameterIndexMap.set(key, escapedParameters.length);
+ return this.createParameter(key, escapedParameters.length - 1);
+ },
+ ); // todo: make replace only in value statements, otherwise problems
+ return [sql, escapedParameters];
+ }
+
+ /**
+ * Escapes a column name.
+ */
+ escape(columnName: string): string {
+ return '"' + columnName + '"';
+ }
+
+ /**
+ * Build full table name with schema name and table name.
+ * E.g. myDB.mySchema.myTable
+ */
+ buildTableName(tableName: string, schema?: string): string {
+ let tablePath = [tableName];
+
+ if (schema) {
+ tablePath.unshift(schema);
+ }
+
+ return tablePath.join('.');
+ }
+
+ /**
+ * Parse a target table name or other types and return a normalized table definition.
+ */
+ parseTableName(target: EntityMetadata | Table | View | TableForeignKey | string): {
+ database?: string;
+ schema?: string;
+ tableName: string;
+ } {
+ const driverDatabase = this.database;
+ const driverSchema = this.schema;
+
+ if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
+ const parsed = this.parseTableName(target.name);
+
+ return {
+ database: target.database || parsed.database || driverDatabase,
+ schema: target.schema || parsed.schema || driverSchema,
+ tableName: parsed.tableName,
+ };
+ }
+
+ if (InstanceChecker.isTableForeignKey(target)) {
+ const parsed = this.parseTableName(target.referencedTableName);
+
+ return {
+ database: target.referencedDatabase || parsed.database || driverDatabase,
+ schema: target.referencedSchema || parsed.schema || driverSchema,
+ tableName: parsed.tableName,
+ };
+ }
+
+ if (InstanceChecker.isEntityMetadata(target)) {
+ // EntityMetadata tableName is never a path
+
+ return {
+ database: target.database || driverDatabase,
+ schema: target.schema || driverSchema,
+ tableName: target.tableName,
+ };
+ }
+
+ const parts = target.split('.');
+
+ return {
+ database: driverDatabase,
+ schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
+ tableName: parts.length > 1 ? parts[1] : parts[0],
+ };
+ }
+
+ /**
+ * Creates a database type from a given column metadata.
+ */
+ normalizeType(column: {
+ type?: ColumnType;
+ length?: number | string;
+ precision?: number | null;
+ scale?: number;
+ isArray?: boolean;
+ }): string {
+ if (column.type === Number || column.type === 'int' || column.type === 'int4') {
+ return 'integer';
+ } else if (column.type === String || column.type === 'varchar') {
+ return 'character varying';
+ } else if (column.type === Date || column.type === 'timestamp') {
+ return 'timestamp without time zone';
+ } else if (column.type === 'timestamptz') {
+ return 'timestamp with time zone';
+ } else if (column.type === 'time') {
+ return 'time without time zone';
+ } else if (column.type === 'timetz') {
+ return 'time with time zone';
+ } else if (column.type === Boolean || column.type === 'bool') {
+ return 'boolean';
+ } else if (column.type === 'simple-array') {
+ return 'text';
+ } else if (column.type === 'simple-json') {
+ return 'text';
+ } else if (column.type === 'simple-enum') {
+ return 'enum';
+ } else if (column.type === 'int2') {
+ return 'smallint';
+ } else if (column.type === 'int8') {
+ return 'bigint';
+ } else if (column.type === 'decimal') {
+ return 'numeric';
+ } else if (column.type === 'float8' || column.type === 'float') {
+ return 'double precision';
+ } else if (column.type === 'float4') {
+ return 'real';
+ } else if (column.type === 'char') {
+ return 'character';
+ } else if (column.type === 'varbit') {
+ return 'bit varying';
+ } else {
+ return (column.type as string) || '';
+ }
+ }
+
+ /**
+ * Normalizes "default" value of the column.
+ */
+ normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
+ const defaultValue = columnMetadata.default;
+
+ if (defaultValue === null || defaultValue === undefined) {
+ return undefined;
+ }
+
+ if (columnMetadata.isArray && Array.isArray(defaultValue)) {
+ return `'{${defaultValue.map((val: string) => `${val}`).join(',')}}'`;
+ }
+
+ if (
+ (columnMetadata.type === 'enum' ||
+ columnMetadata.type === 'simple-enum' ||
+ typeof defaultValue === 'number' ||
+ typeof defaultValue === 'string') &&
+ defaultValue !== undefined
+ ) {
+ return `'${defaultValue}'`;
+ }
+
+ if (typeof defaultValue === 'boolean') {
+ return defaultValue ? 'true' : 'false';
+ }
+
+ if (typeof defaultValue === 'function') {
+ const value = defaultValue();
+
+ return this.normalizeDatetimeFunction(value);
+ }
+
+ if (typeof defaultValue === 'object') {
+ return `'${JSON.stringify(defaultValue)}'`;
+ }
+
+ return `${defaultValue}`;
+ }
+
+ /**
+ * Compares "default" value of the column.
+ * Postgres sorts json values before it is saved, so in that case a deep comparison has to be performed to see if has changed.
+ */
+ private defaultEqual(columnMetadata: ColumnMetadata, tableColumn: TableColumn): boolean {
+ if (
+ ['json', 'jsonb'].includes(columnMetadata.type as string) &&
+ !['function', 'undefined'].includes(typeof columnMetadata.default)
+ ) {
+ const tableColumnDefault =
+ typeof tableColumn.default === 'string'
+ ? JSON.parse(tableColumn.default.substring(1, tableColumn.default.length - 1))
+ : tableColumn.default;
+
+ return OrmUtils.deepCompare(columnMetadata.default, tableColumnDefault);
+ }
+
+ const columnDefault = this.lowerDefaultValueIfNecessary(this.normalizeDefault(columnMetadata));
+ return columnDefault === tableColumn.default;
+ }
+
+ /**
+ * Normalizes "isUnique" value of the column.
+ */
+ normalizeIsUnique(column: ColumnMetadata): boolean {
+ return column.entityMetadata.uniques.some(
+ (uq) => uq.columns.length === 1 && uq.columns[0] === column,
+ );
+ }
+
+ /**
+ * Returns default column lengths, which is required on column creation.
+ */
+ getColumnLength(column: ColumnMetadata): string {
+ return column.length ? column.length.toString() : '';
+ }
+
+ /**
+ * Creates column type definition including length, precision and scale
+ */
+ createFullType(column: TableColumn): string {
+ let type = column.type;
+
+ if (column.length) {
+ type += '(' + column.length + ')';
+ } else if (
+ column.precision !== null &&
+ column.precision !== undefined &&
+ column.scale !== null &&
+ column.scale !== undefined
+ ) {
+ type += '(' + column.precision + ',' + column.scale + ')';
+ } else if (column.precision !== null && column.precision !== undefined) {
+ type += '(' + column.precision + ')';
+ }
+
+ if (column.type === 'time without time zone') {
+ type =
+ 'TIME' +
+ (column.precision !== null && column.precision !== undefined
+ ? '(' + column.precision + ')'
+ : '');
+ } else if (column.type === 'time with time zone') {
+ type =
+ 'TIME' +
+ (column.precision !== null && column.precision !== undefined
+ ? '(' + column.precision + ')'
+ : '') +
+ ' WITH TIME ZONE';
+ } else if (column.type === 'timestamp without time zone') {
+ type =
+ 'TIMESTAMP' +
+ (column.precision !== null && column.precision !== undefined
+ ? '(' + column.precision + ')'
+ : '');
+ } else if (column.type === 'timestamp with time zone') {
+ type =
+ 'TIMESTAMP' +
+ (column.precision !== null && column.precision !== undefined
+ ? '(' + column.precision + ')'
+ : '') +
+ ' WITH TIME ZONE';
+ } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
+ if (column.spatialFeatureType != null && column.srid != null) {
+ type = `${column.type}(${column.spatialFeatureType},${column.srid})`;
+ } else if (column.spatialFeatureType != null) {
+ type = `${column.type}(${column.spatialFeatureType})`;
+ } else {
+ type = column.type;
+ }
+ }
+
+ if (column.isArray) type += ' array';
+
+ return type;
+ }
+
+ /**
+ * Obtains a new database connection to a master server.
+ * Used for replication.
+ * If replication is not setup then returns default connection's database connection.
+ */
+ async obtainMasterConnection(): Promise<[any, Function]> {
+ if (!this.master) {
+ throw new TypeORMError('Driver not Connected');
+ }
+
+ const connection = await this.master.connect();
+ // Apply per-connection session settings (schema creation moved to afterConnect)
+ const { schema, statementTimeout } = this.options;
+ if (schema && schema !== 'public') {
+ await connection.query(`SET search_path TO "${schema}",public`);
+ } else {
+ await connection.query('SET search_path TO public');
+ }
+ if (statementTimeout) {
+ await connection.query(`SET statement_timeout = ${statementTimeout}`);
+ }
+
+ return [connection, () => connection.release()];
+ }
+
+ /**
+ * Obtains a new database connection to a slave server.
+ * Used for replication.
+ * If replication is not setup then returns master (default) connection's database connection.
+ */
+ async obtainSlaveConnection(): Promise<[any, Function]> {
+ if (!this.slaves.length) {
+ return this.obtainMasterConnection();
+ }
+
+ const random = Math.floor(Math.random() * this.slaves.length);
+
+ return new Promise((ok, fail) => {
+ this.slaves[random].connect((err: any, connection: any, release: any) => {
+ err ? fail(err) : ok([connection, release]);
+ });
+ });
+ }
+
+ /**
+ * Creates generated map of values generated or returned by database after INSERT query.
+ *
+ * todo: slow. optimize Object.keys(), OrmUtils.mergeDeep and column.createValueMap parts
+ */
+ createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
+ if (!insertResult) return undefined;
+
+ return Object.keys(insertResult).reduce((map, key) => {
+ const column = metadata.findColumnWithDatabaseName(key);
+ if (column) {
+ OrmUtils.mergeDeep(map, column.createValueMap(insertResult[key]));
+ // OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
+ }
+ return map;
+ }, {} as ObjectLiteral);
+ }
+
+ /**
+ * Differentiate columns of this table and columns from the given column metadatas columns
+ * and returns only changed.
+ */
+ findChangedColumns(
+ tableColumns: TableColumn[],
+ columnMetadatas: ColumnMetadata[],
+ ): ColumnMetadata[] {
+ return columnMetadatas.filter((columnMetadata) => {
+ const tableColumn = tableColumns.find((c) => c.name === columnMetadata.databaseName);
+ if (!tableColumn) return false; // we don't need new columns, we only need exist and changed
+
+ const isColumnChanged =
+ tableColumn.name !== columnMetadata.databaseName ||
+ tableColumn.type !== this.normalizeType(columnMetadata) ||
+ tableColumn.length !== columnMetadata.length ||
+ tableColumn.isArray !== columnMetadata.isArray ||
+ tableColumn.precision !== columnMetadata.precision ||
+ (columnMetadata.scale !== undefined && tableColumn.scale !== columnMetadata.scale) ||
+ tableColumn.comment !== this.escapeComment(columnMetadata.comment) ||
+ (!tableColumn.isGenerated && !this.defaultEqual(columnMetadata, tableColumn)) || // we included check for generated here, because generated columns already can have default values
+ tableColumn.isPrimary !== columnMetadata.isPrimary ||
+ tableColumn.isNullable !== columnMetadata.isNullable ||
+ tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata) ||
+ tableColumn.enumName !== columnMetadata.enumName ||
+ (tableColumn.enum &&
+ columnMetadata.enum &&
+ !OrmUtils.isArraysEqual(
+ tableColumn.enum,
+ columnMetadata.enum.map((val) => val + ''),
+ )) || // enums in postgres are always strings
+ tableColumn.isGenerated !== columnMetadata.isGenerated ||
+ (tableColumn.spatialFeatureType || '').toLowerCase() !==
+ (columnMetadata.spatialFeatureType || '').toLowerCase() ||
+ tableColumn.srid !== columnMetadata.srid ||
+ tableColumn.generatedType !== columnMetadata.generatedType ||
+ (tableColumn.asExpression || '').trim() !== (columnMetadata.asExpression || '').trim();
+
+ // DEBUG SECTION
+ // if (isColumnChanged) {
+ // console.log("table:", columnMetadata.entityMetadata.tableName)
+ // console.log(
+ // "name:",
+ // tableColumn.name,
+ // columnMetadata.databaseName,
+ // )
+ // console.log(
+ // "type:",
+ // tableColumn.type,
+ // this.normalizeType(columnMetadata),
+ // )
+ // console.log(
+ // "length:",
+ // tableColumn.length,
+ // columnMetadata.length,
+ // )
+ // console.log(
+ // "isArray:",
+ // tableColumn.isArray,
+ // columnMetadata.isArray,
+ // )
+ // console.log(
+ // "precision:",
+ // tableColumn.precision,
+ // columnMetadata.precision,
+ // )
+ // console.log("scale:", tableColumn.scale, columnMetadata.scale)
+ // console.log(
+ // "comment:",
+ // tableColumn.comment,
+ // this.escapeComment(columnMetadata.comment),
+ // )
+ // console.log(
+ // "enumName:",
+ // tableColumn.enumName,
+ // columnMetadata.enumName,
+ // )
+ // console.log(
+ // "enum:",
+ // tableColumn.enum &&
+ // columnMetadata.enum &&
+ // !OrmUtils.isArraysEqual(
+ // tableColumn.enum,
+ // columnMetadata.enum.map((val) => val + ""),
+ // ),
+ // )
+ // console.log(
+ // "isPrimary:",
+ // tableColumn.isPrimary,
+ // columnMetadata.isPrimary,
+ // )
+ // console.log(
+ // "isNullable:",
+ // tableColumn.isNullable,
+ // columnMetadata.isNullable,
+ // )
+ // console.log(
+ // "isUnique:",
+ // tableColumn.isUnique,
+ // this.normalizeIsUnique(columnMetadata),
+ // )
+ // console.log(
+ // "isGenerated:",
+ // tableColumn.isGenerated,
+ // columnMetadata.isGenerated,
+ // )
+ // console.log(
+ // "generatedType:",
+ // tableColumn.generatedType,
+ // columnMetadata.generatedType,
+ // )
+ // console.log(
+ // "asExpression:",
+ // (tableColumn.asExpression || "").trim(),
+ // (columnMetadata.asExpression || "").trim(),
+ // )
+ // console.log(
+ // "collation:",
+ // tableColumn.collation,
+ // columnMetadata.collation,
+ // )
+ // console.log(
+ // "isGenerated 2:",
+ // !tableColumn.isGenerated &&
+ // this.lowerDefaultValueIfNecessary(
+ // this.normalizeDefault(columnMetadata),
+ // ) !== tableColumn.default,
+ // )
+ // console.log(
+ // "spatialFeatureType:",
+ // (tableColumn.spatialFeatureType || "").toLowerCase(),
+ // (columnMetadata.spatialFeatureType || "").toLowerCase(),
+ // )
+ // console.log("srid", tableColumn.srid, columnMetadata.srid)
+ // console.log("==========================================")
+ // }
+
+ return isColumnChanged;
+ });
+ }
+
+ private lowerDefaultValueIfNecessary(value: string | undefined) {
+ // Postgres saves function calls in default value as lowercase #2733
+ if (!value) {
+ return value;
+ }
+ return value
+ .split(`'`)
+ .map((v, i) => {
+ return i % 2 === 1 ? v : v.toLowerCase();
+ })
+ .join(`'`);
+ }
+
+ /**
+ * Returns true if driver supports RETURNING / OUTPUT statement.
+ */
+ isReturningSqlSupported(): boolean {
+ return true;
+ }
+
+ /**
+ * Returns true if driver supports uuid values generation on its own.
+ */
+ isUUIDGenerationSupported(): boolean {
+ return true;
+ }
+
+ /**
+ * Returns true if driver supports fulltext indices.
+ */
+ isFullTextColumnTypeSupported(): boolean {
+ return false;
+ }
+
+ get uuidGenerator(): string {
+ return this.options.uuidExtension === 'pgcrypto' ? 'gen_random_uuid()' : 'uuid_generate_v4()';
+ }
+
+ /**
+ * Creates an escaped parameter.
+ */
+ createParameter(parameterName: string, index: number): string {
+ return this.parametersPrefix + (index + 1);
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Loads postgres query stream package.
+ */
+ loadStreamDependency() {
+ try {
+ return require('pg-query-stream');
+ } catch (e) {
+ // todo: better error for browser env
+ throw new TypeORMError(
+ `To use streams you should install pg-query-stream package. Please run npm i pg-query-stream --save command.`,
+ );
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * If driver dependency is not given explicitly, then try to load it via "require".
+ */
+ protected loadDependencies(): void {
+ try {
+ const postgres = this.options.driver || require('pg');
+ this.postgres = postgres;
+ } catch (e) {
+ // todo: better error for browser env
+ throw new DriverPackageNotInstalledError('Postgres', 'pg');
+ }
+ }
+
+ /**
+ * Creates a new connection pool for a given database credentials.
+ */
+ protected async createPool(
+ options: PostgresConnectionOptions,
+ credentials: PostgresConnectionCredentialsOptions,
+ ): Promise {
+ const { logger } = this.connection;
+ credentials = Object.assign({}, credentials);
+
+ // build connection options for the driver
+ // See: https://github.com/brianc/node-postgres/tree/master/packages/pg-pool#create
+ const connectionOptions: pg.PoolConfig = {
+ connectionString: credentials.url,
+ host: credentials.host,
+ user: credentials.username,
+ password: credentials.password,
+ database: credentials.database,
+ port: credentials.port,
+ ssl: credentials.ssl,
+ connectionTimeoutMillis: options.connectTimeoutMS,
+ application_name: options.applicationName ?? credentials.applicationName,
+ max: options.poolSize,
+ query_timeout: options.queryTimeout,
+ ...options.extra,
+ };
+
+ if (options.parseInt8 !== undefined) {
+ if (
+ this.postgres.defaults &&
+ Object.getOwnPropertyDescriptor(this.postgres.defaults, 'parseInt8')?.set
+ ) {
+ this.postgres.defaults.parseInt8 = options.parseInt8;
+ } else {
+ logger.log(
+ 'warn',
+ 'Attempted to set parseInt8 option, but the postgres driver does not support setting defaults.parseInt8. This option will be ignored.',
+ );
+ }
+ }
+
+ // create a connection pool
+ const pool = new this.postgres.Pool(connectionOptions);
+
+ const poolErrorHandler =
+ options.poolErrorHandler ||
+ ((error: any) => logger.log('warn', `Postgres pool raised an error. ${error}`));
+
+ /*
+ Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
+ cause the hosting app to crash.
+ */
+ pool.on('error', poolErrorHandler);
+
+ return new Promise((ok, fail) => {
+ pool.connect((err: any, connection: any, release: Function) => {
+ if (err) return fail(err);
+
+ if (options.logNotifications) {
+ connection.on('notice', (msg: any) => {
+ msg && this.connection.logger.log('info', msg.message);
+ });
+ connection.on('notification', (msg: any) => {
+ msg &&
+ this.connection.logger.log(
+ 'info',
+ `Received NOTIFY on channel ${msg.channel}: ${msg.payload}.`,
+ );
+ });
+ }
+ release();
+ ok(pool);
+ });
+ });
+ }
+
+ /**
+ * Closes connection pool.
+ */
+ protected async closePool(pool: any): Promise {
+ while (this.connectedQueryRunners.length) {
+ await this.connectedQueryRunners[0].release();
+ }
+
+ return new Promise((ok, fail) => {
+ pool.end((err: any) => (err ? fail(err) : ok()));
+ });
+ }
+
+ /**
+ * Executes given query.
+ */
+ protected executeQuery(connection: any, query: string) {
+ this.connection.logger.logQuery(query);
+
+ return new Promise((ok, fail) => {
+ connection.query(query, (err: any, result: any) => (err ? fail(err) : ok(result)));
+ });
+ }
+
+ /**
+ * If parameter is a datetime function, e.g. "CURRENT_TIMESTAMP", normalizes it.
+ * Otherwise returns original input.
+ */
+ protected normalizeDatetimeFunction(value: string) {
+ // check if input is datetime function
+ const upperCaseValue = value.toUpperCase();
+ const isDatetimeFunction =
+ upperCaseValue.indexOf('CURRENT_TIMESTAMP') !== -1 ||
+ upperCaseValue.indexOf('CURRENT_DATE') !== -1 ||
+ upperCaseValue.indexOf('CURRENT_TIME') !== -1 ||
+ upperCaseValue.indexOf('LOCALTIMESTAMP') !== -1 ||
+ upperCaseValue.indexOf('LOCALTIME') !== -1;
+
+ if (isDatetimeFunction) {
+ // extract precision, e.g. "(3)"
+ const precision = value.match(/\(\d+\)/);
+
+ if (upperCaseValue.indexOf('CURRENT_TIMESTAMP') !== -1) {
+ return precision ? `('now'::text)::timestamp${precision[0]} with time zone` : 'now()';
+ } else if (upperCaseValue === 'CURRENT_DATE') {
+ return "('now'::text)::date";
+ } else if (upperCaseValue.indexOf('CURRENT_TIME') !== -1) {
+ return precision
+ ? `('now'::text)::time${precision[0]} with time zone`
+ : "('now'::text)::time with time zone";
+ } else if (upperCaseValue.indexOf('LOCALTIMESTAMP') !== -1) {
+ return precision
+ ? `('now'::text)::timestamp${precision[0]} without time zone`
+ : "('now'::text)::timestamp without time zone";
+ } else if (upperCaseValue.indexOf('LOCALTIME') !== -1) {
+ return precision
+ ? `('now'::text)::time${precision[0]} without time zone`
+ : "('now'::text)::time without time zone";
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Escapes a given comment.
+ */
+ protected escapeComment(comment?: string) {
+ if (!comment) return comment;
+
+ comment = comment.replace(/\u0000/g, ''); // Null bytes aren't allowed in comments
+
+ return comment;
+ }
+}
diff --git a/packages/@n8n/typeorm/src/driver/postgres/PostgresQueryRunner.ts b/packages/@n8n/typeorm/src/driver/postgres/PostgresQueryRunner.ts
new file mode 100644
index 00000000000..e20d241d5c0
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/postgres/PostgresQueryRunner.ts
@@ -0,0 +1,3914 @@
+import { ObjectLiteral } from '../../common/ObjectLiteral';
+import { TypeORMError } from '../../error';
+import { QueryFailedError } from '../../error/QueryFailedError';
+import { QueryRunnerAlreadyReleasedError } from '../../error/QueryRunnerAlreadyReleasedError';
+import { TransactionNotStartedError } from '../../error/TransactionNotStartedError';
+import { ReadStream } from '../../platform/PlatformTools';
+import { BaseQueryRunner } from '../../query-runner/BaseQueryRunner';
+import { QueryResult } from '../../query-runner/QueryResult';
+import { QueryRunner } from '../../query-runner/QueryRunner';
+import { TableIndexOptions } from '../../schema-builder/options/TableIndexOptions';
+import { Table } from '../../schema-builder/table/Table';
+import { TableCheck } from '../../schema-builder/table/TableCheck';
+import { TableColumn } from '../../schema-builder/table/TableColumn';
+import { TableExclusion } from '../../schema-builder/table/TableExclusion';
+import { TableForeignKey } from '../../schema-builder/table/TableForeignKey';
+import { TableIndex } from '../../schema-builder/table/TableIndex';
+import { TableUnique } from '../../schema-builder/table/TableUnique';
+import { View } from '../../schema-builder/view/View';
+import { Broadcaster } from '../../subscriber/Broadcaster';
+import { InstanceChecker } from '../../util/InstanceChecker';
+import { OrmUtils } from '../../util/OrmUtils';
+import { VersionUtils } from '../../util/VersionUtils';
+import { Query } from '../Query';
+import { ColumnType } from '../types/ColumnTypes';
+import { IsolationLevel } from '../types/IsolationLevel';
+import { MetadataTableType } from '../types/MetadataTableType';
+import { ReplicationMode } from '../types/ReplicationMode';
+import { PostgresDriver } from './PostgresDriver';
+import { BroadcasterResult } from '../../subscriber/BroadcasterResult';
+
+/**
+ * Runs queries on a single postgres database connection.
+ */
+export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner {
+ // -------------------------------------------------------------------------
+ // Public Implemented Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Database driver used by connection.
+ */
+ driver: PostgresDriver;
+
+ // -------------------------------------------------------------------------
+ // Protected Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Promise used to obtain a database connection for a first time.
+ */
+ protected databaseConnectionPromise: Promise;
+
+ /**
+ * Special callback provided by a driver used to release a created connection.
+ */
+ protected releaseCallback?: (err: any) => void;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(driver: PostgresDriver, mode: ReplicationMode) {
+ super();
+ this.driver = driver;
+ this.connection = driver.connection;
+ this.mode = mode;
+ this.broadcaster = new Broadcaster(this);
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates/uses database connection from the connection pool to perform further operations.
+ * Returns obtained database connection.
+ */
+ connect(): Promise {
+ if (this.databaseConnection) return Promise.resolve(this.databaseConnection);
+
+ if (this.databaseConnectionPromise) return this.databaseConnectionPromise;
+
+ if (this.mode === 'slave' && this.driver.isReplicated) {
+ this.databaseConnectionPromise = this.driver
+ .obtainSlaveConnection()
+ .then(([connection, release]: any[]) => {
+ this.driver.connectedQueryRunners.push(this);
+ this.databaseConnection = connection;
+
+ const onErrorCallback = (err: Error) => this.releasePostgresConnection(err);
+ this.releaseCallback = (err?: Error) => {
+ this.databaseConnection.removeListener('error', onErrorCallback);
+ release(err);
+ };
+ this.databaseConnection.on('error', onErrorCallback);
+
+ return this.databaseConnection;
+ });
+ } else {
+ // master
+ this.databaseConnectionPromise = this.driver
+ .obtainMasterConnection()
+ .then(([connection, release]: any[]) => {
+ this.driver.connectedQueryRunners.push(this);
+ this.databaseConnection = connection;
+
+ const onErrorCallback = (err: Error) => this.releasePostgresConnection(err);
+ this.releaseCallback = (err?: Error) => {
+ this.databaseConnection.removeListener('error', onErrorCallback);
+ release(err);
+ };
+ this.databaseConnection.on('error', onErrorCallback);
+
+ return this.databaseConnection;
+ });
+ }
+
+ return this.databaseConnectionPromise;
+ }
+
+ /**
+ * Release a connection back to the pool, optionally specifying an Error to release with.
+ * Per pg-pool documentation this will prevent the pool from re-using the broken connection.
+ */
+ private async releasePostgresConnection(err?: Error) {
+ if (this.isReleased) {
+ return;
+ }
+
+ this.isReleased = true;
+ if (this.releaseCallback) {
+ this.releaseCallback(err);
+ this.releaseCallback = undefined;
+ }
+
+ const index = this.driver.connectedQueryRunners.indexOf(this);
+
+ if (index !== -1) {
+ this.driver.connectedQueryRunners.splice(index, 1);
+ }
+ }
+
+ /**
+ * Releases used database connection.
+ * You cannot use query runner methods once its released.
+ */
+ release(): Promise {
+ return this.releasePostgresConnection();
+ }
+
+ /**
+ * Starts transaction.
+ */
+ async startTransaction(isolationLevel?: IsolationLevel): Promise {
+ this.isTransactionActive = true;
+ try {
+ await this.broadcaster.broadcast('BeforeTransactionStart');
+ } catch (err) {
+ this.isTransactionActive = false;
+ throw err;
+ }
+
+ if (this.transactionDepth === 0) {
+ this.transactionDepth += 1;
+ await this.query('START TRANSACTION');
+ if (isolationLevel) {
+ await this.query('SET TRANSACTION ISOLATION LEVEL ' + isolationLevel);
+ }
+ } else {
+ this.transactionDepth += 1;
+ await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`);
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionStart');
+ }
+
+ /**
+ * Commits transaction.
+ * Error will be thrown if transaction was not started.
+ */
+ async commitTransaction(): Promise {
+ if (!this.isTransactionActive) throw new TransactionNotStartedError();
+
+ await this.broadcaster.broadcast('BeforeTransactionCommit');
+
+ if (this.transactionDepth > 1) {
+ this.transactionDepth -= 1;
+ await this.query(`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`);
+ } else {
+ this.transactionDepth -= 1;
+ await this.query('COMMIT');
+ this.isTransactionActive = false;
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionCommit');
+ }
+
+ /**
+ * Rollbacks transaction.
+ * Error will be thrown if transaction was not started.
+ */
+ async rollbackTransaction(): Promise {
+ if (!this.isTransactionActive) throw new TransactionNotStartedError();
+
+ await this.broadcaster.broadcast('BeforeTransactionRollback');
+
+ if (this.transactionDepth > 1) {
+ this.transactionDepth -= 1;
+ await this.query(`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`);
+ } else {
+ this.transactionDepth -= 1;
+ await this.query('ROLLBACK');
+ this.isTransactionActive = false;
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionRollback');
+ }
+
+ /**
+ * Executes a given SQL query.
+ */
+ async query(
+ query: string,
+ parameters?: any[],
+ useStructuredResult: boolean = false,
+ ): Promise {
+ if (this.isReleased) throw new QueryRunnerAlreadyReleasedError();
+
+ const databaseConnection = await this.connect();
+ const broadcasterResult = new BroadcasterResult();
+
+ this.driver.connection.logger.logQuery(query, parameters, this);
+ this.broadcaster.broadcastBeforeQueryEvent(broadcasterResult, query, parameters);
+
+ try {
+ const queryStartTime = +new Date();
+ const raw = await databaseConnection.query(query, parameters);
+ // log slow queries if maxQueryExecution time is set
+ const maxQueryExecutionTime = this.driver.options.maxQueryExecutionTime;
+ const queryEndTime = +new Date();
+ const queryExecutionTime = queryEndTime - queryStartTime;
+
+ this.broadcaster.broadcastAfterQueryEvent(
+ broadcasterResult,
+ query,
+ parameters,
+ true,
+ queryExecutionTime,
+ raw,
+ undefined,
+ );
+
+ if (maxQueryExecutionTime && queryExecutionTime > maxQueryExecutionTime)
+ this.driver.connection.logger.logQuerySlow(queryExecutionTime, query, parameters, this);
+
+ const result = new QueryResult();
+ if (raw) {
+ if (raw.hasOwnProperty('rows')) {
+ result.records = raw.rows;
+ }
+
+ if (raw.hasOwnProperty('rowCount')) {
+ result.affected = raw.rowCount;
+ }
+
+ switch (raw.command) {
+ case 'DELETE':
+ case 'UPDATE':
+ // for UPDATE and DELETE query additionally return number of affected rows
+ result.raw = [raw.rows, raw.rowCount];
+ break;
+ default:
+ result.raw = raw.rows;
+ }
+
+ if (!useStructuredResult) {
+ return result.raw;
+ }
+ }
+
+ return result;
+ } catch (err) {
+ this.driver.connection.logger.logQueryError(err, query, parameters, this);
+ this.broadcaster.broadcastAfterQueryEvent(
+ broadcasterResult,
+ query,
+ parameters,
+ false,
+ undefined,
+ undefined,
+ err,
+ );
+
+ throw new QueryFailedError(query, parameters, err);
+ } finally {
+ await broadcasterResult.wait();
+ }
+ }
+
+ /**
+ * Returns raw data stream.
+ */
+ async stream(
+ query: string,
+ parameters?: any[],
+ onEnd?: Function,
+ onError?: Function,
+ ): Promise {
+ const QueryStream = this.driver.loadStreamDependency();
+ if (this.isReleased) throw new QueryRunnerAlreadyReleasedError();
+
+ const databaseConnection = await this.connect();
+ this.driver.connection.logger.logQuery(query, parameters, this);
+ const stream = databaseConnection.query(new QueryStream(query, parameters));
+ if (onEnd) stream.on('end', onEnd);
+ if (onError) stream.on('error', onError);
+
+ return stream;
+ }
+
+ /**
+ * Returns all available database names including system databases.
+ */
+ async getDatabases(): Promise {
+ return Promise.resolve([]);
+ }
+
+ /**
+ * Returns all available schema names including system schemas.
+ * If database parameter specified, returns schemas of that database.
+ */
+ async getSchemas(database?: string): Promise {
+ return Promise.resolve([]);
+ }
+
+ /**
+ * Checks if database with the given name exist.
+ */
+ async hasDatabase(database: string): Promise {
+ const result = await this.query(`SELECT * FROM pg_database WHERE datname=$1;`, [database]);
+ return result.length ? true : false;
+ }
+
+ /**
+ * Loads currently using database
+ */
+ async getCurrentDatabase(): Promise {
+ const query = await this.query(`SELECT * FROM current_database()`);
+ return query[0]['current_database'];
+ }
+
+ /**
+ * Checks if schema with the given name exist.
+ */
+ async hasSchema(schema: string): Promise {
+ const result = await this.query(
+ `SELECT * FROM "information_schema"."schemata" WHERE "schema_name" = $1`,
+ [schema],
+ );
+ return result.length ? true : false;
+ }
+
+ /**
+ * Loads currently using database schema
+ */
+ async getCurrentSchema(): Promise {
+ const query = await this.query(`SELECT * FROM current_schema()`);
+ return query[0]['current_schema'];
+ }
+
+ /**
+ * Checks if table with the given name exist in the database.
+ */
+ async hasTable(tableOrName: Table | string): Promise {
+ const parsedTableName = this.driver.parseTableName(tableOrName);
+
+ if (!parsedTableName.schema) {
+ parsedTableName.schema = await this.getCurrentSchema();
+ }
+
+ const sql = `SELECT * FROM "information_schema"."tables" WHERE "table_schema" = $1 AND "table_name" = $2`;
+ const result = await this.query(sql, [parsedTableName.schema, parsedTableName.tableName]);
+ return result.length ? true : false;
+ }
+
+ /**
+ * Checks if column with the given name exist in the given table.
+ */
+ async hasColumn(tableOrName: Table | string, columnName: string): Promise {
+ const parsedTableName = this.driver.parseTableName(tableOrName);
+
+ if (!parsedTableName.schema) {
+ parsedTableName.schema = await this.getCurrentSchema();
+ }
+
+ const sql = `SELECT * FROM "information_schema"."columns" WHERE "table_schema" = $1 AND "table_name" = $2 AND "column_name" = $3`;
+ const result = await this.query(sql, [
+ parsedTableName.schema,
+ parsedTableName.tableName,
+ columnName,
+ ]);
+ return result.length ? true : false;
+ }
+
+ /**
+ * Creates a new database.
+ * Note: Postgres does not support database creation inside a transaction block.
+ */
+ async createDatabase(database: string, ifNotExist?: boolean): Promise {
+ if (ifNotExist) {
+ const databaseAlreadyExists = await this.hasDatabase(database);
+
+ if (databaseAlreadyExists) return Promise.resolve();
+ }
+
+ const up = `CREATE DATABASE "${database}"`;
+ const down = `DROP DATABASE "${database}"`;
+ await this.executeQueries(new Query(up), new Query(down));
+ }
+
+ /**
+ * Drops database.
+ * Note: Postgres does not support database dropping inside a transaction block.
+ */
+ async dropDatabase(database: string, ifExist?: boolean): Promise {
+ const up = ifExist ? `DROP DATABASE IF EXISTS "${database}"` : `DROP DATABASE "${database}"`;
+ const down = `CREATE DATABASE "${database}"`;
+ await this.executeQueries(new Query(up), new Query(down));
+ }
+
+ /**
+ * Creates a new table schema.
+ */
+ async createSchema(schemaPath: string, ifNotExist?: boolean): Promise {
+ const schema = schemaPath.indexOf('.') === -1 ? schemaPath : schemaPath.split('.')[1];
+
+ const up = ifNotExist ? `CREATE SCHEMA IF NOT EXISTS "${schema}"` : `CREATE SCHEMA "${schema}"`;
+ const down = `DROP SCHEMA "${schema}" CASCADE`;
+ await this.executeQueries(new Query(up), new Query(down));
+ }
+
+ /**
+ * Drops table schema.
+ */
+ async dropSchema(schemaPath: string, ifExist?: boolean, isCascade?: boolean): Promise {
+ const schema = schemaPath.indexOf('.') === -1 ? schemaPath : schemaPath.split('.')[1];
+
+ const up = ifExist
+ ? `DROP SCHEMA IF EXISTS "${schema}" ${isCascade ? 'CASCADE' : ''}`
+ : `DROP SCHEMA "${schema}" ${isCascade ? 'CASCADE' : ''}`;
+ const down = `CREATE SCHEMA "${schema}"`;
+ await this.executeQueries(new Query(up), new Query(down));
+ }
+
+ /**
+ * Creates a new table.
+ */
+ async createTable(
+ table: Table,
+ ifNotExist: boolean = false,
+ createForeignKeys: boolean = true,
+ createIndices: boolean = true,
+ ): Promise {
+ if (ifNotExist) {
+ const isTableExist = await this.hasTable(table);
+ if (isTableExist) return Promise.resolve();
+ }
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ // if table have column with ENUM type, we must create this type in postgres.
+ const enumColumns = table.columns.filter(
+ (column) => column.type === 'enum' || column.type === 'simple-enum',
+ );
+ const createdEnumTypes: string[] = [];
+ for (const column of enumColumns) {
+ // TODO: Should also check if values of existing type matches expected ones
+ const hasEnum = await this.hasEnumType(table, column);
+ const enumName = this.buildEnumName(table, column);
+
+ // if enum with the same "enumName" is defined more then once, me must prevent double creation
+ if (!hasEnum && createdEnumTypes.indexOf(enumName) === -1) {
+ createdEnumTypes.push(enumName);
+ upQueries.push(this.createEnumTypeSql(table, column, enumName));
+ downQueries.push(this.dropEnumTypeSql(table, column, enumName));
+ }
+ }
+
+ // if table have column with generated type, we must add the expression to the metadata table
+ const generatedColumns = table.columns.filter(
+ (column) => column.generatedType === 'STORED' && column.asExpression,
+ );
+ for (const column of generatedColumns) {
+ const tableNameWithSchema = (await this.getTableNameWithSchema(table.name)).split('.');
+ const tableName = tableNameWithSchema[1];
+ const schema = tableNameWithSchema[0];
+
+ const insertQuery = this.insertTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+
+ upQueries.push(insertQuery);
+ downQueries.push(deleteQuery);
+ }
+
+ upQueries.push(this.createTableSql(table, createForeignKeys));
+ downQueries.push(this.dropTableSql(table));
+
+ // if createForeignKeys is true, we must drop created foreign keys in down query.
+ // createTable does not need separate method to create foreign keys, because it create fk's in the same query with table creation.
+ if (createForeignKeys)
+ table.foreignKeys.forEach((foreignKey) =>
+ downQueries.push(this.dropForeignKeySql(table, foreignKey)),
+ );
+
+ if (createIndices) {
+ table.indices.forEach((index) => {
+ // new index may be passed without name. In this case we generate index name manually.
+ if (!index.name)
+ index.name = this.connection.namingStrategy.indexName(
+ table,
+ index.columnNames,
+ index.where,
+ );
+ upQueries.push(this.createIndexSql(table, index));
+ downQueries.push(this.dropIndexSql(table, index));
+ });
+ }
+
+ if (table.comment) {
+ upQueries.push(
+ new Query('COMMENT ON TABLE ' + this.escapePath(table) + " IS '" + table.comment + "'"),
+ );
+ downQueries.push(new Query('COMMENT ON TABLE ' + this.escapePath(table) + ' IS NULL'));
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Drops the table.
+ */
+ async dropTable(
+ target: Table | string,
+ ifExist?: boolean,
+ dropForeignKeys: boolean = true,
+ dropIndices: boolean = true,
+ ): Promise {
+ // It needs because if table does not exist and dropForeignKeys or dropIndices is true, we don't need
+ // to perform drop queries for foreign keys and indices.
+ if (ifExist) {
+ const isTableExist = await this.hasTable(target);
+ if (!isTableExist) return Promise.resolve();
+ }
+
+ // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
+ const createForeignKeys: boolean = dropForeignKeys;
+ const tablePath = this.getTablePath(target);
+ const table = await this.getCachedTable(tablePath);
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ if (dropIndices) {
+ table.indices.forEach((index) => {
+ upQueries.push(this.dropIndexSql(table, index));
+ downQueries.push(this.createIndexSql(table, index));
+ });
+ }
+
+ if (dropForeignKeys)
+ table.foreignKeys.forEach((foreignKey) =>
+ upQueries.push(this.dropForeignKeySql(table, foreignKey)),
+ );
+
+ upQueries.push(this.dropTableSql(table));
+ downQueries.push(this.createTableSql(table, createForeignKeys));
+
+ // if table had columns with generated type, we must remove the expression from the metadata table
+ const generatedColumns = table.columns.filter(
+ (column) => column.generatedType && column.asExpression,
+ );
+ for (const column of generatedColumns) {
+ const tableNameWithSchema = (await this.getTableNameWithSchema(table.name)).split('.');
+ const tableName = tableNameWithSchema[1];
+ const schema = tableNameWithSchema[0];
+
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+
+ const insertQuery = this.insertTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ upQueries.push(deleteQuery);
+ downQueries.push(insertQuery);
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Creates a new view.
+ */
+ async createView(view: View, syncWithMetadata: boolean = false): Promise {
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+ upQueries.push(this.createViewSql(view));
+ if (syncWithMetadata) upQueries.push(await this.insertViewDefinitionSql(view));
+ downQueries.push(this.dropViewSql(view));
+ if (syncWithMetadata) downQueries.push(await this.deleteViewDefinitionSql(view));
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Drops the view.
+ */
+ async dropView(target: View | string): Promise {
+ const viewName = InstanceChecker.isView(target) ? target.name : target;
+ const view = await this.getCachedView(viewName);
+
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+ upQueries.push(await this.deleteViewDefinitionSql(view));
+ upQueries.push(this.dropViewSql(view));
+ downQueries.push(await this.insertViewDefinitionSql(view));
+ downQueries.push(this.createViewSql(view));
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Renames the given table.
+ */
+ async renameTable(oldTableOrName: Table | string, newTableName: string): Promise {
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+ const oldTable = InstanceChecker.isTable(oldTableOrName)
+ ? oldTableOrName
+ : await this.getCachedTable(oldTableOrName);
+ const newTable = oldTable.clone();
+
+ const { schema: schemaName, tableName: oldTableName } = this.driver.parseTableName(oldTable);
+
+ newTable.name = schemaName ? `${schemaName}.${newTableName}` : newTableName;
+
+ upQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(oldTable)} RENAME TO "${newTableName}"`),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(newTable)} RENAME TO "${oldTableName}"`),
+ );
+
+ // rename column primary key constraint if it has default constraint name
+ if (
+ newTable.primaryColumns.length > 0 &&
+ !newTable.primaryColumns[0].primaryKeyConstraintName
+ ) {
+ const columnNames = newTable.primaryColumns.map((column) => column.name);
+
+ const oldPkName = this.connection.namingStrategy.primaryKeyName(oldTable, columnNames);
+
+ const newPkName = this.connection.namingStrategy.primaryKeyName(newTable, columnNames);
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ newTable,
+ )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ newTable,
+ )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+ ),
+ );
+ }
+
+ // rename sequences
+ newTable.columns.map((col) => {
+ if (col.isGenerated && col.generationStrategy === 'increment') {
+ const sequencePath = this.buildSequencePath(oldTable, col.name);
+ const sequenceName = this.buildSequenceName(oldTable, col.name);
+
+ const newSequencePath = this.buildSequencePath(newTable, col.name);
+ const newSequenceName = this.buildSequenceName(newTable, col.name);
+
+ const up = `ALTER SEQUENCE ${this.escapePath(sequencePath)} RENAME TO "${newSequenceName}"`;
+ const down = `ALTER SEQUENCE ${this.escapePath(
+ newSequencePath,
+ )} RENAME TO "${sequenceName}"`;
+
+ upQueries.push(new Query(up));
+ downQueries.push(new Query(down));
+ }
+ });
+
+ // rename unique constraints
+ newTable.uniques.forEach((unique) => {
+ const oldUniqueName = this.connection.namingStrategy.uniqueConstraintName(
+ oldTable,
+ unique.columnNames,
+ );
+
+ // Skip renaming if Unique has user defined constraint name
+ if (unique.name !== oldUniqueName) return;
+
+ // build new constraint name
+ const newUniqueName = this.connection.namingStrategy.uniqueConstraintName(
+ newTable,
+ unique.columnNames,
+ );
+
+ // build queries
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(newTable)} RENAME CONSTRAINT "${
+ unique.name
+ }" TO "${newUniqueName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(newTable)} RENAME CONSTRAINT "${newUniqueName}" TO "${
+ unique.name
+ }"`,
+ ),
+ );
+
+ // replace constraint name
+ unique.name = newUniqueName;
+ });
+
+ // rename index constraints
+ newTable.indices.forEach((index) => {
+ const oldIndexName = this.connection.namingStrategy.indexName(
+ oldTable,
+ index.columnNames,
+ index.where,
+ );
+
+ // Skip renaming if Index has user defined constraint name
+ if (index.name !== oldIndexName) return;
+
+ // build new constraint name
+ const { schema } = this.driver.parseTableName(newTable);
+ const newIndexName = this.connection.namingStrategy.indexName(
+ newTable,
+ index.columnNames,
+ index.where,
+ );
+
+ // build queries
+ const up = schema
+ ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
+ : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`;
+ const down = schema
+ ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
+ : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`;
+ upQueries.push(new Query(up));
+ downQueries.push(new Query(down));
+
+ // replace constraint name
+ index.name = newIndexName;
+ });
+
+ // rename foreign key constraints
+ newTable.foreignKeys.forEach((foreignKey) => {
+ const oldForeignKeyName = this.connection.namingStrategy.foreignKeyName(
+ oldTable,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+
+ // Skip renaming if foreign key has user defined constraint name
+ if (foreignKey.name !== oldForeignKeyName) return;
+
+ // build new constraint name
+ const newForeignKeyName = this.connection.namingStrategy.foreignKeyName(
+ newTable,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+
+ // build queries
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(newTable)} RENAME CONSTRAINT "${
+ foreignKey.name
+ }" TO "${newForeignKeyName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(newTable)} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
+ foreignKey.name
+ }"`,
+ ),
+ );
+
+ // replace constraint name
+ foreignKey.name = newForeignKeyName;
+ });
+
+ // rename ENUM types
+ const enumColumns = newTable.columns.filter(
+ (column) => column.type === 'enum' || column.type === 'simple-enum',
+ );
+ for (let column of enumColumns) {
+ // skip renaming for user-defined enum name
+ if (column.enumName) continue;
+
+ const oldEnumType = await this.getUserDefinedTypeName(oldTable, column);
+ upQueries.push(
+ new Query(
+ `ALTER TYPE "${oldEnumType.schema}"."${oldEnumType.name}" RENAME TO ${this.buildEnumName(
+ newTable,
+ column,
+ false,
+ )}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TYPE ${this.buildEnumName(newTable, column)} RENAME TO "${oldEnumType.name}"`,
+ ),
+ );
+ }
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Creates a new column from the column in the table.
+ */
+ async addColumn(tableOrName: Table | string, column: TableColumn): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const clonedTable = table.clone();
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ if (column.type === 'enum' || column.type === 'simple-enum') {
+ const hasEnum = await this.hasEnumType(table, column);
+ if (!hasEnum) {
+ upQueries.push(this.createEnumTypeSql(table, column));
+ downQueries.push(this.dropEnumTypeSql(table, column));
+ }
+ }
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(table, column)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${column.name}"`),
+ );
+
+ // create or update primary key constraint
+ if (column.isPrimary) {
+ const primaryColumns = clonedTable.primaryColumns;
+ // if table already have primary key, me must drop it and recreate again
+ if (primaryColumns.length > 0) {
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ }
+
+ primaryColumns.push(column);
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ }
+
+ // create column index
+ const columnIndex = clonedTable.indices.find(
+ (index) => index.columnNames.length === 1 && index.columnNames[0] === column.name,
+ );
+ if (columnIndex) {
+ upQueries.push(this.createIndexSql(table, columnIndex));
+ downQueries.push(this.dropIndexSql(table, columnIndex));
+ }
+
+ // create unique constraint
+ if (column.isUnique) {
+ const uniqueConstraint = new TableUnique({
+ name: this.connection.namingStrategy.uniqueConstraintName(table, [column.name]),
+ columnNames: [column.name],
+ });
+ clonedTable.uniques.push(uniqueConstraint);
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ uniqueConstraint.name
+ }" UNIQUE ("${column.name}")`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint.name}"`,
+ ),
+ );
+ }
+
+ if (column.generatedType === 'STORED' && column.asExpression) {
+ const tableNameWithSchema = (await this.getTableNameWithSchema(table.name)).split('.');
+ const tableName = tableNameWithSchema[1];
+ const schema = tableNameWithSchema[0];
+
+ const insertQuery = this.insertTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+
+ upQueries.push(insertQuery);
+ downQueries.push(deleteQuery);
+ }
+
+ // create column's comment
+ if (column.comment) {
+ upQueries.push(
+ new Query(
+ `COMMENT ON COLUMN ${this.escapePath(table)}."${
+ column.name
+ }" IS ${this.escapeComment(column.comment)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `COMMENT ON COLUMN ${this.escapePath(table)}."${
+ column.name
+ }" IS ${this.escapeComment(column.comment)}`,
+ ),
+ );
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+
+ clonedTable.addColumn(column);
+ this.replaceCachedTable(table, clonedTable);
+ }
+
+ /**
+ * Creates a new columns from the column in the table.
+ */
+ async addColumns(tableOrName: Table | string, columns: TableColumn[]): Promise {
+ for (const column of columns) {
+ await this.addColumn(tableOrName, column);
+ }
+ }
+
+ /**
+ * Renames column in the given table.
+ */
+ async renameColumn(
+ tableOrName: Table | string,
+ oldTableColumnOrName: TableColumn | string,
+ newTableColumnOrName: TableColumn | string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
+ ? oldTableColumnOrName
+ : table.columns.find((c) => c.name === oldTableColumnOrName);
+ if (!oldColumn)
+ throw new TypeORMError(
+ `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
+ );
+
+ let newColumn;
+ if (InstanceChecker.isTableColumn(newTableColumnOrName)) {
+ newColumn = newTableColumnOrName;
+ } else {
+ newColumn = oldColumn.clone();
+ newColumn.name = newTableColumnOrName;
+ }
+
+ return this.changeColumn(table, oldColumn, newColumn);
+ }
+
+ /**
+ * Changes a column in the table.
+ */
+ async changeColumn(
+ tableOrName: Table | string,
+ oldTableColumnOrName: TableColumn | string,
+ newColumn: TableColumn,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ let clonedTable = table.clone();
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+ let defaultValueChanged = false;
+
+ const oldColumn = InstanceChecker.isTableColumn(oldTableColumnOrName)
+ ? oldTableColumnOrName
+ : table.columns.find((column) => column.name === oldTableColumnOrName);
+ if (!oldColumn)
+ throw new TypeORMError(
+ `Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`,
+ );
+
+ if (
+ oldColumn.type !== newColumn.type ||
+ oldColumn.length !== newColumn.length ||
+ newColumn.isArray !== oldColumn.isArray ||
+ (!oldColumn.generatedType && newColumn.generatedType === 'STORED') ||
+ (oldColumn.asExpression !== newColumn.asExpression && newColumn.generatedType === 'STORED')
+ ) {
+ // To avoid data conversion, we just recreate column
+ await this.dropColumn(table, oldColumn);
+ await this.addColumn(table, newColumn);
+
+ // update cloned table
+ clonedTable = table.clone();
+ } else {
+ if (oldColumn.name !== newColumn.name) {
+ // rename column
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
+ oldColumn.name
+ }" TO "${newColumn.name}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME COLUMN "${
+ newColumn.name
+ }" TO "${oldColumn.name}"`,
+ ),
+ );
+
+ // rename ENUM type
+ if (oldColumn.type === 'enum' || oldColumn.type === 'simple-enum') {
+ const oldEnumType = await this.getUserDefinedTypeName(table, oldColumn);
+ upQueries.push(
+ new Query(
+ `ALTER TYPE "${oldEnumType.schema}"."${
+ oldEnumType.name
+ }" RENAME TO ${this.buildEnumName(table, newColumn, false)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TYPE ${this.buildEnumName(table, newColumn)} RENAME TO "${oldEnumType.name}"`,
+ ),
+ );
+ }
+
+ // rename column primary key constraint
+ if (oldColumn.isPrimary === true && !oldColumn.primaryKeyConstraintName) {
+ const primaryColumns = clonedTable.primaryColumns;
+
+ // build old primary constraint name
+ const columnNames = primaryColumns.map((column) => column.name);
+ const oldPkName = this.connection.namingStrategy.primaryKeyName(clonedTable, columnNames);
+
+ // replace old column name with new column name
+ columnNames.splice(columnNames.indexOf(oldColumn.name), 1);
+ columnNames.push(newColumn.name);
+
+ // build new primary constraint name
+ const newPkName = this.connection.namingStrategy.primaryKeyName(clonedTable, columnNames);
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} RENAME CONSTRAINT "${oldPkName}" TO "${newPkName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} RENAME CONSTRAINT "${newPkName}" TO "${oldPkName}"`,
+ ),
+ );
+ }
+
+ // rename column sequence
+ if (oldColumn.isGenerated === true && newColumn.generationStrategy === 'increment') {
+ const sequencePath = this.buildSequencePath(table, oldColumn.name);
+ const sequenceName = this.buildSequenceName(table, oldColumn.name);
+
+ const newSequencePath = this.buildSequencePath(table, newColumn.name);
+ const newSequenceName = this.buildSequenceName(table, newColumn.name);
+
+ const up = `ALTER SEQUENCE ${this.escapePath(
+ sequencePath,
+ )} RENAME TO "${newSequenceName}"`;
+ const down = `ALTER SEQUENCE ${this.escapePath(
+ newSequencePath,
+ )} RENAME TO "${sequenceName}"`;
+
+ upQueries.push(new Query(up));
+ downQueries.push(new Query(down));
+ }
+
+ // rename unique constraints
+ clonedTable.findColumnUniques(oldColumn).forEach((unique) => {
+ const oldUniqueName = this.connection.namingStrategy.uniqueConstraintName(
+ clonedTable,
+ unique.columnNames,
+ );
+
+ // Skip renaming if Unique has user defined constraint name
+ if (unique.name !== oldUniqueName) return;
+
+ // build new constraint name
+ unique.columnNames.splice(unique.columnNames.indexOf(oldColumn.name), 1);
+ unique.columnNames.push(newColumn.name);
+ const newUniqueName = this.connection.namingStrategy.uniqueConstraintName(
+ clonedTable,
+ unique.columnNames,
+ );
+
+ // build queries
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME CONSTRAINT "${
+ unique.name
+ }" TO "${newUniqueName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME CONSTRAINT "${newUniqueName}" TO "${
+ unique.name
+ }"`,
+ ),
+ );
+
+ // replace constraint name
+ unique.name = newUniqueName;
+ });
+
+ // rename index constraints
+ clonedTable.findColumnIndices(oldColumn).forEach((index) => {
+ const oldIndexName = this.connection.namingStrategy.indexName(
+ clonedTable,
+ index.columnNames,
+ index.where,
+ );
+
+ // Skip renaming if Index has user defined constraint name
+ if (index.name !== oldIndexName) return;
+
+ // build new constraint name
+ index.columnNames.splice(index.columnNames.indexOf(oldColumn.name), 1);
+ index.columnNames.push(newColumn.name);
+ const { schema } = this.driver.parseTableName(table);
+ const newIndexName = this.connection.namingStrategy.indexName(
+ clonedTable,
+ index.columnNames,
+ index.where,
+ );
+
+ // build queries
+ const up = schema
+ ? `ALTER INDEX "${schema}"."${index.name}" RENAME TO "${newIndexName}"`
+ : `ALTER INDEX "${index.name}" RENAME TO "${newIndexName}"`;
+ const down = schema
+ ? `ALTER INDEX "${schema}"."${newIndexName}" RENAME TO "${index.name}"`
+ : `ALTER INDEX "${newIndexName}" RENAME TO "${index.name}"`;
+
+ upQueries.push(new Query(up));
+ downQueries.push(new Query(down));
+
+ // replace constraint name
+ index.name = newIndexName;
+ });
+
+ // rename foreign key constraints
+ clonedTable.findColumnForeignKeys(oldColumn).forEach((foreignKey) => {
+ const foreignKeyName = this.connection.namingStrategy.foreignKeyName(
+ clonedTable,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+
+ // Skip renaming if foreign key has user defined constraint name
+ if (foreignKey.name !== foreignKeyName) return;
+
+ // build new constraint name
+ foreignKey.columnNames.splice(foreignKey.columnNames.indexOf(oldColumn.name), 1);
+ foreignKey.columnNames.push(newColumn.name);
+ const newForeignKeyName = this.connection.namingStrategy.foreignKeyName(
+ clonedTable,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+
+ // build queries
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME CONSTRAINT "${
+ foreignKey.name
+ }" TO "${newForeignKeyName}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} RENAME CONSTRAINT "${newForeignKeyName}" TO "${
+ foreignKey.name
+ }"`,
+ ),
+ );
+
+ // replace constraint name
+ foreignKey.name = newForeignKeyName;
+ });
+
+ // rename old column in the Table object
+ const oldTableColumn = clonedTable.columns.find((column) => column.name === oldColumn.name);
+ clonedTable.columns[clonedTable.columns.indexOf(oldTableColumn!)].name = newColumn.name;
+ oldColumn.name = newColumn.name;
+ }
+
+ if (newColumn.precision !== oldColumn.precision || newColumn.scale !== oldColumn.scale) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" TYPE ${this.driver.createFullType(newColumn)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" TYPE ${this.driver.createFullType(oldColumn)}`,
+ ),
+ );
+ }
+
+ if (
+ (newColumn.type === 'enum' || newColumn.type === 'simple-enum') &&
+ (oldColumn.type === 'enum' || oldColumn.type === 'simple-enum') &&
+ (!OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) ||
+ newColumn.enumName !== oldColumn.enumName)
+ ) {
+ const arraySuffix = newColumn.isArray ? '[]' : '';
+
+ // "public"."new_enum"
+ const newEnumName = this.buildEnumName(table, newColumn);
+
+ // "public"."old_enum"
+ const oldEnumName = this.buildEnumName(table, oldColumn);
+
+ // "old_enum"
+ const oldEnumNameWithoutSchema = this.buildEnumName(table, oldColumn, false);
+
+ //"public"."old_enum_old"
+ const oldEnumNameWithSchema_old = this.buildEnumName(table, oldColumn, true, false, true);
+
+ //"old_enum_old"
+ const oldEnumNameWithoutSchema_old = this.buildEnumName(
+ table,
+ oldColumn,
+ false,
+ false,
+ true,
+ );
+
+ // rename old ENUM
+ upQueries.push(
+ new Query(`ALTER TYPE ${oldEnumName} RENAME TO ${oldEnumNameWithoutSchema_old}`),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TYPE ${oldEnumNameWithSchema_old} RENAME TO ${oldEnumNameWithoutSchema}`,
+ ),
+ );
+
+ // create new ENUM
+ upQueries.push(this.createEnumTypeSql(table, newColumn, newEnumName));
+ downQueries.push(this.dropEnumTypeSql(table, newColumn, newEnumName));
+
+ // if column have default value, we must drop it to avoid issues with type casting
+ if (oldColumn.default !== null && oldColumn.default !== undefined) {
+ // mark default as changed to prevent double update
+ defaultValueChanged = true;
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" SET DEFAULT ${
+ oldColumn.default
+ }`,
+ ),
+ );
+ }
+
+ // build column types
+ const upType = `${newEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${newEnumName}${arraySuffix}`;
+ const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`;
+
+ // update column to use new type
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" TYPE ${upType}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" TYPE ${downType}`,
+ ),
+ );
+
+ // restore column default or create new one
+ if (newColumn.default !== null && newColumn.default !== undefined) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
+ newColumn.default
+ }`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`,
+ ),
+ );
+ }
+
+ // remove old ENUM
+ upQueries.push(this.dropEnumTypeSql(table, oldColumn, oldEnumNameWithSchema_old));
+ downQueries.push(this.createEnumTypeSql(table, oldColumn, oldEnumNameWithSchema_old));
+ }
+
+ if (oldColumn.isNullable !== newColumn.isNullable) {
+ if (newColumn.isNullable) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
+ ),
+ );
+ } else {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" SET NOT NULL`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ALTER COLUMN "${oldColumn.name}" DROP NOT NULL`,
+ ),
+ );
+ }
+ }
+
+ if (oldColumn.comment !== newColumn.comment) {
+ upQueries.push(
+ new Query(
+ `COMMENT ON COLUMN ${this.escapePath(table)}."${
+ oldColumn.name
+ }" IS ${this.escapeComment(newColumn.comment)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `COMMENT ON COLUMN ${this.escapePath(table)}."${
+ newColumn.name
+ }" IS ${this.escapeComment(oldColumn.comment)}`,
+ ),
+ );
+ }
+
+ if (newColumn.isPrimary !== oldColumn.isPrimary) {
+ const primaryColumns = clonedTable.primaryColumns;
+
+ // if primary column state changed, we must always drop existed constraint.
+ if (primaryColumns.length > 0) {
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ }
+
+ if (newColumn.isPrimary === true) {
+ primaryColumns.push(newColumn);
+ // update column in table
+ const column = clonedTable.columns.find((column) => column.name === newColumn.name);
+ column!.isPrimary = true;
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ } else {
+ const primaryColumn = primaryColumns.find((c) => c.name === newColumn.name);
+ primaryColumns.splice(primaryColumns.indexOf(primaryColumn!), 1);
+
+ // update column in table
+ const column = clonedTable.columns.find((column) => column.name === newColumn.name);
+ column!.isPrimary = false;
+
+ // if we have another primary keys, we must recreate constraint.
+ if (primaryColumns.length > 0) {
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ }
+ }
+ }
+
+ if (newColumn.isUnique !== oldColumn.isUnique) {
+ if (newColumn.isUnique === true) {
+ const uniqueConstraint = new TableUnique({
+ name: this.connection.namingStrategy.uniqueConstraintName(table, [newColumn.name]),
+ columnNames: [newColumn.name],
+ });
+ clonedTable.uniques.push(uniqueConstraint);
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ uniqueConstraint.name
+ }" UNIQUE ("${newColumn.name}")`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint.name}"`,
+ ),
+ );
+ } else {
+ const uniqueConstraint = clonedTable.uniques.find((unique) => {
+ return (
+ unique.columnNames.length === 1 &&
+ !!unique.columnNames.find((columnName) => columnName === newColumn.name)
+ );
+ });
+ clonedTable.uniques.splice(clonedTable.uniques.indexOf(uniqueConstraint!), 1);
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueConstraint!.name}"`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ uniqueConstraint!.name
+ }" UNIQUE ("${newColumn.name}")`,
+ ),
+ );
+ }
+ }
+
+ if (oldColumn.isGenerated !== newColumn.isGenerated) {
+ // if old column was "generated", we should clear defaults
+ if (oldColumn.isGenerated) {
+ if (oldColumn.generationStrategy === 'uuid') {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ oldColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ oldColumn.name
+ }" SET DEFAULT ${this.driver.uuidGenerator}`,
+ ),
+ );
+ } else if (oldColumn.generationStrategy === 'increment') {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT nextval('${this.escapePath(
+ this.buildSequencePath(table, newColumn),
+ )}')`,
+ ),
+ );
+
+ upQueries.push(
+ new Query(
+ `DROP SEQUENCE ${this.escapePath(this.buildSequencePath(table, newColumn))}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
+ this.buildSequencePath(table, newColumn),
+ )} OWNED BY ${this.escapePath(table)}."${newColumn.name}"`,
+ ),
+ );
+ }
+ }
+
+ if (newColumn.generationStrategy === 'uuid') {
+ if (newColumn.isGenerated === true) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT ${this.driver.uuidGenerator}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ } else {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT ${this.driver.uuidGenerator}`,
+ ),
+ );
+ }
+ } else if (newColumn.generationStrategy === 'increment') {
+ 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))}`,
+ ),
+ );
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT nextval('${this.escapePath(
+ this.buildSequencePath(table, newColumn),
+ )}')`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ } else {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT nextval('${this.escapePath(
+ this.buildSequencePath(table, newColumn),
+ )}')`,
+ ),
+ );
+
+ upQueries.push(
+ new Query(
+ `DROP SEQUENCE ${this.escapePath(this.buildSequencePath(table, newColumn))}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `CREATE SEQUENCE IF NOT EXISTS ${this.escapePath(
+ this.buildSequencePath(table, newColumn),
+ )} OWNED BY ${this.escapePath(table)}."${newColumn.name}"`,
+ ),
+ );
+ }
+ }
+ }
+
+ // the default might have changed when the enum changed
+ if (newColumn.default !== oldColumn.default && !defaultValueChanged) {
+ if (newColumn.default !== null && newColumn.default !== undefined) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
+ newColumn.default
+ }`,
+ ),
+ );
+
+ if (oldColumn.default !== null && oldColumn.default !== undefined) {
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" SET DEFAULT ${oldColumn.default}`,
+ ),
+ );
+ } else {
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" DROP DEFAULT`,
+ ),
+ );
+ }
+ } else if (oldColumn.default !== null && oldColumn.default !== undefined) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${
+ oldColumn.default
+ }`,
+ ),
+ );
+ }
+ }
+
+ if (
+ (newColumn.spatialFeatureType || '').toLowerCase() !==
+ (oldColumn.spatialFeatureType || '').toLowerCase() ||
+ newColumn.srid !== oldColumn.srid
+ ) {
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" TYPE ${this.driver.createFullType(newColumn)}`,
+ ),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${
+ newColumn.name
+ }" TYPE ${this.driver.createFullType(oldColumn)}`,
+ ),
+ );
+ }
+
+ if (newColumn.generatedType !== oldColumn.generatedType) {
+ // Convert generated column data to normal column
+ if (!newColumn.generatedType || newColumn.generatedType === 'VIRTUAL') {
+ // We can copy the generated data to the new column
+ const tableNameWithSchema = (await this.getTableNameWithSchema(table.name)).split('.');
+ const tableName = tableNameWithSchema[1];
+ const schema = tableNameWithSchema[0];
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} RENAME COLUMN "${oldColumn.name}" TO "TEMP_OLD_${oldColumn.name}"`,
+ ),
+ );
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(
+ table,
+ newColumn,
+ )}`,
+ ),
+ );
+ upQueries.push(
+ new Query(
+ `UPDATE ${this.escapePath(table)} SET "${
+ newColumn.name
+ }" = "TEMP_OLD_${oldColumn.name}"`,
+ ),
+ );
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} DROP COLUMN "TEMP_OLD_${oldColumn.name}"`,
+ ),
+ );
+ upQueries.push(
+ this.deleteTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: oldColumn.name,
+ }),
+ );
+ // However, we can't copy it back on downgrade. It needs to regenerate.
+ downQueries.push(
+ this.insertTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: oldColumn.name,
+ value: oldColumn.asExpression,
+ }),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(
+ table,
+ oldColumn,
+ )}`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${newColumn.name}"`),
+ );
+ // downQueries.push(
+ // this.deleteTypeormMetadataSql({
+ // database: this.driver.database,
+ // schema,
+ // table: tableName,
+ // type: MetadataTableType.GENERATED_COLUMN,
+ // name: newColumn.name,
+ // }),
+ // )
+ }
+ }
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+ this.replaceCachedTable(table, clonedTable);
+ }
+
+ /**
+ * Changes a column in the table.
+ */
+ async changeColumns(
+ tableOrName: Table | string,
+ changedColumns: { newColumn: TableColumn; oldColumn: TableColumn }[],
+ ): Promise {
+ for (const { oldColumn, newColumn } of changedColumns) {
+ await this.changeColumn(tableOrName, oldColumn, newColumn);
+ }
+ }
+
+ /**
+ * Drops column in the table.
+ */
+ async dropColumn(tableOrName: Table | string, columnOrName: TableColumn | string): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const column = InstanceChecker.isTableColumn(columnOrName)
+ ? columnOrName
+ : table.findColumnByName(columnOrName);
+ if (!column)
+ throw new TypeORMError(`Column "${columnOrName}" was not found in table "${table.name}"`);
+
+ const clonedTable = table.clone();
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ // drop primary key constraint
+ if (column.isPrimary) {
+ const pkName = column.primaryKeyConstraintName
+ ? column.primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ clonedTable.primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = clonedTable.primaryColumns
+ .map((primaryColumn) => `"${primaryColumn.name}"`)
+ .join(', ');
+
+ upQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(clonedTable)} DROP CONSTRAINT "${pkName}"`),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ clonedTable,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+
+ // update column in table
+ const tableColumn = clonedTable.findColumnByName(column.name);
+ tableColumn!.isPrimary = false;
+
+ // if primary key have multiple columns, we must recreate it without dropped column
+ if (clonedTable.primaryColumns.length > 0) {
+ const pkName = clonedTable.primaryColumns[0].primaryKeyConstraintName
+ ? clonedTable.primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ clonedTable.primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = clonedTable.primaryColumns
+ .map((primaryColumn) => `"${primaryColumn.name}"`)
+ .join(', ');
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ clonedTable,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNames})`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(clonedTable)} DROP CONSTRAINT "${pkName}"`),
+ );
+ }
+ }
+
+ // drop column index
+ const columnIndex = clonedTable.indices.find(
+ (index) => index.columnNames.length === 1 && index.columnNames[0] === column.name,
+ );
+ if (columnIndex) {
+ clonedTable.indices.splice(clonedTable.indices.indexOf(columnIndex), 1);
+ upQueries.push(this.dropIndexSql(table, columnIndex));
+ downQueries.push(this.createIndexSql(table, columnIndex));
+ }
+
+ // drop column check
+ const columnCheck = clonedTable.checks.find(
+ (check) =>
+ !!check.columnNames &&
+ check.columnNames.length === 1 &&
+ check.columnNames[0] === column.name,
+ );
+ if (columnCheck) {
+ clonedTable.checks.splice(clonedTable.checks.indexOf(columnCheck), 1);
+ upQueries.push(this.dropCheckConstraintSql(table, columnCheck));
+ downQueries.push(this.createCheckConstraintSql(table, columnCheck));
+ }
+
+ // drop column unique
+ const columnUnique = clonedTable.uniques.find(
+ (unique) => unique.columnNames.length === 1 && unique.columnNames[0] === column.name,
+ );
+ if (columnUnique) {
+ clonedTable.uniques.splice(clonedTable.uniques.indexOf(columnUnique), 1);
+ upQueries.push(this.dropUniqueConstraintSql(table, columnUnique));
+ downQueries.push(this.createUniqueConstraintSql(table, columnUnique));
+ }
+
+ upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} DROP COLUMN "${column.name}"`));
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD ${this.buildCreateColumnSql(table, column)}`,
+ ),
+ );
+
+ // drop enum type
+ if (column.type === 'enum' || column.type === 'simple-enum') {
+ const hasEnum = await this.hasEnumType(table, column);
+ if (hasEnum) {
+ const enumType = await this.getUserDefinedTypeName(table, column);
+ const escapedEnumName = `"${enumType.schema}"."${enumType.name}"`;
+ upQueries.push(this.dropEnumTypeSql(table, column, escapedEnumName));
+ downQueries.push(this.createEnumTypeSql(table, column, escapedEnumName));
+ }
+ }
+
+ if (column.generatedType === 'STORED') {
+ const tableNameWithSchema = (await this.getTableNameWithSchema(table.name)).split('.');
+ const tableName = tableNameWithSchema[1];
+ const schema = tableNameWithSchema[0];
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+ const insertQuery = this.insertTypeormMetadataSql({
+ database: this.driver.database,
+ schema,
+ table: tableName,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ upQueries.push(deleteQuery);
+ downQueries.push(insertQuery);
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+
+ clonedTable.removeColumn(column);
+ this.replaceCachedTable(table, clonedTable);
+ }
+
+ /**
+ * Drops the columns in the table.
+ */
+ async dropColumns(tableOrName: Table | string, columns: TableColumn[] | string[]): Promise {
+ for (const column of columns) {
+ await this.dropColumn(tableOrName, column);
+ }
+ }
+
+ /**
+ * Creates a new primary key.
+ */
+ async createPrimaryKey(
+ tableOrName: Table | string,
+ columnNames: string[],
+ constraintName?: string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const clonedTable = table.clone();
+
+ const up = this.createPrimaryKeySql(table, columnNames, constraintName);
+
+ // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names.
+ clonedTable.columns.forEach((column) => {
+ if (columnNames.find((columnName) => columnName === column.name)) column.isPrimary = true;
+ });
+ const down = this.dropPrimaryKeySql(clonedTable);
+
+ await this.executeQueries(up, down);
+ this.replaceCachedTable(table, clonedTable);
+ }
+
+ /**
+ * Updates composite primary keys.
+ */
+ async updatePrimaryKeys(tableOrName: Table | string, columns: TableColumn[]): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const clonedTable = table.clone();
+ const columnNames = columns.map((column) => column.name);
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ // if table already have primary columns, we must drop them.
+ const primaryColumns = clonedTable.primaryColumns;
+ if (primaryColumns.length > 0) {
+ const pkName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ clonedTable,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNamesString = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+
+ upQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+ downQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
+ ),
+ );
+ }
+
+ // update columns in table.
+ clonedTable.columns
+ .filter((column) => columnNames.indexOf(column.name) !== -1)
+ .forEach((column) => (column.isPrimary = true));
+
+ const pkName = primaryColumns[0]?.primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(clonedTable, columnNames);
+
+ const columnNamesString = columnNames.map((columnName) => `"${columnName}"`).join(', ');
+
+ upQueries.push(
+ new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${pkName}" PRIMARY KEY (${columnNamesString})`,
+ ),
+ );
+ downQueries.push(
+ new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${pkName}"`),
+ );
+
+ await this.executeQueries(upQueries, downQueries);
+ this.replaceCachedTable(table, clonedTable);
+ }
+
+ /**
+ * Drops a primary key.
+ */
+ async dropPrimaryKey(tableOrName: Table | string, constraintName?: string): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const up = this.dropPrimaryKeySql(table);
+ const down = this.createPrimaryKeySql(
+ table,
+ table.primaryColumns.map((column) => column.name),
+ constraintName,
+ );
+ await this.executeQueries(up, down);
+ table.primaryColumns.forEach((column) => {
+ column.isPrimary = false;
+ });
+ }
+
+ /**
+ * Creates new unique constraint.
+ */
+ async createUniqueConstraint(
+ tableOrName: Table | string,
+ uniqueConstraint: TableUnique,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ // new unique constraint may be passed without name. In this case we generate unique name manually.
+ if (!uniqueConstraint.name)
+ uniqueConstraint.name = this.connection.namingStrategy.uniqueConstraintName(
+ table,
+ uniqueConstraint.columnNames,
+ );
+
+ const up = this.createUniqueConstraintSql(table, uniqueConstraint);
+ const down = this.dropUniqueConstraintSql(table, uniqueConstraint);
+ await this.executeQueries(up, down);
+ table.addUniqueConstraint(uniqueConstraint);
+ }
+
+ /**
+ * Creates new unique constraints.
+ */
+ async createUniqueConstraints(
+ tableOrName: Table | string,
+ uniqueConstraints: TableUnique[],
+ ): Promise {
+ for (const uniqueConstraint of uniqueConstraints) {
+ await this.createUniqueConstraint(tableOrName, uniqueConstraint);
+ }
+ }
+
+ /**
+ * Drops unique constraint.
+ */
+ async dropUniqueConstraint(
+ tableOrName: Table | string,
+ uniqueOrName: TableUnique | string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const uniqueConstraint = InstanceChecker.isTableUnique(uniqueOrName)
+ ? uniqueOrName
+ : table.uniques.find((u) => u.name === uniqueOrName);
+ if (!uniqueConstraint)
+ throw new TypeORMError(`Supplied unique constraint was not found in table ${table.name}`);
+
+ const up = this.dropUniqueConstraintSql(table, uniqueConstraint);
+ const down = this.createUniqueConstraintSql(table, uniqueConstraint);
+ await this.executeQueries(up, down);
+ table.removeUniqueConstraint(uniqueConstraint);
+ }
+
+ /**
+ * Drops unique constraints.
+ */
+ async dropUniqueConstraints(
+ tableOrName: Table | string,
+ uniqueConstraints: TableUnique[],
+ ): Promise {
+ for (const uniqueConstraint of uniqueConstraints) {
+ await this.dropUniqueConstraint(tableOrName, uniqueConstraint);
+ }
+ }
+
+ /**
+ * Creates new check constraint.
+ */
+ async createCheckConstraint(
+ tableOrName: Table | string,
+ checkConstraint: TableCheck,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ // new unique constraint may be passed without name. In this case we generate unique name manually.
+ if (!checkConstraint.name)
+ checkConstraint.name = this.connection.namingStrategy.checkConstraintName(
+ table,
+ checkConstraint.expression!,
+ );
+
+ const up = this.createCheckConstraintSql(table, checkConstraint);
+ const down = this.dropCheckConstraintSql(table, checkConstraint);
+ await this.executeQueries(up, down);
+ table.addCheckConstraint(checkConstraint);
+ }
+
+ /**
+ * Creates new check constraints.
+ */
+ async createCheckConstraints(
+ tableOrName: Table | string,
+ checkConstraints: TableCheck[],
+ ): Promise {
+ const promises = checkConstraints.map((checkConstraint) =>
+ this.createCheckConstraint(tableOrName, checkConstraint),
+ );
+ await Promise.all(promises);
+ }
+
+ /**
+ * Drops check constraint.
+ */
+ async dropCheckConstraint(
+ tableOrName: Table | string,
+ checkOrName: TableCheck | string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const checkConstraint = InstanceChecker.isTableCheck(checkOrName)
+ ? checkOrName
+ : table.checks.find((c) => c.name === checkOrName);
+ if (!checkConstraint)
+ throw new TypeORMError(`Supplied check constraint was not found in table ${table.name}`);
+
+ const up = this.dropCheckConstraintSql(table, checkConstraint);
+ const down = this.createCheckConstraintSql(table, checkConstraint);
+ await this.executeQueries(up, down);
+ table.removeCheckConstraint(checkConstraint);
+ }
+
+ /**
+ * Drops check constraints.
+ */
+ async dropCheckConstraints(
+ tableOrName: Table | string,
+ checkConstraints: TableCheck[],
+ ): Promise {
+ const promises = checkConstraints.map((checkConstraint) =>
+ this.dropCheckConstraint(tableOrName, checkConstraint),
+ );
+ await Promise.all(promises);
+ }
+
+ /**
+ * Creates new exclusion constraint.
+ */
+ async createExclusionConstraint(
+ tableOrName: Table | string,
+ exclusionConstraint: TableExclusion,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ // new unique constraint may be passed without name. In this case we generate unique name manually.
+ if (!exclusionConstraint.name)
+ exclusionConstraint.name = this.connection.namingStrategy.exclusionConstraintName(
+ table,
+ exclusionConstraint.expression!,
+ );
+
+ const up = this.createExclusionConstraintSql(table, exclusionConstraint);
+ const down = this.dropExclusionConstraintSql(table, exclusionConstraint);
+ await this.executeQueries(up, down);
+ table.addExclusionConstraint(exclusionConstraint);
+ }
+
+ /**
+ * Creates new exclusion constraints.
+ */
+ async createExclusionConstraints(
+ tableOrName: Table | string,
+ exclusionConstraints: TableExclusion[],
+ ): Promise {
+ const promises = exclusionConstraints.map((exclusionConstraint) =>
+ this.createExclusionConstraint(tableOrName, exclusionConstraint),
+ );
+ await Promise.all(promises);
+ }
+
+ /**
+ * Drops exclusion constraint.
+ */
+ async dropExclusionConstraint(
+ tableOrName: Table | string,
+ exclusionOrName: TableExclusion | string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const exclusionConstraint = InstanceChecker.isTableExclusion(exclusionOrName)
+ ? exclusionOrName
+ : table.exclusions.find((c) => c.name === exclusionOrName);
+ if (!exclusionConstraint)
+ throw new TypeORMError(`Supplied exclusion constraint was not found in table ${table.name}`);
+
+ const up = this.dropExclusionConstraintSql(table, exclusionConstraint);
+ const down = this.createExclusionConstraintSql(table, exclusionConstraint);
+ await this.executeQueries(up, down);
+ table.removeExclusionConstraint(exclusionConstraint);
+ }
+
+ /**
+ * Drops exclusion constraints.
+ */
+ async dropExclusionConstraints(
+ tableOrName: Table | string,
+ exclusionConstraints: TableExclusion[],
+ ): Promise {
+ const promises = exclusionConstraints.map((exclusionConstraint) =>
+ this.dropExclusionConstraint(tableOrName, exclusionConstraint),
+ );
+ await Promise.all(promises);
+ }
+
+ /**
+ * Creates a new foreign key.
+ */
+ async createForeignKey(tableOrName: Table | string, foreignKey: TableForeignKey): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ // new FK may be passed without name. In this case we generate FK name manually.
+ if (!foreignKey.name)
+ foreignKey.name = this.connection.namingStrategy.foreignKeyName(
+ table,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+
+ const up = this.createForeignKeySql(table, foreignKey);
+ const down = this.dropForeignKeySql(table, foreignKey);
+ await this.executeQueries(up, down);
+ table.addForeignKey(foreignKey);
+ }
+
+ /**
+ * Creates a new foreign keys.
+ */
+ async createForeignKeys(
+ tableOrName: Table | string,
+ foreignKeys: TableForeignKey[],
+ ): Promise {
+ for (const foreignKey of foreignKeys) {
+ await this.createForeignKey(tableOrName, foreignKey);
+ }
+ }
+
+ /**
+ * Drops a foreign key from the table.
+ */
+ async dropForeignKey(
+ tableOrName: Table | string,
+ foreignKeyOrName: TableForeignKey | string,
+ ): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const foreignKey = InstanceChecker.isTableForeignKey(foreignKeyOrName)
+ ? foreignKeyOrName
+ : table.foreignKeys.find((fk) => fk.name === foreignKeyOrName);
+ if (!foreignKey)
+ throw new TypeORMError(`Supplied foreign key was not found in table ${table.name}`);
+
+ if (!foreignKey.name) {
+ foreignKey.name = this.connection.namingStrategy.foreignKeyName(
+ table,
+ foreignKey.columnNames,
+ this.getTablePath(foreignKey),
+ foreignKey.referencedColumnNames,
+ );
+ }
+
+ const up = this.dropForeignKeySql(table, foreignKey);
+ const down = this.createForeignKeySql(table, foreignKey);
+ await this.executeQueries(up, down);
+ table.removeForeignKey(foreignKey);
+ }
+
+ /**
+ * Drops a foreign keys from the table.
+ */
+ async dropForeignKeys(
+ tableOrName: Table | string,
+ foreignKeys: TableForeignKey[],
+ ): Promise {
+ for (const foreignKey of foreignKeys) {
+ await this.dropForeignKey(tableOrName, foreignKey);
+ }
+ }
+
+ /**
+ * Creates a new index.
+ */
+ async createIndex(tableOrName: Table | string, index: TableIndex): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ // new index may be passed without name. In this case we generate index name manually.
+ if (!index.name) index.name = this.generateIndexName(table, index);
+
+ const up = this.createIndexSql(table, index);
+ const down = this.dropIndexSql(table, index);
+ await this.executeQueries(up, down);
+ table.addIndex(index);
+ }
+
+ /**
+ * Create a new view index.
+ */
+ async createViewIndex(viewOrName: View | string, index: TableIndex): Promise {
+ const view = InstanceChecker.isView(viewOrName)
+ ? viewOrName
+ : await this.getCachedView(viewOrName);
+
+ // new index may be passed without name. In this case we generate index name manually.
+ if (!index.name) index.name = this.generateIndexName(view, index);
+
+ const up = this.createViewIndexSql(view, index);
+ const down = this.dropIndexSql(view, index);
+ await this.executeQueries(up, down);
+ view.addIndex(index);
+ }
+
+ /**
+ * Creates a new indices
+ */
+ async createIndices(tableOrName: Table | string, indices: TableIndex[]): Promise {
+ for (const index of indices) {
+ await this.createIndex(tableOrName, index);
+ }
+ }
+
+ /**
+ * Creates new view indices
+ */
+ async createViewIndices(viewOrName: View | string, indices: TableIndex[]): Promise {
+ for (const index of indices) {
+ await this.createViewIndex(viewOrName, index);
+ }
+ }
+
+ /**
+ * Drops an index from the table.
+ */
+ async dropIndex(tableOrName: Table | string, indexOrName: TableIndex | string): Promise {
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const index = InstanceChecker.isTableIndex(indexOrName)
+ ? indexOrName
+ : table.indices.find((i) => i.name === indexOrName);
+ if (!index)
+ throw new TypeORMError(`Supplied index ${indexOrName} was not found in table ${table.name}`);
+ // old index may be passed without name. In this case we generate index name manually.
+ if (!index.name) index.name = this.generateIndexName(table, index);
+
+ const up = this.dropIndexSql(table, index);
+ const down = this.createIndexSql(table, index);
+ await this.executeQueries(up, down);
+ table.removeIndex(index);
+ }
+
+ /**
+ * Drops an index from a view.
+ */
+ async dropViewIndex(viewOrName: View | string, indexOrName: TableIndex | string): Promise {
+ const view = InstanceChecker.isView(viewOrName)
+ ? viewOrName
+ : await this.getCachedView(viewOrName);
+ const index = InstanceChecker.isTableIndex(indexOrName)
+ ? indexOrName
+ : view.indices.find((i) => i.name === indexOrName);
+ if (!index)
+ throw new TypeORMError(`Supplied index ${indexOrName} was not found in view ${view.name}`);
+ // old index may be passed without name. In this case we generate index name manually.
+ if (!index.name) index.name = this.generateIndexName(view, index);
+
+ const up = this.dropIndexSql(view, index);
+ const down = this.createViewIndexSql(view, index);
+ await this.executeQueries(up, down);
+ view.removeIndex(index);
+ }
+
+ /**
+ * Drops an indices from the table.
+ */
+ async dropIndices(tableOrName: Table | string, indices: TableIndex[]): Promise {
+ for (const index of indices) {
+ await this.dropIndex(tableOrName, index);
+ }
+ }
+
+ /**
+ * Clears all table contents.
+ * Note: this operation uses SQL's TRUNCATE query which cannot be reverted in transactions.
+ */
+ async clearTable(tableName: string): Promise {
+ await this.query(`TRUNCATE TABLE ${this.escapePath(tableName)}`);
+ }
+
+ /**
+ * Removes all tables from the currently connected database.
+ */
+ async clearDatabase(): Promise {
+ const schemas: string[] = [];
+ this.connection.entityMetadatas
+ .filter((metadata) => metadata.schema)
+ .forEach((metadata) => {
+ const isSchemaExist = !!schemas.find((schema) => schema === metadata.schema);
+ if (!isSchemaExist) schemas.push(metadata.schema!);
+ });
+ schemas.push(this.driver.options.schema || 'current_schema()');
+ const schemaNamesString = schemas
+ .map((name) => {
+ return name === 'current_schema()' ? name : "'" + name + "'";
+ })
+ .join(', ');
+
+ const isAnotherTransactionActive = this.isTransactionActive;
+ if (!isAnotherTransactionActive) await this.startTransaction();
+ try {
+ const version = await this.getVersion();
+ // drop views
+ const selectViewDropsQuery =
+ `SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" ` +
+ `FROM "pg_views" WHERE "schemaname" IN (${schemaNamesString}) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')`;
+ const dropViewQueries: ObjectLiteral[] = await this.query(selectViewDropsQuery);
+ await Promise.all(dropViewQueries.map((q) => this.query(q['query'])));
+
+ // drop materialized views
+ // Note: materialized views introduced in Postgres 9.3
+ if (VersionUtils.isGreaterOrEqual(version, '9.3')) {
+ const selectMatViewDropsQuery =
+ `SELECT 'DROP MATERIALIZED VIEW IF EXISTS "' || schemaname || '"."' || matviewname || '" CASCADE;' as "query" ` +
+ `FROM "pg_matviews" WHERE "schemaname" IN (${schemaNamesString})`;
+ const dropMatViewQueries: ObjectLiteral[] = await this.query(selectMatViewDropsQuery);
+ await Promise.all(dropMatViewQueries.map((q) => this.query(q['query'])));
+ }
+
+ // ignore spatial_ref_sys; it's a special table supporting PostGIS
+ // TODO generalize this as this.driver.ignoreTables
+
+ // drop tables
+ const selectTableDropsQuery = `SELECT 'DROP TABLE IF EXISTS "' || schemaname || '"."' || tablename || '" CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (${schemaNamesString}) AND "tablename" NOT IN ('spatial_ref_sys')`;
+ const dropTableQueries: ObjectLiteral[] = await this.query(selectTableDropsQuery);
+ await Promise.all(dropTableQueries.map((q) => this.query(q['query'])));
+
+ // drop enum types
+ await this.dropEnumTypes(schemaNamesString);
+
+ if (!isAnotherTransactionActive) {
+ await this.commitTransaction();
+ }
+ } catch (error) {
+ try {
+ // we throw original error even if rollback thrown an error
+ if (!isAnotherTransactionActive) {
+ await this.rollbackTransaction();
+ }
+ } catch (rollbackError) {}
+ throw error;
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ protected async loadViews(viewNames?: string[]): Promise {
+ const hasTable = await this.hasTable(this.getTypeormMetadataTableName());
+
+ if (!hasTable) return [];
+
+ if (!viewNames) {
+ viewNames = [];
+ }
+
+ const currentDatabase = await this.getCurrentDatabase();
+ const currentSchema = await this.getCurrentSchema();
+ const viewsCondition =
+ viewNames.length === 0
+ ? '1=1'
+ : viewNames
+ .map((tableName) => this.driver.parseTableName(tableName))
+ .map(({ schema, tableName }) => {
+ if (!schema) {
+ schema = this.driver.options.schema || currentSchema;
+ }
+
+ return `("t"."schema" = '${schema}' AND "t"."name" = '${tableName}')`;
+ })
+ .join(' OR ');
+
+ const constraintsCondition =
+ viewNames.length === 0
+ ? '1=1'
+ : viewNames
+ .map((tableName) => this.driver.parseTableName(tableName))
+ .map(({ schema, tableName }) => {
+ if (!schema) {
+ schema = this.driver.options.schema || currentSchema;
+ }
+
+ return `("ns"."nspname" = '${schema}' AND "t"."relname" = '${tableName}')`;
+ })
+ .join(' OR ');
+
+ const indicesSql =
+ `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
+ `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
+ `"types"."typname" AS "type_name" ` +
+ `FROM "pg_class" "t" ` +
+ `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
+ `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid" AND "a"."attnum" = ANY ("ix"."indkey") ` +
+ `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
+ `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
+ `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
+ `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
+ `WHERE "t"."relkind" IN ('m') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`;
+
+ const query =
+ `SELECT "t".* FROM ${this.escapePath(this.getTypeormMetadataTableName())} "t" ` +
+ `INNER JOIN "pg_catalog"."pg_class" "c" ON "c"."relname" = "t"."name" ` +
+ `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "c"."relnamespace" AND "n"."nspname" = "t"."schema" ` +
+ `WHERE "t"."type" IN ('${MetadataTableType.VIEW}', '${
+ MetadataTableType.MATERIALIZED_VIEW
+ }') ${viewsCondition ? `AND (${viewsCondition})` : ''}`;
+
+ const dbViews = await this.query(query);
+ const dbIndices: ObjectLiteral[] = await this.query(indicesSql);
+ return dbViews.map((dbView: any) => {
+ // find index constraints of table, group them by constraint name and build TableIndex.
+ const tableIndexConstraints = OrmUtils.uniq(
+ dbIndices.filter((dbIndex) => {
+ return (
+ dbIndex['table_name'] === dbView['name'] && dbIndex['table_schema'] === dbView['schema']
+ );
+ }),
+ (dbIndex) => dbIndex['constraint_name'],
+ );
+ const view = new View();
+ const schema =
+ dbView['schema'] === currentSchema && !this.driver.options.schema
+ ? undefined
+ : dbView['schema'];
+ view.database = currentDatabase;
+ view.schema = dbView['schema'];
+ view.name = this.driver.buildTableName(dbView['name'], schema);
+ view.expression = dbView['value'];
+ view.materialized = dbView['type'] === MetadataTableType.MATERIALIZED_VIEW;
+ view.indices = tableIndexConstraints.map((constraint) => {
+ const indices = dbIndices.filter((index) => {
+ return (
+ index['table_schema'] === constraint['table_schema'] &&
+ index['table_name'] === constraint['table_name'] &&
+ index['constraint_name'] === constraint['constraint_name']
+ );
+ });
+ return new TableIndex({
+ view: view,
+ name: constraint['constraint_name'],
+ columnNames: indices.map((i) => i['column_name']),
+ isUnique: constraint['is_unique'] === 'TRUE',
+ where: constraint['condition'],
+ isFulltext: false,
+ });
+ });
+ return view;
+ });
+ }
+
+ /**
+ * Loads all tables (with given names) from the database and creates a Table from them.
+ */
+ protected async loadTables(tableNames?: string[]): Promise {
+ // if no tables given then no need to proceed
+ if (tableNames && tableNames.length === 0) {
+ return [];
+ }
+
+ const currentSchema = await this.getCurrentSchema();
+ const currentDatabase = await this.getCurrentDatabase();
+
+ const dbTables: {
+ table_schema: string;
+ table_name: string;
+ table_comment: string;
+ }[] = [];
+
+ if (!tableNames) {
+ const tablesSql = `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables"`;
+ dbTables.push(...(await this.query(tablesSql)));
+ } else {
+ const tablesCondition = tableNames
+ .map((tableName) => this.driver.parseTableName(tableName))
+ .map(({ schema, tableName }) => {
+ return `("table_schema" = '${
+ schema || currentSchema
+ }' AND "table_name" = '${tableName}')`;
+ })
+ .join(' OR ');
+
+ const tablesSql =
+ `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ` +
+ tablesCondition;
+ dbTables.push(...(await this.query(tablesSql)));
+ }
+
+ // if tables were not found in the db, no need to proceed
+ if (dbTables.length === 0) {
+ return [];
+ }
+
+ /**
+ * 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 columnsCondition = dbTables
+ .map(({ table_schema, table_name }) => {
+ return `("table_schema" = '${table_schema}' AND "table_name" = '${table_name}')`;
+ })
+ .join(' OR ');
+ const columnsSql =
+ `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 ` +
+ columnsCondition;
+
+ const constraintsCondition = dbTables
+ .map(({ table_schema, table_name }) => {
+ return `("ns"."nspname" = '${table_schema}' AND "t"."relname" = '${table_name}')`;
+ })
+ .join(' OR ');
+
+ const constraintsSql =
+ `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "cnst"."conname" AS "constraint_name", ` +
+ `pg_get_constraintdef("cnst"."oid") AS "expression", ` +
+ `CASE "cnst"."contype" WHEN 'p' THEN 'PRIMARY' WHEN 'u' THEN 'UNIQUE' WHEN 'c' THEN 'CHECK' WHEN 'x' THEN 'EXCLUDE' END AS "constraint_type", "a"."attname" AS "column_name" ` +
+ `FROM "pg_constraint" "cnst" ` +
+ `INNER JOIN "pg_class" "t" ON "t"."oid" = "cnst"."conrelid" ` +
+ `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace" ` +
+ `LEFT JOIN "pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = ANY ("cnst"."conkey") ` +
+ `WHERE "t"."relkind" IN ('r', 'p') AND (${constraintsCondition})`;
+
+ const indicesSql =
+ `SELECT "ns"."nspname" AS "table_schema", "t"."relname" AS "table_name", "i"."relname" AS "constraint_name", "a"."attname" AS "column_name", ` +
+ `CASE "ix"."indisunique" WHEN 't' THEN 'TRUE' ELSE'FALSE' END AS "is_unique", pg_get_expr("ix"."indpred", "ix"."indrelid") AS "condition", ` +
+ `"types"."typname" AS "type_name", "am"."amname" AS "index_type" ` +
+ `FROM "pg_class" "t" ` +
+ `INNER JOIN "pg_index" "ix" ON "ix"."indrelid" = "t"."oid" ` +
+ `INNER JOIN "pg_attribute" "a" ON "a"."attrelid" = "t"."oid" AND "a"."attnum" = ANY ("ix"."indkey") ` +
+ `INNER JOIN "pg_namespace" "ns" ON "ns"."oid" = "t"."relnamespace" ` +
+ `INNER JOIN "pg_class" "i" ON "i"."oid" = "ix"."indexrelid" ` +
+ `INNER JOIN "pg_type" "types" ON "types"."oid" = "a"."atttypid" ` +
+ `INNER JOIN "pg_am" "am" ON "i"."relam" = "am"."oid" ` +
+ `LEFT JOIN "pg_constraint" "cnst" ON "cnst"."conname" = "i"."relname" ` +
+ `WHERE "t"."relkind" IN ('r', 'p') AND "cnst"."contype" IS NULL AND (${constraintsCondition})`;
+
+ const foreignKeysCondition = dbTables
+ .map(({ table_schema, table_name }) => {
+ return `("ns"."nspname" = '${table_schema}' AND "cl"."relname" = '${table_name}')`;
+ })
+ .join(' OR ');
+
+ const hasRelispartitionColumn = await this.hasSupportForPartitionedTables();
+ const isPartitionCondition = hasRelispartitionColumn ? ` AND "cl"."relispartition" = 'f'` : '';
+
+ const foreignKeysSql =
+ `SELECT "con"."conname" AS "constraint_name", "con"."nspname" AS "table_schema", "con"."relname" AS "table_name", "att2"."attname" AS "column_name", ` +
+ `"ns"."nspname" AS "referenced_table_schema", "cl"."relname" AS "referenced_table_name", "att"."attname" AS "referenced_column_name", "con"."confdeltype" AS "on_delete", ` +
+ `"con"."confupdtype" AS "on_update", "con"."condeferrable" AS "deferrable", "con"."condeferred" AS "deferred" ` +
+ `FROM ( ` +
+ `SELECT UNNEST ("con1"."conkey") AS "parent", UNNEST ("con1"."confkey") AS "child", "con1"."confrelid", "con1"."conrelid", "con1"."conname", "con1"."contype", "ns"."nspname", ` +
+ `"cl"."relname", "con1"."condeferrable", ` +
+ `CASE WHEN "con1"."condeferred" THEN 'INITIALLY DEFERRED' ELSE 'INITIALLY IMMEDIATE' END as condeferred, ` +
+ `CASE "con1"."confdeltype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confdeltype", ` +
+ `CASE "con1"."confupdtype" WHEN 'a' THEN 'NO ACTION' WHEN 'r' THEN 'RESTRICT' WHEN 'c' THEN 'CASCADE' WHEN 'n' THEN 'SET NULL' WHEN 'd' THEN 'SET DEFAULT' END as "confupdtype" ` +
+ `FROM "pg_class" "cl" ` +
+ `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
+ `INNER JOIN "pg_constraint" "con1" ON "con1"."conrelid" = "cl"."oid" ` +
+ `WHERE "con1"."contype" = 'f' AND (${foreignKeysCondition}) ` +
+ `) "con" ` +
+ `INNER JOIN "pg_attribute" "att" ON "att"."attrelid" = "con"."confrelid" AND "att"."attnum" = "con"."child" ` +
+ `INNER JOIN "pg_class" "cl" ON "cl"."oid" = "con"."confrelid" ${isPartitionCondition}` +
+ `INNER JOIN "pg_namespace" "ns" ON "cl"."relnamespace" = "ns"."oid" ` +
+ `INNER JOIN "pg_attribute" "att2" ON "att2"."attrelid" = "con"."conrelid" AND "att2"."attnum" = "con"."parent"`;
+
+ const [dbColumns, dbConstraints, dbIndices, dbForeignKeys]: ObjectLiteral[][] =
+ await Promise.all([
+ this.query(columnsSql),
+ this.query(constraintsSql),
+ this.query(indicesSql),
+ this.query(foreignKeysSql),
+ ]);
+
+ // create tables for loaded tables
+ return Promise.all(
+ dbTables.map(async (dbTable) => {
+ const table = new Table();
+
+ const getSchemaFromKey = (dbObject: any, key: string) => {
+ return dbObject[key] === currentSchema &&
+ (!this.driver.options.schema || this.driver.options.schema === currentSchema)
+ ? undefined
+ : dbObject[key];
+ };
+ // We do not need to join schema name, when database is by default.
+ const schema = getSchemaFromKey(dbTable, 'table_schema');
+ table.database = currentDatabase;
+ table.schema = dbTable['table_schema'];
+ table.comment = dbTable['table_comment'];
+ table.name = this.driver.buildTableName(dbTable['table_name'], schema);
+
+ // create columns from the loaded columns
+ table.columns = await Promise.all(
+ dbColumns
+ .filter(
+ (dbColumn) =>
+ dbColumn['table_name'] === dbTable['table_name'] &&
+ dbColumn['table_schema'] === dbTable['table_schema'],
+ )
+ .map(async (dbColumn) => {
+ const columnConstraints = dbConstraints.filter((dbConstraint) => {
+ return (
+ dbConstraint['table_name'] === dbColumn['table_name'] &&
+ dbConstraint['table_schema'] === dbColumn['table_schema'] &&
+ dbConstraint['column_name'] === dbColumn['column_name']
+ );
+ });
+
+ const tableColumn = new TableColumn();
+ tableColumn.name = dbColumn['column_name'];
+ tableColumn.type = dbColumn['regtype'].toLowerCase();
+
+ if (
+ tableColumn.type === 'numeric' ||
+ tableColumn.type === 'numeric[]' ||
+ tableColumn.type === 'decimal' ||
+ tableColumn.type === 'float'
+ ) {
+ let numericPrecision = dbColumn['numeric_precision'];
+ let numericScale = dbColumn['numeric_scale'];
+ if (dbColumn['data_type'] === 'ARRAY') {
+ const numericSize = dbColumn['format_type'].match(
+ /^numeric\(([0-9]+),([0-9]+)\)\[\]$/,
+ );
+ if (numericSize) {
+ numericPrecision = +numericSize[1];
+ numericScale = +numericSize[2];
+ }
+ }
+ // If one of these properties was set, and another was not, Postgres sets '0' in to unspecified property
+ // we set 'undefined' in to unspecified property to avoid changing column on sync
+ if (
+ numericPrecision !== null &&
+ !this.isDefaultColumnPrecision(table, tableColumn, numericPrecision)
+ ) {
+ tableColumn.precision = numericPrecision;
+ } else if (
+ numericScale !== null &&
+ !this.isDefaultColumnScale(table, tableColumn, numericScale)
+ ) {
+ tableColumn.precision = undefined;
+ }
+ if (
+ numericScale !== null &&
+ !this.isDefaultColumnScale(table, tableColumn, numericScale)
+ ) {
+ tableColumn.scale = numericScale;
+ } else if (
+ numericPrecision !== null &&
+ !this.isDefaultColumnPrecision(table, tableColumn, numericPrecision)
+ ) {
+ tableColumn.scale = undefined;
+ }
+ }
+
+ if (
+ tableColumn.type === 'interval' ||
+ tableColumn.type === 'time without time zone' ||
+ tableColumn.type === 'time with time zone' ||
+ tableColumn.type === 'timestamp without time zone' ||
+ tableColumn.type === 'timestamp with time zone'
+ ) {
+ tableColumn.precision = !this.isDefaultColumnPrecision(
+ table,
+ tableColumn,
+ dbColumn['datetime_precision'],
+ )
+ ? dbColumn['datetime_precision']
+ : undefined;
+ }
+
+ // check if column has user-defined data type.
+ // NOTE: if ENUM type defined with "array:true" it comes with ARRAY type instead of USER-DEFINED
+ if (dbColumn['data_type'] === 'USER-DEFINED' || dbColumn['data_type'] === 'ARRAY') {
+ const { name } = await this.getUserDefinedTypeName(table, tableColumn);
+
+ // check if `enumName` is specified by user
+ const builtEnumName = this.buildEnumName(table, tableColumn, false, true);
+ const enumName = builtEnumName !== name ? name : undefined;
+
+ // check if type is ENUM
+ const sql =
+ `SELECT "e"."enumlabel" AS "value" FROM "pg_enum" "e" ` +
+ `INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" ` +
+ `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
+ `WHERE "n"."nspname" = '${dbTable['table_schema']}' AND "t"."typname" = '${
+ enumName || name
+ }'`;
+ const results: ObjectLiteral[] = await this.query(sql);
+
+ if (results.length) {
+ tableColumn.type = 'enum';
+ tableColumn.enum = results.map((result) => result['value']);
+ tableColumn.enumName = enumName;
+ }
+
+ if (dbColumn['data_type'] === 'ARRAY') {
+ tableColumn.isArray = true;
+ const type = tableColumn.type.replace('[]', '');
+ tableColumn.type = this.connection.driver.normalizeType({
+ type: type,
+ });
+ }
+ }
+
+ if (tableColumn.type === 'geometry' || tableColumn.type === 'geography') {
+ const sql =
+ `SELECT * FROM (` +
+ `SELECT "f_table_schema" "table_schema", "f_table_name" "table_name", ` +
+ `"f_${tableColumn.type}_column" "column_name", "srid", "type" ` +
+ `FROM "${tableColumn.type}_columns"` +
+ `) AS _ ` +
+ `WHERE "column_name" = '${dbColumn['column_name']}' AND ` +
+ `"table_schema" = '${dbColumn['table_schema']}' AND ` +
+ `"table_name" = '${dbColumn['table_name']}'`;
+
+ const results: ObjectLiteral[] = await this.query(sql);
+
+ if (results.length > 0) {
+ tableColumn.spatialFeatureType = results[0].type;
+ tableColumn.srid = results[0].srid;
+ }
+ }
+
+ // check only columns that have length property
+ 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';
+
+ const primaryConstraint = columnConstraints.find(
+ (constraint) => constraint['constraint_type'] === 'PRIMARY',
+ );
+ if (primaryConstraint) {
+ tableColumn.isPrimary = true;
+ // find another columns involved in primary key constraint
+ const anotherPrimaryConstraints = dbConstraints.filter(
+ (constraint) =>
+ constraint['table_name'] === dbColumn['table_name'] &&
+ constraint['table_schema'] === dbColumn['table_schema'] &&
+ constraint['column_name'] !== dbColumn['column_name'] &&
+ constraint['constraint_type'] === 'PRIMARY',
+ );
+
+ // collect all column names
+ const columnNames = anotherPrimaryConstraints.map(
+ (constraint) => constraint['column_name'],
+ );
+ columnNames.push(dbColumn['column_name']);
+
+ // build default primary key constraint name
+ const pkName = this.connection.namingStrategy.primaryKeyName(table, columnNames);
+
+ // if primary key has user-defined constraint name, write it in table column
+ if (primaryConstraint['constraint_name'] !== pkName) {
+ tableColumn.primaryKeyConstraintName = primaryConstraint['constraint_name'];
+ }
+ }
+
+ const uniqueConstraints = columnConstraints.filter(
+ (constraint) => constraint['constraint_type'] === 'UNIQUE',
+ );
+ const isConstraintComposite = uniqueConstraints.every((uniqueConstraint) => {
+ return dbConstraints.some(
+ (dbConstraint) =>
+ dbConstraint['constraint_type'] === 'UNIQUE' &&
+ dbConstraint['constraint_name'] === uniqueConstraint['constraint_name'] &&
+ dbConstraint['column_name'] !== dbColumn['column_name'],
+ );
+ });
+ tableColumn.isUnique = uniqueConstraints.length > 0 && !isConstraintComposite;
+
+ 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)`;
+
+ const defaultWithoutQuotes = dbColumn['column_default'].replace(/"/g, '');
+
+ if (
+ defaultWithoutQuotes === serialDefaultName ||
+ defaultWithoutQuotes === serialDefaultPath
+ ) {
+ tableColumn.isGenerated = true;
+ tableColumn.generationStrategy = 'increment';
+ } else if (
+ dbColumn['column_default'] === 'gen_random_uuid()' ||
+ /^uuid_generate_v\d\(\)/.test(dbColumn['column_default'])
+ ) {
+ if (tableColumn.type === 'uuid') {
+ tableColumn.isGenerated = true;
+ tableColumn.generationStrategy = 'uuid';
+ } else {
+ tableColumn.default = dbColumn['column_default'];
+ }
+ } else if (
+ dbColumn['column_default'] === 'now()' ||
+ dbColumn['column_default'].indexOf("'now'::text") !== -1
+ ) {
+ tableColumn.default = dbColumn['column_default'];
+ } else {
+ tableColumn.default = dbColumn['column_default'].replace(/::[\w\s.[\]\-"]+/g, '');
+ tableColumn.default = tableColumn.default.replace(/^(-?\d+)$/, "'$1'");
+ }
+ }
+
+ if (dbColumn['is_generated'] === 'ALWAYS' && dbColumn['generation_expression']) {
+ // In postgres there is no VIRTUAL generated column type
+ tableColumn.generatedType = 'STORED';
+ // We cannot relay on information_schema.columns.generation_expression, because it is formatted different.
+ const asExpressionQuery = this.selectTypeormMetadataSql({
+ database: currentDatabase,
+ schema: dbTable['table_schema'],
+ table: dbTable['table_name'],
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: tableColumn.name,
+ });
+
+ const results = await this.query(
+ asExpressionQuery.query,
+ asExpressionQuery.parameters,
+ );
+ if (results[0] && results[0].value) {
+ tableColumn.asExpression = results[0].value;
+ } else {
+ tableColumn.asExpression = '';
+ }
+ }
+
+ tableColumn.comment = dbColumn['description'] ? dbColumn['description'] : undefined;
+ if (dbColumn['character_set_name'])
+ tableColumn.charset = dbColumn['character_set_name'];
+ if (dbColumn['collation_name']) tableColumn.collation = dbColumn['collation_name'];
+ return tableColumn;
+ }),
+ );
+
+ // find unique constraints of table, group them by constraint name and build TableUnique.
+ const tableUniqueConstraints = OrmUtils.uniq(
+ dbConstraints.filter((dbConstraint) => {
+ return (
+ dbConstraint['table_name'] === dbTable['table_name'] &&
+ dbConstraint['table_schema'] === dbTable['table_schema'] &&
+ dbConstraint['constraint_type'] === 'UNIQUE'
+ );
+ }),
+ (dbConstraint) => dbConstraint['constraint_name'],
+ );
+
+ table.uniques = tableUniqueConstraints.map((constraint) => {
+ const uniques = dbConstraints.filter(
+ (dbC) => dbC['constraint_name'] === constraint['constraint_name'],
+ );
+ return new TableUnique({
+ name: constraint['constraint_name'],
+ columnNames: uniques.map((u) => u['column_name']),
+ deferrable: constraint['deferrable'] ? constraint['deferred'] : undefined,
+ });
+ });
+
+ // find check constraints of table, group them by constraint name and build TableCheck.
+ const tableCheckConstraints = OrmUtils.uniq(
+ dbConstraints.filter((dbConstraint) => {
+ return (
+ dbConstraint['table_name'] === dbTable['table_name'] &&
+ dbConstraint['table_schema'] === dbTable['table_schema'] &&
+ dbConstraint['constraint_type'] === 'CHECK'
+ );
+ }),
+ (dbConstraint) => dbConstraint['constraint_name'],
+ );
+
+ table.checks = tableCheckConstraints.map((constraint) => {
+ const checks = dbConstraints.filter(
+ (dbC) => dbC['constraint_name'] === constraint['constraint_name'],
+ );
+ return new TableCheck({
+ name: constraint['constraint_name'],
+ columnNames: checks.map((c) => c['column_name']),
+ expression: constraint['expression'].replace(/^\s*CHECK\s*\((.*)\)\s*$/i, '$1'),
+ });
+ });
+
+ // find exclusion constraints of table, group them by constraint name and build TableExclusion.
+ const tableExclusionConstraints = OrmUtils.uniq(
+ dbConstraints.filter((dbConstraint) => {
+ return (
+ dbConstraint['table_name'] === dbTable['table_name'] &&
+ dbConstraint['table_schema'] === dbTable['table_schema'] &&
+ dbConstraint['constraint_type'] === 'EXCLUDE'
+ );
+ }),
+ (dbConstraint) => dbConstraint['constraint_name'],
+ );
+
+ table.exclusions = tableExclusionConstraints.map((constraint) => {
+ return new TableExclusion({
+ name: constraint['constraint_name'],
+ expression: constraint['expression'].substring(8), // trim EXCLUDE from start of expression
+ });
+ });
+
+ // find foreign key constraints of table, group them by constraint name and build TableForeignKey.
+ const tableForeignKeyConstraints = OrmUtils.uniq(
+ dbForeignKeys.filter((dbForeignKey) => {
+ return (
+ dbForeignKey['table_name'] === dbTable['table_name'] &&
+ dbForeignKey['table_schema'] === dbTable['table_schema']
+ );
+ }),
+ (dbForeignKey) => dbForeignKey['constraint_name'],
+ );
+
+ table.foreignKeys = tableForeignKeyConstraints.map((dbForeignKey) => {
+ const foreignKeys = dbForeignKeys.filter(
+ (dbFk) => dbFk['constraint_name'] === dbForeignKey['constraint_name'],
+ );
+
+ // if referenced table located in currently used schema, we don't need to concat schema name to table name.
+ const schema = getSchemaFromKey(dbForeignKey, 'referenced_table_schema');
+ const referencedTableName = this.driver.buildTableName(
+ dbForeignKey['referenced_table_name'],
+ schema,
+ );
+
+ return new TableForeignKey({
+ name: dbForeignKey['constraint_name'],
+ columnNames: foreignKeys.map((dbFk) => dbFk['column_name']),
+ referencedSchema: dbForeignKey['referenced_table_schema'],
+ referencedTableName: referencedTableName,
+ referencedColumnNames: foreignKeys.map((dbFk) => dbFk['referenced_column_name']),
+ onDelete: dbForeignKey['on_delete'],
+ onUpdate: dbForeignKey['on_update'],
+ deferrable: dbForeignKey['deferrable'] ? dbForeignKey['deferred'] : undefined,
+ });
+ });
+
+ // find index constraints of table, group them by constraint name and build TableIndex.
+ const tableIndexConstraints = OrmUtils.uniq(
+ dbIndices.filter((dbIndex) => {
+ return (
+ dbIndex['table_name'] === dbTable['table_name'] &&
+ dbIndex['table_schema'] === dbTable['table_schema']
+ );
+ }),
+ (dbIndex) => dbIndex['constraint_name'],
+ );
+
+ table.indices = tableIndexConstraints.map((constraint) => {
+ const indices = dbIndices.filter((index) => {
+ return (
+ index['table_schema'] === constraint['table_schema'] &&
+ index['table_name'] === constraint['table_name'] &&
+ index['constraint_name'] === constraint['constraint_name']
+ );
+ });
+ return new TableIndex({
+ table: table,
+ name: constraint['constraint_name'],
+ columnNames: indices.map((i) => i['column_name']),
+ isUnique: constraint['is_unique'] === 'TRUE',
+ where: constraint['condition'],
+ isSpatial: constraint['index_type'] === 'gist',
+ isFulltext: false,
+ });
+ });
+
+ return table;
+ }),
+ );
+ }
+
+ /**
+ * Builds create table sql.
+ */
+ protected createTableSql(table: Table, createForeignKeys?: boolean): Query {
+ const columnDefinitions = table.columns
+ .map((column) => this.buildCreateColumnSql(table, column))
+ .join(', ');
+ let sql = `CREATE TABLE ${this.escapePath(table)} (${columnDefinitions}`;
+
+ table.columns
+ .filter((column) => column.isUnique)
+ .forEach((column) => {
+ const isUniqueExist = table.uniques.some(
+ (unique) => unique.columnNames.length === 1 && unique.columnNames[0] === column.name,
+ );
+ if (!isUniqueExist)
+ table.uniques.push(
+ new TableUnique({
+ name: this.connection.namingStrategy.uniqueConstraintName(table, [column.name]),
+ columnNames: [column.name],
+ }),
+ );
+ });
+
+ if (table.uniques.length > 0) {
+ const uniquesSql = table.uniques
+ .map((unique) => {
+ const uniqueName = unique.name
+ ? unique.name
+ : this.connection.namingStrategy.uniqueConstraintName(table, unique.columnNames);
+ const columnNames = unique.columnNames.map((columnName) => `"${columnName}"`).join(', ');
+ let constraint = `CONSTRAINT "${uniqueName}" UNIQUE (${columnNames})`;
+ if (unique.deferrable) constraint += ` DEFERRABLE ${unique.deferrable}`;
+ return constraint;
+ })
+ .join(', ');
+
+ sql += `, ${uniquesSql}`;
+ }
+
+ if (table.checks.length > 0) {
+ const checksSql = table.checks
+ .map((check) => {
+ const checkName = check.name
+ ? check.name
+ : this.connection.namingStrategy.checkConstraintName(table, check.expression!);
+ return `CONSTRAINT "${checkName}" CHECK (${check.expression})`;
+ })
+ .join(', ');
+
+ sql += `, ${checksSql}`;
+ }
+
+ if (table.exclusions.length > 0) {
+ const exclusionsSql = table.exclusions
+ .map((exclusion) => {
+ const exclusionName = exclusion.name
+ ? exclusion.name
+ : this.connection.namingStrategy.exclusionConstraintName(table, exclusion.expression!);
+ return `CONSTRAINT "${exclusionName}" EXCLUDE ${exclusion.expression}`;
+ })
+ .join(', ');
+
+ sql += `, ${exclusionsSql}`;
+ }
+
+ if (table.foreignKeys.length > 0 && createForeignKeys) {
+ const foreignKeysSql = table.foreignKeys
+ .map((fk) => {
+ const columnNames = fk.columnNames.map((columnName) => `"${columnName}"`).join(', ');
+ if (!fk.name)
+ fk.name = this.connection.namingStrategy.foreignKeyName(
+ table,
+ fk.columnNames,
+ this.getTablePath(fk),
+ fk.referencedColumnNames,
+ );
+
+ const referencedColumnNames = fk.referencedColumnNames
+ .map((columnName) => `"${columnName}"`)
+ .join(', ');
+
+ let constraint = `CONSTRAINT "${
+ fk.name
+ }" FOREIGN KEY (${columnNames}) REFERENCES ${this.escapePath(
+ this.getTablePath(fk),
+ )} (${referencedColumnNames})`;
+ if (fk.onDelete) constraint += ` ON DELETE ${fk.onDelete}`;
+ if (fk.onUpdate) constraint += ` ON UPDATE ${fk.onUpdate}`;
+ if (fk.deferrable) constraint += ` DEFERRABLE ${fk.deferrable}`;
+
+ return constraint;
+ })
+ .join(', ');
+
+ sql += `, ${foreignKeysSql}`;
+ }
+
+ const primaryColumns = table.columns.filter((column) => column.isPrimary);
+ if (primaryColumns.length > 0) {
+ const primaryKeyName = primaryColumns[0].primaryKeyConstraintName
+ ? primaryColumns[0].primaryKeyConstraintName
+ : this.connection.namingStrategy.primaryKeyName(
+ table,
+ primaryColumns.map((column) => column.name),
+ );
+
+ const columnNames = primaryColumns.map((column) => `"${column.name}"`).join(', ');
+ sql += `, CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNames})`;
+ }
+
+ sql += `)`;
+
+ table.columns
+ .filter((it) => it.comment)
+ .forEach(
+ (it) =>
+ (sql += `; COMMENT ON COLUMN ${this.escapePath(table)}."${
+ it.name
+ }" IS ${this.escapeComment(it.comment)}`),
+ );
+
+ return new Query(sql);
+ }
+
+ /**
+ * Loads Postgres version.
+ */
+ protected async getVersion(): Promise {
+ const result = await this.query(`SELECT version()`);
+ return result[0]['version'].replace(/^PostgreSQL ([\d.]+) .*$/, '$1');
+ }
+
+ /**
+ * Builds drop table sql.
+ */
+ protected dropTableSql(tableOrPath: Table | string): Query {
+ return new Query(`DROP TABLE ${this.escapePath(tableOrPath)}`);
+ }
+
+ protected createViewSql(view: View): Query {
+ const materializedClause = view.materialized ? 'MATERIALIZED ' : '';
+ const viewName = this.escapePath(view);
+
+ if (typeof view.expression === 'string') {
+ return new Query(`CREATE ${materializedClause}VIEW ${viewName} AS ${view.expression}`);
+ } else {
+ return new Query(
+ `CREATE ${materializedClause}VIEW ${viewName} AS ${view
+ .expression(this.connection)
+ .getQuery()}`,
+ );
+ }
+ }
+
+ protected async insertViewDefinitionSql(view: View): Promise {
+ const currentSchema = await this.getCurrentSchema();
+
+ let { schema, tableName: name } = this.driver.parseTableName(view);
+
+ if (!schema) {
+ schema = currentSchema;
+ }
+
+ const type = view.materialized ? MetadataTableType.MATERIALIZED_VIEW : MetadataTableType.VIEW;
+ const expression =
+ typeof view.expression === 'string'
+ ? view.expression.trim()
+ : view.expression(this.connection).getQuery();
+ return this.insertTypeormMetadataSql({
+ type,
+ schema,
+ name,
+ value: expression,
+ });
+ }
+
+ /**
+ * Builds drop view sql.
+ */
+ protected dropViewSql(view: View): Query {
+ const materializedClause = view.materialized ? 'MATERIALIZED ' : '';
+ return new Query(`DROP ${materializedClause}VIEW ${this.escapePath(view)}`);
+ }
+
+ /**
+ * Builds remove view sql.
+ */
+ protected async deleteViewDefinitionSql(view: View): Promise {
+ const currentSchema = await this.getCurrentSchema();
+
+ let { schema, tableName: name } = this.driver.parseTableName(view);
+
+ if (!schema) {
+ schema = currentSchema;
+ }
+
+ const type = view.materialized ? MetadataTableType.MATERIALIZED_VIEW : MetadataTableType.VIEW;
+ return this.deleteTypeormMetadataSql({ type, schema, name });
+ }
+
+ /**
+ * Drops ENUM type from given schemas.
+ */
+ protected async dropEnumTypes(schemaNames: string): Promise {
+ const selectDropsQuery =
+ `SELECT 'DROP TYPE IF EXISTS "' || n.nspname || '"."' || t.typname || '" CASCADE;' as "query" FROM "pg_type" "t" ` +
+ `INNER JOIN "pg_enum" "e" ON "e"."enumtypid" = "t"."oid" ` +
+ `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
+ `WHERE "n"."nspname" IN (${schemaNames}) GROUP BY "n"."nspname", "t"."typname"`;
+ const dropQueries: ObjectLiteral[] = await this.query(selectDropsQuery);
+ await Promise.all(dropQueries.map((q) => this.query(q['query'])));
+ }
+
+ /**
+ * Checks if enum with the given name exist in the database.
+ */
+ protected async hasEnumType(table: Table, column: TableColumn): Promise {
+ let { schema } = this.driver.parseTableName(table);
+
+ if (!schema) {
+ schema = await this.getCurrentSchema();
+ }
+
+ const enumName = this.buildEnumName(table, column, false, true);
+ const sql =
+ `SELECT "n"."nspname", "t"."typname" FROM "pg_type" "t" ` +
+ `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` +
+ `WHERE "n"."nspname" = $1 AND "t"."typname" = $2`;
+ const result = await this.query(sql, [schema, enumName]);
+ return result.length ? true : false;
+ }
+
+ /**
+ * Builds create ENUM type sql.
+ */
+ protected createEnumTypeSql(table: Table, column: TableColumn, enumName?: string): Query {
+ if (!enumName) enumName = this.buildEnumName(table, column);
+ const enumValues = column.enum!.map((value) => `'${value.replace("'", "''")}'`).join(', ');
+ return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`);
+ }
+
+ /**
+ * Builds create ENUM type sql.
+ */
+ protected dropEnumTypeSql(table: Table, column: TableColumn, enumName?: string): Query {
+ if (!enumName) enumName = this.buildEnumName(table, column);
+ return new Query(`DROP TYPE ${enumName}`);
+ }
+
+ /**
+ * Builds create index sql.
+ */
+ protected createIndexSql(table: Table, index: TableIndex): Query {
+ const columns = index.columnNames.map((columnName) => `"${columnName}"`).join(', ');
+ return new Query(
+ `CREATE ${index.isUnique ? 'UNIQUE ' : ''}INDEX${
+ index.isConcurrent ? ' CONCURRENTLY' : ''
+ } "${index.name}" ON ${this.escapePath(table)} ${
+ index.isSpatial ? 'USING GiST ' : ''
+ }(${columns}) ${index.where ? 'WHERE ' + index.where : ''}`,
+ );
+ }
+
+ /**
+ * Builds create view index sql.
+ */
+ protected createViewIndexSql(view: View, index: TableIndex): Query {
+ const columns = index.columnNames.map((columnName) => `"${columnName}"`).join(', ');
+ return new Query(
+ `CREATE ${index.isUnique ? 'UNIQUE ' : ''}INDEX "${
+ index.name
+ }" ON ${this.escapePath(view)} (${columns}) ${index.where ? 'WHERE ' + index.where : ''}`,
+ );
+ }
+
+ /**
+ * Builds drop index sql.
+ */
+ protected dropIndexSql(table: Table | View, indexOrName: TableIndex | string): Query {
+ const indexName = InstanceChecker.isTableIndex(indexOrName) ? indexOrName.name : indexOrName;
+ const concurrent = InstanceChecker.isTableIndex(indexOrName) ? indexOrName.isConcurrent : false;
+ const { schema } = this.driver.parseTableName(table);
+ return schema
+ ? new Query(`DROP INDEX ${concurrent ? 'CONCURRENTLY ' : ''}"${schema}"."${indexName}"`)
+ : new Query(`DROP INDEX ${concurrent ? 'CONCURRENTLY ' : ''}"${indexName}"`);
+ }
+
+ /**
+ * Builds create primary key sql.
+ */
+ protected createPrimaryKeySql(
+ table: Table,
+ columnNames: string[],
+ constraintName?: string,
+ ): Query {
+ const primaryKeyName = constraintName
+ ? constraintName
+ : this.connection.namingStrategy.primaryKeyName(table, columnNames);
+
+ const columnNamesString = columnNames.map((columnName) => `"${columnName}"`).join(', ');
+
+ return new Query(
+ `ALTER TABLE ${this.escapePath(
+ table,
+ )} ADD CONSTRAINT "${primaryKeyName}" PRIMARY KEY (${columnNamesString})`,
+ );
+ }
+
+ /**
+ * Builds drop primary key sql.
+ */
+ protected dropPrimaryKeySql(table: Table): Query {
+ if (!table.primaryColumns.length) throw new TypeORMError(`Table ${table} has no primary keys.`);
+
+ const columnNames = table.primaryColumns.map((column) => column.name);
+ const constraintName = table.primaryColumns[0].primaryKeyConstraintName;
+ const primaryKeyName = constraintName
+ ? constraintName
+ : this.connection.namingStrategy.primaryKeyName(table, columnNames);
+
+ return new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${primaryKeyName}"`);
+ }
+
+ /**
+ * Builds create unique constraint sql.
+ */
+ protected createUniqueConstraintSql(table: Table, uniqueConstraint: TableUnique): Query {
+ const columnNames = uniqueConstraint.columnNames.map((column) => `"` + column + `"`).join(', ');
+ let sql = `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ uniqueConstraint.name
+ }" UNIQUE (${columnNames})`;
+ if (uniqueConstraint.deferrable) sql += ` DEFERRABLE ${uniqueConstraint.deferrable}`;
+ return new Query(sql);
+ }
+
+ /**
+ * Builds drop unique constraint sql.
+ */
+ protected dropUniqueConstraintSql(table: Table, uniqueOrName: TableUnique | string): Query {
+ const uniqueName = InstanceChecker.isTableUnique(uniqueOrName)
+ ? uniqueOrName.name
+ : uniqueOrName;
+ return new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${uniqueName}"`);
+ }
+
+ /**
+ * Builds create check constraint sql.
+ */
+ protected createCheckConstraintSql(table: Table, checkConstraint: TableCheck): Query {
+ return new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ checkConstraint.name
+ }" CHECK (${checkConstraint.expression})`,
+ );
+ }
+
+ /**
+ * Builds drop check constraint sql.
+ */
+ protected dropCheckConstraintSql(table: Table, checkOrName: TableCheck | string): Query {
+ const checkName = InstanceChecker.isTableCheck(checkOrName) ? checkOrName.name : checkOrName;
+ return new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${checkName}"`);
+ }
+
+ /**
+ * Builds create exclusion constraint sql.
+ */
+ protected createExclusionConstraintSql(table: Table, exclusionConstraint: TableExclusion): Query {
+ return new Query(
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ exclusionConstraint.name
+ }" EXCLUDE ${exclusionConstraint.expression}`,
+ );
+ }
+
+ /**
+ * Builds drop exclusion constraint sql.
+ */
+ protected dropExclusionConstraintSql(
+ table: Table,
+ exclusionOrName: TableExclusion | string,
+ ): Query {
+ const exclusionName = InstanceChecker.isTableExclusion(exclusionOrName)
+ ? exclusionOrName.name
+ : exclusionOrName;
+ return new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${exclusionName}"`);
+ }
+
+ /**
+ * Builds create foreign key sql.
+ */
+ protected createForeignKeySql(table: Table, foreignKey: TableForeignKey): Query {
+ const columnNames = foreignKey.columnNames.map((column) => `"` + column + `"`).join(', ');
+ const referencedColumnNames = foreignKey.referencedColumnNames
+ .map((column) => `"` + column + `"`)
+ .join(',');
+ let sql =
+ `ALTER TABLE ${this.escapePath(table)} ADD CONSTRAINT "${
+ foreignKey.name
+ }" FOREIGN KEY (${columnNames}) ` +
+ `REFERENCES ${this.escapePath(this.getTablePath(foreignKey))}(${referencedColumnNames})`;
+ if (foreignKey.onDelete) sql += ` ON DELETE ${foreignKey.onDelete}`;
+ if (foreignKey.onUpdate) sql += ` ON UPDATE ${foreignKey.onUpdate}`;
+ if (foreignKey.deferrable) sql += ` DEFERRABLE ${foreignKey.deferrable}`;
+
+ return new Query(sql);
+ }
+
+ /**
+ * Builds drop foreign key sql.
+ */
+ protected dropForeignKeySql(table: Table, foreignKeyOrName: TableForeignKey | string): Query {
+ const foreignKeyName = InstanceChecker.isTableForeignKey(foreignKeyOrName)
+ ? foreignKeyOrName.name
+ : foreignKeyOrName;
+ return new Query(`ALTER TABLE ${this.escapePath(table)} DROP CONSTRAINT "${foreignKeyName}"`);
+ }
+
+ /**
+ * Builds sequence name from given table and column.
+ */
+ protected buildSequenceName(table: Table, columnOrName: TableColumn | string): string {
+ const { tableName } = this.driver.parseTableName(table);
+
+ const columnName = InstanceChecker.isTableColumn(columnOrName)
+ ? columnOrName.name
+ : columnOrName;
+
+ let seqName = `${tableName}_${columnName}_seq`;
+
+ if (seqName.length > this.connection.driver.maxAliasLength!) {
+ // note doesn't yet handle corner cases where .length differs from number of UTF-8 bytes
+ seqName = `${tableName.substring(0, 29)}_${columnName.substring(
+ 0,
+ Math.max(29, 63 - table.name.length - 5),
+ )}_seq`;
+ }
+
+ return seqName;
+ }
+
+ protected buildSequencePath(table: Table, columnOrName: TableColumn | string): string {
+ const { schema } = this.driver.parseTableName(table);
+
+ return schema
+ ? `${schema}.${this.buildSequenceName(table, columnOrName)}`
+ : this.buildSequenceName(table, columnOrName);
+ }
+
+ /**
+ * Builds ENUM type name from given table and column.
+ */
+ protected buildEnumName(
+ table: Table,
+ column: TableColumn,
+ withSchema: boolean = true,
+ disableEscape?: boolean,
+ toOld?: boolean,
+ ): string {
+ const { schema, tableName } = this.driver.parseTableName(table);
+ let enumName = column.enumName
+ ? column.enumName
+ : `${tableName}_${column.name.toLowerCase()}_enum`;
+ if (schema && withSchema) enumName = `${schema}.${enumName}`;
+ if (toOld) enumName = enumName + '_old';
+ return enumName
+ .split('.')
+ .map((i) => {
+ return disableEscape ? i : `"${i.replace(/"/g, '""')}"`;
+ })
+ .join('.');
+ }
+
+ protected async getUserDefinedTypeName(table: Table, column: TableColumn) {
+ let { schema, tableName: name } = this.driver.parseTableName(table);
+
+ if (!schema) {
+ schema = await this.getCurrentSchema();
+ }
+
+ const result = await this.query(
+ `SELECT "udt_schema", "udt_name" ` +
+ `FROM "information_schema"."columns" WHERE "table_schema" = $1 AND "table_name" = $2 AND "column_name"=$3`,
+ [schema, name, column.name],
+ );
+
+ // docs: https://www.postgresql.org/docs/current/xtypes.html
+ // When you define a new base type, PostgreSQL automatically provides support for arrays of that type.
+ // The array type typically has the same name as the base type with the underscore character (_) prepended.
+ // ----
+ // so, we must remove this underscore character from enum type name
+ let udtName = result[0]['udt_name'];
+ if (udtName.indexOf('_') === 0) {
+ udtName = udtName.substr(1, udtName.length);
+ }
+ return {
+ schema: result[0]['udt_schema'],
+ name: udtName,
+ };
+ }
+
+ /**
+ * Escapes a given comment so it's safe to include in a query.
+ */
+ protected escapeComment(comment?: string) {
+ if (!comment || comment.length === 0) {
+ return 'NULL';
+ }
+
+ comment = comment.replace(/'/g, "''").replace(/\u0000/g, ''); // Null bytes aren't allowed in comments
+
+ return `'${comment}'`;
+ }
+
+ /**
+ * Escapes given table or view path.
+ */
+ protected escapePath(target: Table | View | string): string {
+ const { schema, tableName } = this.driver.parseTableName(target);
+
+ if (schema && schema !== this.driver.searchSchema) {
+ return `"${schema}"."${tableName.replace(/"/g, '""')}"`;
+ }
+
+ return `"${tableName.replace(/"/g, '""')}"`;
+ }
+
+ /**
+ * Get the table name with table schema
+ * Note: Without ' or "
+ */
+ protected async getTableNameWithSchema(target: Table | string) {
+ const tableName = InstanceChecker.isTable(target) ? target.name : target;
+ if (tableName.indexOf('.') === -1) {
+ const schemaResult = await this.query(`SELECT current_schema()`);
+ const schema = schemaResult[0]['current_schema'];
+ return `${schema}.${tableName}`;
+ } else {
+ return `${tableName.split('.')[0]}.${tableName.split('.')[1]}`;
+ }
+ }
+
+ /**
+ * Builds a query for create column.
+ */
+ protected buildCreateColumnSql(table: Table, column: TableColumn) {
+ let c = '"' + column.name + '"';
+ if (column.isGenerated === true && column.generationStrategy !== 'uuid') {
+ if (column.generationStrategy === 'identity') {
+ // Postgres 10+ Identity generated column
+ 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';
+ 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);
+ if (column.isArray) c += ' array';
+ } else if (!column.isGenerated || column.type === 'uuid') {
+ c += ' ' + this.connection.driver.createFullType(column);
+ }
+
+ // Postgres only supports the stored generated column type
+ if (column.generatedType === 'STORED' && column.asExpression) {
+ c += ` GENERATED ALWAYS AS (${column.asExpression}) STORED`;
+ }
+
+ if (column.charset) c += ' CHARACTER SET "' + column.charset + '"';
+ if (column.collation) c += ' COLLATE "' + column.collation + '"';
+ if (column.isNullable !== true) c += ' NOT NULL';
+ if (column.default !== undefined && column.default !== null) c += ' DEFAULT ' + column.default;
+ if (column.isGenerated && column.generationStrategy === 'uuid' && !column.default)
+ c += ` DEFAULT ${this.driver.uuidGenerator}`;
+
+ return c;
+ }
+
+ /**
+ * Checks if the PostgreSQL server has support for partitioned tables
+ */
+ protected async hasSupportForPartitionedTables() {
+ const result = await this.query(
+ `SELECT TRUE FROM information_schema.columns WHERE table_name = 'pg_class' and column_name = 'relispartition'`,
+ );
+ return result.length ? true : false;
+ }
+
+ /**
+ * Change table comment.
+ */
+ async changeTableComment(tableOrName: Table | string, newComment?: string): Promise {
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+
+ newComment = this.escapeComment(newComment);
+ const comment = this.escapeComment(table.comment);
+
+ if (newComment === comment) {
+ return;
+ }
+
+ const newTable = table.clone();
+
+ upQueries.push(new Query(`COMMENT ON TABLE ${this.escapePath(newTable)} IS ${newComment}`));
+
+ downQueries.push(new Query(`COMMENT ON TABLE ${this.escapePath(table)} IS ${comment}`));
+
+ await this.executeQueries(upQueries, downQueries);
+
+ table.comment = newTable.comment;
+ this.replaceCachedTable(table, newTable);
+ }
+}
diff --git a/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteConnectionOptions.ts b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteConnectionOptions.ts
new file mode 100644
index 00000000000..a4b4a9031f9
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteConnectionOptions.ts
@@ -0,0 +1,31 @@
+import { BaseDataSourceOptions } from '../../data-source/BaseDataSourceOptions';
+
+export interface AbstractSqliteConnectionOptions extends BaseDataSourceOptions {
+ /**
+ * Database type.
+ */
+ readonly type: 'sqlite' | 'sqlite-pooled';
+
+ /**
+ * Storage type or path to the storage.
+ */
+ readonly database: string;
+
+ /**
+ * The driver object
+ * This defaults to require("sqlite3")
+ */
+ readonly driver?: any;
+
+ /**
+ * Encryption key for for SQLCipher.
+ */
+ readonly key?: string;
+
+ /**
+ * Enables WAL mode. By default its disabled.
+ *
+ * @see https://www.sqlite.org/wal.html
+ */
+ readonly enableWAL?: boolean;
+}
diff --git a/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteDriver.ts b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteDriver.ts
new file mode 100644
index 00000000000..4d96124fe11
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteDriver.ts
@@ -0,0 +1,851 @@
+import { Driver } from '../Driver';
+import { ObjectLiteral } from '../../common/ObjectLiteral';
+import { ColumnMetadata } from '../../metadata/ColumnMetadata';
+import { DateUtils } from '../../util/DateUtils';
+import { DataSource } from '../../data-source/DataSource';
+import { RdbmsSchemaBuilder } from '../../schema-builder/RdbmsSchemaBuilder';
+import { CteCapabilities } from '../types/CteCapabilities';
+import { MappedColumnTypes } from '../types/MappedColumnTypes';
+import { ColumnType } from '../types/ColumnTypes';
+import { QueryRunner } from '../../query-runner/QueryRunner';
+import { DataTypeDefaults } from '../types/DataTypeDefaults';
+import { TableColumn } from '../../schema-builder/table/TableColumn';
+import { BaseDataSourceOptions } from '../../data-source/BaseDataSourceOptions';
+import { EntityMetadata } from '../../metadata/EntityMetadata';
+import { OrmUtils } from '../../util/OrmUtils';
+import { ApplyValueTransformers } from '../../util/ApplyValueTransformers';
+import { ReplicationMode } from '../types/ReplicationMode';
+import { DriverUtils } from '../DriverUtils';
+import { TypeORMError } from '../../error';
+import { Table } from '../../schema-builder/table/Table';
+import { View } from '../../schema-builder/view/View';
+import { TableForeignKey } from '../../schema-builder/table/TableForeignKey';
+import { InstanceChecker } from '../../util/InstanceChecker';
+import { UpsertType } from '../types/UpsertType';
+
+type DatabasesMap = Record<
+ string,
+ {
+ attachFilepathAbsolute: string;
+ attachFilepathRelative: string;
+ attachHandle: string;
+ }
+>;
+
+/**
+ * Organizes communication with sqlite DBMS.
+ */
+export abstract class AbstractSqliteDriver implements Driver {
+ // -------------------------------------------------------------------------
+ // Public Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connection used by driver.
+ */
+ connection: DataSource;
+
+ /**
+ * Sqlite has a single QueryRunner because it works on a single database connection.
+ */
+ queryRunner?: QueryRunner;
+
+ /**
+ * Real database connection with sqlite database.
+ */
+ databaseConnection: any;
+
+ // -------------------------------------------------------------------------
+ // Public Implemented Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Connection options.
+ */
+ options: BaseDataSourceOptions;
+
+ /**
+ * Master database used to perform all write queries.
+ */
+ database?: string;
+
+ /**
+ * Indicates if replication is enabled.
+ */
+ isReplicated: boolean = false;
+
+ /**
+ * SQLite underlying library.
+ */
+ sqlite: any;
+
+ /**
+ * Indicates if tree tables are supported by this driver.
+ */
+ treeSupport = true;
+
+ /**
+ * Represent transaction support by this driver
+ */
+ transactionSupport: 'simple' | 'nested' | 'none' = 'nested';
+
+ /**
+ * Gets list of supported column data types by a driver.
+ *
+ * @see https://www.tutorialspoint.com/sqlite/sqlite_data_types.htm
+ * @see https://sqlite.org/datatype3.html
+ */
+ supportedDataTypes: ColumnType[] = [
+ 'int',
+ 'integer',
+ 'tinyint',
+ 'smallint',
+ 'mediumint',
+ 'bigint',
+ 'unsigned big int',
+ 'int2',
+ 'int8',
+ 'integer',
+ 'character',
+ 'varchar',
+ 'varying character',
+ 'nchar',
+ 'native character',
+ 'nvarchar',
+ 'text',
+ 'clob',
+ 'text',
+ 'blob',
+ 'real',
+ 'double',
+ 'double precision',
+ 'float',
+ 'real',
+ 'numeric',
+ 'decimal',
+ 'boolean',
+ 'date',
+ 'time',
+ 'datetime',
+ 'json',
+ ];
+
+ /**
+ * Returns type of upsert supported by driver if any
+ */
+ supportedUpsertTypes: UpsertType[] = ['on-conflict-do-update'];
+
+ /**
+ * Gets list of column data types that support length by a driver.
+ */
+ withLengthColumnTypes: ColumnType[] = [
+ 'character',
+ 'varchar',
+ 'varying character',
+ 'nchar',
+ 'native character',
+ 'nvarchar',
+ 'text',
+ 'blob',
+ 'clob',
+ ];
+
+ /**
+ * Gets list of spatial column data types.
+ */
+ spatialTypes: ColumnType[] = [];
+
+ /**
+ * Gets list of column data types that support precision by a driver.
+ */
+ withPrecisionColumnTypes: ColumnType[] = [
+ 'real',
+ 'double',
+ 'double precision',
+ 'float',
+ 'real',
+ 'numeric',
+ 'decimal',
+ 'date',
+ 'time',
+ 'datetime',
+ ];
+
+ /**
+ * Gets list of column data types that support scale by a driver.
+ */
+ withScaleColumnTypes: ColumnType[] = [
+ 'real',
+ 'double',
+ 'double precision',
+ 'float',
+ 'real',
+ 'numeric',
+ 'decimal',
+ ];
+
+ /**
+ * Orm has special columns and we need to know what database column types should be for those types.
+ * Column types are driver dependant.
+ */
+ mappedDataTypes: MappedColumnTypes = {
+ createDate: 'datetime',
+ createDateDefault: "datetime('now')",
+ updateDate: 'datetime',
+ updateDateDefault: "datetime('now')",
+ deleteDate: 'datetime',
+ deleteDateNullable: true,
+ version: 'integer',
+ treeLevel: 'integer',
+ migrationId: 'integer',
+ migrationName: 'varchar',
+ migrationTimestamp: 'bigint',
+ cacheId: 'int',
+ cacheIdentifier: 'varchar',
+ cacheTime: 'bigint',
+ cacheDuration: 'int',
+ cacheQuery: 'text',
+ cacheResult: 'text',
+ metadataType: 'varchar',
+ metadataDatabase: 'varchar',
+ metadataSchema: 'varchar',
+ metadataTable: 'varchar',
+ metadataName: 'varchar',
+ metadataValue: 'text',
+ };
+
+ /**
+ * Default values of length, precision and scale depends on column data type.
+ * Used in the cases when length/precision/scale is not specified by user.
+ */
+ dataTypeDefaults: DataTypeDefaults;
+
+ /**
+ * No documentation specifying a maximum length for identifiers could be found
+ * for SQLite.
+ */
+ maxAliasLength?: number;
+
+ cteCapabilities: CteCapabilities = {
+ enabled: true,
+ requiresRecursiveHint: true,
+ };
+
+ // -------------------------------------------------------------------------
+ // Protected Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Any attached databases (excepting default 'main')
+ */
+ attachedDatabases: DatabasesMap = {};
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor(connection: DataSource) {
+ this.connection = connection;
+ this.options = connection.options as BaseDataSourceOptions;
+
+ this.database = DriverUtils.buildDriverOptions(this.options).database;
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Abstract
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates a query runner used to execute database queries.
+ */
+ abstract createQueryRunner(mode: ReplicationMode): QueryRunner;
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Performs connection to the database.
+ */
+ async connect(): Promise {
+ this.databaseConnection = await this.createDatabaseConnection();
+ }
+
+ /**
+ * Makes any action after connection (e.g. create extensions in Postgres driver).
+ */
+ afterConnect(): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Closes connection with database.
+ */
+ async disconnect(): Promise {
+ return new Promise((ok, fail) => {
+ this.queryRunner = undefined;
+ this.databaseConnection.close((err: any) => (err ? fail(err) : ok()));
+ });
+ }
+
+ hasAttachedDatabases(): boolean {
+ return !!Object.keys(this.attachedDatabases).length;
+ }
+
+ getAttachedDatabaseHandleByRelativePath(path: string): string | undefined {
+ return this.attachedDatabases?.[path]?.attachHandle;
+ }
+
+ getAttachedDatabasePathRelativeByHandle(handle: string): string | undefined {
+ return Object.values(this.attachedDatabases).find(({ attachHandle }) => handle === attachHandle)
+ ?.attachFilepathRelative;
+ }
+
+ /**
+ * Creates a schema builder used to build and sync a schema.
+ */
+ createSchemaBuilder() {
+ return new RdbmsSchemaBuilder(this.connection);
+ }
+
+ /**
+ * Prepares given value to a value to be persisted, based on its column type and metadata.
+ */
+ preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
+ if (columnMetadata.transformer)
+ value = ApplyValueTransformers.transformTo(columnMetadata.transformer, value);
+
+ if (value === null || value === undefined) return value;
+
+ if (columnMetadata.type === Boolean || columnMetadata.type === 'boolean') {
+ return value === true ? 1 : 0;
+ } else if (columnMetadata.type === 'date') {
+ return DateUtils.mixedDateToDateString(value);
+ } else if (columnMetadata.type === 'time') {
+ return DateUtils.mixedDateToTimeString(value);
+ } else if (columnMetadata.type === 'datetime' || columnMetadata.type === Date) {
+ // to string conversation needs because SQLite stores date as integer number, when date came as Object
+ // TODO: think about `toUTC` conversion
+ return DateUtils.mixedDateToUtcDatetimeString(value);
+ } else if (columnMetadata.type === 'json' || columnMetadata.type === 'simple-json') {
+ return DateUtils.simpleJsonToString(value);
+ } else if (columnMetadata.type === 'simple-array') {
+ return DateUtils.simpleArrayToString(value);
+ } else if (columnMetadata.type === 'simple-enum') {
+ return DateUtils.simpleEnumToString(value);
+ }
+
+ return value;
+ }
+
+ /**
+ * Prepares given value to a value to be hydrated, based on its column type or metadata.
+ */
+ prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
+ if (value === null || value === undefined)
+ return columnMetadata.transformer
+ ? ApplyValueTransformers.transformFrom(columnMetadata.transformer, value)
+ : value;
+
+ if (columnMetadata.type === Boolean || columnMetadata.type === 'boolean') {
+ value = value ? true : false;
+ } else if (columnMetadata.type === 'datetime' || columnMetadata.type === Date) {
+ /**
+ * Fix date conversion issue
+ *
+ * If the format of the date string is "2018-03-14 02:33:33.906", Safari (and iOS WKWebView) will convert it to an invalid date object.
+ * We need to modify the date string to "2018-03-14T02:33:33.906Z" and Safari will convert it correctly.
+ *
+ * ISO 8601
+ * https://www.w3.org/TR/NOTE-datetime
+ */
+ if (value && typeof value === 'string') {
+ // There are various valid time string formats a sqlite time string might have:
+ // https://www.sqlite.org/lang_datefunc.html
+ // There are two separate fixes we may need to do:
+ // 1) Add 'T' separator if space is used instead
+ // 2) Add 'Z' UTC suffix if no timezone or offset specified
+
+ if (/^\d\d\d\d-\d\d-\d\d \d\d:\d\d/.test(value)) {
+ value = value.replace(' ', 'T');
+ }
+ if (/^\d\d\d\d-\d\d-\d\dT\d\d:\d\d(:\d\d(\.\d\d\d)?)?$/.test(value)) {
+ value += 'Z';
+ }
+ }
+
+ value = DateUtils.normalizeHydratedDate(value);
+ } else if (columnMetadata.type === 'date') {
+ value = DateUtils.mixedDateToDateString(value);
+ } else if (columnMetadata.type === 'time') {
+ value = DateUtils.mixedTimeToString(value);
+ } else if (columnMetadata.type === 'json' || columnMetadata.type === 'simple-json') {
+ value = DateUtils.stringToSimpleJson(value);
+ } else if (columnMetadata.type === 'simple-array') {
+ value = DateUtils.stringToSimpleArray(value);
+ } else if (columnMetadata.type === 'simple-enum') {
+ value = DateUtils.stringToSimpleEnum(value, columnMetadata);
+ } else if (columnMetadata.type === Number) {
+ // convert to number if number
+ value = !isNaN(+value) ? parseInt(value) : value;
+ }
+
+ if (columnMetadata.transformer)
+ value = ApplyValueTransformers.transformFrom(columnMetadata.transformer, value);
+
+ return value;
+ }
+
+ /**
+ * Replaces parameters in the given sql with special escaping character
+ * and an array of parameter names to be passed to a query.
+ */
+ escapeQueryWithParameters(
+ sql: string,
+ parameters: ObjectLiteral,
+ nativeParameters: ObjectLiteral,
+ ): [string, any[]] {
+ const escapedParameters: any[] = Object.keys(nativeParameters).map((key) => {
+ // Mapping boolean values to their numeric representation
+ if (typeof nativeParameters[key] === 'boolean') {
+ return nativeParameters[key] === true ? 1 : 0;
+ }
+
+ if (nativeParameters[key] instanceof Date) {
+ return DateUtils.mixedDateToUtcDatetimeString(nativeParameters[key]);
+ }
+
+ return nativeParameters[key];
+ });
+
+ if (!parameters || !Object.keys(parameters).length) return [sql, escapedParameters];
+
+ sql = sql.replace(
+ /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
+ (full, isArray: string, key: string): string => {
+ if (!parameters.hasOwnProperty(key)) {
+ return full;
+ }
+
+ let value: any = parameters[key];
+
+ if (isArray) {
+ return value
+ .map((v: any) => {
+ escapedParameters.push(v);
+ return this.createParameter(key, escapedParameters.length - 1);
+ })
+ .join(', ');
+ }
+
+ if (typeof value === 'function') {
+ return value();
+ } else if (typeof value === 'number') {
+ return String(value);
+ }
+
+ // Sqlite does not have a boolean data type so we have to transform
+ // it to 1 or 0
+ if (typeof value === 'boolean') {
+ escapedParameters.push(+value);
+ return this.createParameter(key, escapedParameters.length - 1);
+ }
+
+ if (value instanceof Date) {
+ escapedParameters.push(DateUtils.mixedDateToUtcDatetimeString(value));
+ return this.createParameter(key, escapedParameters.length - 1);
+ }
+
+ escapedParameters.push(value);
+ return this.createParameter(key, escapedParameters.length - 1);
+ },
+ ); // todo: make replace only in value statements, otherwise problems
+ return [sql, escapedParameters];
+ }
+
+ /**
+ * Escapes a column name.
+ */
+ escape(columnName: string): string {
+ return '"' + columnName + '"';
+ }
+
+ /**
+ * Build full table name with database name, schema name and table name.
+ * E.g. myDB.mySchema.myTable
+ *
+ * Returns only simple table name because all inherited drivers does not supports schema and database.
+ */
+ buildTableName(tableName: string, schema?: string, database?: string): string {
+ return tableName;
+ }
+
+ /**
+ * Parse a target table name or other types and return a normalized table definition.
+ */
+ parseTableName(target: EntityMetadata | Table | View | TableForeignKey | string): {
+ database?: string;
+ schema?: string;
+ tableName: string;
+ } {
+ const driverDatabase = this.database;
+ const driverSchema = undefined;
+
+ if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
+ const parsed = this.parseTableName(
+ target.schema ? `"${target.schema}"."${target.name}"` : target.name,
+ );
+
+ return {
+ database: target.database || parsed.database || driverDatabase,
+ schema: target.schema || parsed.schema || driverSchema,
+ tableName: parsed.tableName,
+ };
+ }
+
+ if (InstanceChecker.isTableForeignKey(target)) {
+ const parsed = this.parseTableName(target.referencedTableName);
+
+ return {
+ database: target.referencedDatabase || parsed.database || driverDatabase,
+ schema: target.referencedSchema || parsed.schema || driverSchema,
+ tableName: parsed.tableName,
+ };
+ }
+
+ if (InstanceChecker.isEntityMetadata(target)) {
+ // EntityMetadata tableName is never a path
+
+ return {
+ database: target.database || driverDatabase,
+ schema: target.schema || driverSchema,
+ tableName: target.tableName,
+ };
+ }
+
+ const parts = target.split('.');
+
+ if (parts.length === 3) {
+ return {
+ database: parts[0] || driverDatabase,
+ schema: parts[1] || driverSchema,
+ tableName: parts[2],
+ };
+ } else if (parts.length === 2) {
+ const database = this.getAttachedDatabasePathRelativeByHandle(parts[0]) ?? driverDatabase;
+ return {
+ database: database,
+ schema: parts[0],
+ tableName: parts[1],
+ };
+ } else {
+ return {
+ database: driverDatabase,
+ schema: driverSchema,
+ tableName: target,
+ };
+ }
+ }
+
+ /**
+ * Creates a database type from a given column metadata.
+ */
+ normalizeType(column: {
+ type?: ColumnType;
+ length?: number | string;
+ precision?: number | null;
+ scale?: number;
+ }): string {
+ if (column.type === Number || column.type === 'int') {
+ return 'integer';
+ } else if (column.type === String) {
+ return 'varchar';
+ } else if (column.type === Date) {
+ return 'datetime';
+ } else if (column.type === Boolean) {
+ return 'boolean';
+ } else if (column.type === 'uuid') {
+ return 'varchar';
+ } else if (column.type === 'simple-array') {
+ return 'text';
+ } else if (column.type === 'simple-json') {
+ return 'text';
+ } else if (column.type === 'simple-enum') {
+ return 'varchar';
+ } else {
+ return (column.type as string) || '';
+ }
+ }
+
+ /**
+ * Normalizes "default" value of the column.
+ */
+ normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
+ const defaultValue = columnMetadata.default;
+
+ if (typeof defaultValue === 'number') {
+ return '' + defaultValue;
+ }
+
+ if (typeof defaultValue === 'boolean') {
+ return defaultValue ? '1' : '0';
+ }
+
+ if (typeof defaultValue === 'function') {
+ return defaultValue();
+ }
+
+ if (typeof defaultValue === 'string') {
+ return `'${defaultValue}'`;
+ }
+
+ if (defaultValue === null || defaultValue === undefined) {
+ return undefined;
+ }
+
+ return `${defaultValue}`;
+ }
+
+ /**
+ * Normalizes "isUnique" value of the column.
+ */
+ normalizeIsUnique(column: ColumnMetadata): boolean {
+ return column.entityMetadata.uniques.some(
+ (uq) => uq.columns.length === 1 && uq.columns[0] === column,
+ );
+ }
+
+ /**
+ * Calculates column length taking into account the default length values.
+ */
+ getColumnLength(column: ColumnMetadata): string {
+ return column.length ? column.length.toString() : '';
+ }
+
+ /**
+ * Normalizes "default" value of the column.
+ */
+ createFullType(column: TableColumn): string {
+ let type = column.type;
+ if (column.enum) {
+ return 'varchar';
+ }
+ if (column.length) {
+ type += '(' + column.length + ')';
+ } else if (
+ column.precision !== null &&
+ column.precision !== undefined &&
+ column.scale !== null &&
+ column.scale !== undefined
+ ) {
+ type += '(' + column.precision + ',' + column.scale + ')';
+ } else if (column.precision !== null && column.precision !== undefined) {
+ type += '(' + column.precision + ')';
+ }
+
+ if (column.isArray) type += ' array';
+
+ return type;
+ }
+
+ /**
+ * Obtains a new database connection to a master server.
+ * Used for replication.
+ * If replication is not setup then returns default connection's database connection.
+ */
+ obtainMasterConnection(): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Obtains a new database connection to a slave server.
+ * Used for replication.
+ * If replication is not setup then returns master (default) connection's database connection.
+ */
+ obtainSlaveConnection(): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Creates generated map of values generated or returned by database after INSERT query.
+ */
+ createGeneratedMap(
+ metadata: EntityMetadata,
+ insertResult: any,
+ entityIndex: number,
+ entityNum: number,
+ ) {
+ const generatedMap = metadata.generatedColumns.reduce((map, generatedColumn) => {
+ let value: any;
+ if (generatedColumn.generationStrategy === 'increment' && insertResult) {
+ // NOTE: When INSERT statement is successfully completed, the last inserted row ID is returned.
+ // see also: SqliteQueryRunner.query()
+ value = insertResult - entityNum + entityIndex + 1;
+ // } else if (generatedColumn.generationStrategy === "uuid") {
+ // value = insertValue[generatedColumn.databaseName];
+ }
+
+ if (!value) return map;
+ return OrmUtils.mergeDeep(map, generatedColumn.createValueMap(value));
+ }, {} as ObjectLiteral);
+
+ return Object.keys(generatedMap).length > 0 ? generatedMap : undefined;
+ }
+
+ /**
+ * Differentiate columns of this table and columns from the given column metadatas columns
+ * and returns only changed.
+ */
+ findChangedColumns(
+ tableColumns: TableColumn[],
+ columnMetadatas: ColumnMetadata[],
+ ): ColumnMetadata[] {
+ return columnMetadatas.filter((columnMetadata) => {
+ const tableColumn = tableColumns.find((c) => c.name === columnMetadata.databaseName);
+ if (!tableColumn) return false; // we don't need new columns, we only need exist and changed
+
+ const isColumnChanged =
+ tableColumn.name !== columnMetadata.databaseName ||
+ tableColumn.type !== this.normalizeType(columnMetadata) ||
+ tableColumn.length !== columnMetadata.length ||
+ tableColumn.precision !== columnMetadata.precision ||
+ tableColumn.scale !== columnMetadata.scale ||
+ this.normalizeDefault(columnMetadata) !== tableColumn.default ||
+ tableColumn.isPrimary !== columnMetadata.isPrimary ||
+ tableColumn.isNullable !== columnMetadata.isNullable ||
+ tableColumn.generatedType !== columnMetadata.generatedType ||
+ tableColumn.asExpression !== columnMetadata.asExpression ||
+ tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata) ||
+ (tableColumn.enum &&
+ columnMetadata.enum &&
+ !OrmUtils.isArraysEqual(
+ tableColumn.enum,
+ columnMetadata.enum.map((val) => val + ''),
+ )) ||
+ (columnMetadata.generationStrategy !== 'uuid' &&
+ tableColumn.isGenerated !== columnMetadata.isGenerated);
+
+ // DEBUG SECTION
+ // if (isColumnChanged) {
+ // console.log("table:", columnMetadata.entityMetadata.tableName)
+ // console.log(
+ // "name:",
+ // tableColumn.name,
+ // columnMetadata.databaseName,
+ // )
+ // console.log(
+ // "type:",
+ // tableColumn.type,
+ // this.normalizeType(columnMetadata),
+ // )
+ // console.log(
+ // "length:",
+ // tableColumn.length,
+ // columnMetadata.length,
+ // )
+ // console.log(
+ // "precision:",
+ // tableColumn.precision,
+ // columnMetadata.precision,
+ // )
+ // console.log("scale:", tableColumn.scale, columnMetadata.scale)
+ // console.log(
+ // "default:",
+ // this.normalizeDefault(columnMetadata),
+ // columnMetadata.default,
+ // )
+ // console.log(
+ // "isPrimary:",
+ // tableColumn.isPrimary,
+ // columnMetadata.isPrimary,
+ // )
+ // console.log(
+ // "isNullable:",
+ // tableColumn.isNullable,
+ // columnMetadata.isNullable,
+ // )
+ // console.log(
+ // "generatedType:",
+ // tableColumn.generatedType,
+ // columnMetadata.generatedType,
+ // )
+ // console.log(
+ // "asExpression:",
+ // tableColumn.asExpression,
+ // columnMetadata.asExpression,
+ // )
+ // console.log(
+ // "isUnique:",
+ // tableColumn.isUnique,
+ // this.normalizeIsUnique(columnMetadata),
+ // )
+ // console.log(
+ // "enum:",
+ // tableColumn.enum &&
+ // columnMetadata.enum &&
+ // !OrmUtils.isArraysEqual(
+ // tableColumn.enum,
+ // columnMetadata.enum.map((val) => val + ""),
+ // ),
+ // )
+ // console.log(
+ // "isGenerated:",
+ // tableColumn.isGenerated,
+ // columnMetadata.isGenerated,
+ // )
+ // }
+
+ return isColumnChanged;
+ });
+ }
+
+ /**
+ * Returns true if driver supports RETURNING / OUTPUT statement.
+ */
+ isReturningSqlSupported(): boolean {
+ return false;
+ }
+
+ /**
+ * Returns true if driver supports uuid values generation on its own.
+ */
+ isUUIDGenerationSupported(): boolean {
+ return false;
+ }
+
+ /**
+ * Returns true if driver supports fulltext indices.
+ */
+ isFullTextColumnTypeSupported(): boolean {
+ return false;
+ }
+
+ /**
+ * Creates an escaped parameter.
+ */
+ createParameter(parameterName: string, index: number): string {
+ // return "$" + (index + 1);
+ return '?';
+ // return "$" + parameterName;
+ }
+
+ // -------------------------------------------------------------------------
+ // Protected Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates connection with the database.
+ */
+ protected createDatabaseConnection() {
+ throw new TypeORMError(
+ 'Do not use AbstractSqlite directly, it has to be used with one of the sqlite drivers',
+ );
+ }
+
+ /**
+ * If driver dependency is not given explicitly, then try to load it via "require".
+ */
+ protected loadDependencies(): void {
+ // dependencies have to be loaded in the specific driver
+ }
+}
diff --git a/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
new file mode 100644
index 00000000000..2ea39f4c898
--- /dev/null
+++ b/packages/@n8n/typeorm/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts
@@ -0,0 +1,1955 @@
+import { QueryRunner } from '../../query-runner/QueryRunner';
+import { ObjectLiteral } from '../../common/ObjectLiteral';
+import { TransactionNotStartedError } from '../../error/TransactionNotStartedError';
+import { TableColumn } from '../../schema-builder/table/TableColumn';
+import { Table } from '../../schema-builder/table/Table';
+import { TableIndex } from '../../schema-builder/table/TableIndex';
+import { TableForeignKey } from '../../schema-builder/table/TableForeignKey';
+import { View } from '../../schema-builder/view/View';
+import { Query } from '../Query';
+import { AbstractSqliteDriver } from './AbstractSqliteDriver';
+import { ReadStream } from '../../platform/PlatformTools';
+import { TableIndexOptions } from '../../schema-builder/options/TableIndexOptions';
+import { TableUnique } from '../../schema-builder/table/TableUnique';
+import { BaseQueryRunner } from '../../query-runner/BaseQueryRunner';
+import { OrmUtils } from '../../util/OrmUtils';
+import { TableCheck } from '../../schema-builder/table/TableCheck';
+import { IsolationLevel } from '../types/IsolationLevel';
+import { TableExclusion } from '../../schema-builder/table/TableExclusion';
+import { TransactionAlreadyStartedError, TypeORMError } from '../../error';
+import { MetadataTableType } from '../types/MetadataTableType';
+import { InstanceChecker } from '../../util/InstanceChecker';
+import { TransactionRollbackFailedError } from '../../error/TransactionRollbackFailedError';
+
+/**
+ * Runs queries on a single sqlite database connection.
+ */
+export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implements QueryRunner {
+ // -------------------------------------------------------------------------
+ // Public Implemented Properties
+ // -------------------------------------------------------------------------
+
+ /**
+ * Database driver used by connection.
+ */
+ driver: AbstractSqliteDriver;
+
+ protected transactionPromise: Promise | null = null;
+
+ // -------------------------------------------------------------------------
+ // Constructor
+ // -------------------------------------------------------------------------
+
+ constructor() {
+ super();
+ }
+
+ // -------------------------------------------------------------------------
+ // Public Methods
+ // -------------------------------------------------------------------------
+
+ /**
+ * Creates/uses database connection from the connection pool to perform further operations.
+ * Returns obtained database connection.
+ */
+ connect(): Promise {
+ return Promise.resolve(this.driver.databaseConnection);
+ }
+
+ /**
+ * Releases used database connection.
+ * We just clear loaded tables and sql in memory, because sqlite do not support multiple connections thus query runners.
+ */
+ release(): Promise {
+ this.loadedTables = [];
+ this.clearSqlMemory();
+ return Promise.resolve();
+ }
+
+ /**
+ * Starts transaction.
+ */
+ async startTransaction(isolationLevel?: IsolationLevel): Promise {
+ if (this.driver.transactionSupport === 'none')
+ throw new TypeORMError(
+ `Transactions aren't supported by ${this.connection.driver.options.type}.`,
+ );
+
+ if (this.isTransactionActive && this.driver.transactionSupport === 'simple')
+ throw new TransactionAlreadyStartedError();
+
+ if (
+ isolationLevel &&
+ isolationLevel !== 'READ UNCOMMITTED' &&
+ isolationLevel !== 'SERIALIZABLE'
+ )
+ throw new TypeORMError(`SQLite only supports SERIALIZABLE and READ UNCOMMITTED isolation`);
+
+ this.isTransactionActive = true;
+ try {
+ await this.broadcaster.broadcast('BeforeTransactionStart');
+ } catch (err) {
+ this.isTransactionActive = false;
+ throw err;
+ }
+
+ if (this.transactionDepth === 0) {
+ this.transactionDepth += 1;
+ if (isolationLevel) {
+ if (isolationLevel === 'READ UNCOMMITTED') {
+ await this.query('PRAGMA read_uncommitted = true');
+ } else {
+ await this.query('PRAGMA read_uncommitted = false');
+ }
+ }
+ await this.query('BEGIN TRANSACTION');
+ } else {
+ this.transactionDepth += 1;
+ await this.query(`SAVEPOINT typeorm_${this.transactionDepth - 1}`);
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionStart');
+ }
+
+ /**
+ * Commits transaction.
+ * Error will be thrown if transaction was not started.
+ */
+ async commitTransaction(): Promise {
+ if (!this.isTransactionActive) throw new TransactionNotStartedError();
+
+ await this.broadcaster.broadcast('BeforeTransactionCommit');
+
+ if (this.transactionDepth > 1) {
+ this.transactionDepth -= 1;
+ await this.query(`RELEASE SAVEPOINT typeorm_${this.transactionDepth}`);
+ } else {
+ this.transactionDepth -= 1;
+ await this.query('COMMIT');
+ this.isTransactionActive = false;
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionCommit');
+ }
+
+ /**
+ * Rollbacks transaction.
+ * Error will be thrown if transaction was not started.
+ */
+ async rollbackTransaction(): Promise {
+ try {
+ if (!this.isTransactionActive) throw new TransactionNotStartedError();
+
+ await this.broadcaster.broadcast('BeforeTransactionRollback');
+
+ if (this.transactionDepth > 1) {
+ this.transactionDepth -= 1;
+ await this.query(`ROLLBACK TO SAVEPOINT typeorm_${this.transactionDepth}`);
+ } else {
+ this.transactionDepth -= 1;
+ await this.query('ROLLBACK');
+ this.isTransactionActive = false;
+ }
+
+ await this.broadcaster.broadcast('AfterTransactionRollback');
+ } catch (rollbackError) {
+ throw new TransactionRollbackFailedError(rollbackError);
+ }
+ }
+
+ /**
+ * Returns raw data stream.
+ */
+ stream(
+ query: string,
+ parameters?: any[],
+ onEnd?: Function,
+ onError?: Function,
+ ): Promise {
+ throw new TypeORMError(`Stream is not supported by sqlite driver.`);
+ }
+
+ /**
+ * Returns all available database names including system databases.
+ */
+ async getDatabases(): Promise {
+ return Promise.resolve([]);
+ }
+
+ /**
+ * Returns all available schema names including system schemas.
+ * If database parameter specified, returns schemas of that database.
+ */
+ async getSchemas(database?: string): Promise {
+ return Promise.resolve([]);
+ }
+
+ /**
+ * Checks if database with the given name exist.
+ */
+ async hasDatabase(database: string): Promise {
+ return Promise.resolve(false);
+ }
+
+ /**
+ * Loads currently using database
+ */
+ async getCurrentDatabase(): Promise {
+ return Promise.resolve(undefined);
+ }
+
+ /**
+ * Checks if schema with the given name exist.
+ */
+ async hasSchema(schema: string): Promise {
+ throw new TypeORMError(`This driver does not support table schemas`);
+ }
+
+ /**
+ * Loads currently using database schema
+ */
+ async getCurrentSchema(): Promise {
+ return Promise.resolve(undefined);
+ }
+
+ /**
+ * Checks if table with the given name exist in the database.
+ */
+ async hasTable(tableOrName: Table | string): Promise {
+ const tableName = InstanceChecker.isTable(tableOrName) ? tableOrName.name : tableOrName;
+ const sql = `SELECT * FROM "sqlite_master" WHERE "type" = 'table' AND "name" = ?1`;
+ const result = await this.query(sql, [tableName]);
+ return result.length ? true : false;
+ }
+
+ /**
+ * Checks if column with the given name exist in the given table.
+ */
+ async hasColumn(tableOrName: Table | string, columnName: string): Promise {
+ const tableName = InstanceChecker.isTable(tableOrName) ? tableOrName.name : tableOrName;
+ const sql = `PRAGMA table_xinfo(${this.escapePath(tableName)})`;
+ const columns: ObjectLiteral[] = await this.query(sql);
+ return !!columns.find((column) => column['name'] === columnName);
+ }
+
+ /**
+ * Creates a new database.
+ */
+ async createDatabase(database: string, ifNotExist?: boolean): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Drops database.
+ */
+ async dropDatabase(database: string, ifExist?: boolean): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Creates a new table schema.
+ */
+ async createSchema(schemaPath: string, ifNotExist?: boolean): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Drops table schema.
+ */
+ async dropSchema(schemaPath: string, ifExist?: boolean): Promise {
+ return Promise.resolve();
+ }
+
+ /**
+ * Creates a new table.
+ */
+ async createTable(
+ table: Table,
+ ifNotExist: boolean = false,
+ createForeignKeys: boolean = true,
+ createIndices: boolean = true,
+ ): Promise {
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ if (ifNotExist) {
+ const isTableExist = await this.hasTable(table);
+ if (isTableExist) return Promise.resolve();
+ }
+
+ upQueries.push(this.createTableSql(table, createForeignKeys));
+ downQueries.push(this.dropTableSql(table));
+
+ if (createIndices) {
+ table.indices.forEach((index) => {
+ // new index may be passed without name. In this case we generate index name manually.
+ if (!index.name)
+ index.name = this.connection.namingStrategy.indexName(
+ table,
+ index.columnNames,
+ index.where,
+ );
+ upQueries.push(this.createIndexSql(table, index));
+ downQueries.push(this.dropIndexSql(index));
+ });
+ }
+
+ // if table have column with generated type, we must add the expression to the metadata table
+ const generatedColumns = table.columns.filter(
+ (column) => column.generatedType && column.asExpression,
+ );
+
+ for (const column of generatedColumns) {
+ const insertQuery = this.insertTypeormMetadataSql({
+ table: table.name,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ table: table.name,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+
+ upQueries.push(insertQuery);
+ downQueries.push(deleteQuery);
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Drops the table.
+ */
+ async dropTable(
+ tableOrName: Table | string,
+ ifExist?: boolean,
+ dropForeignKeys: boolean = true,
+ dropIndices: boolean = true,
+ ): Promise {
+ if (ifExist) {
+ const isTableExist = await this.hasTable(tableOrName);
+ if (!isTableExist) return Promise.resolve();
+ }
+
+ // if dropTable called with dropForeignKeys = true, we must create foreign keys in down query.
+ const createForeignKeys: boolean = dropForeignKeys;
+ const table = InstanceChecker.isTable(tableOrName)
+ ? tableOrName
+ : await this.getCachedTable(tableOrName);
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+
+ if (dropIndices) {
+ table.indices.forEach((index) => {
+ upQueries.push(this.dropIndexSql(index));
+ downQueries.push(this.createIndexSql(table, index));
+ });
+ }
+
+ upQueries.push(this.dropTableSql(table, ifExist));
+ downQueries.push(this.createTableSql(table, createForeignKeys));
+
+ // if table had columns with generated type, we must remove the expression from the metadata table
+ const generatedColumns = table.columns.filter(
+ (column) => column.generatedType && column.asExpression,
+ );
+
+ for (const column of generatedColumns) {
+ const deleteQuery = this.deleteTypeormMetadataSql({
+ table: table.name,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ });
+
+ const insertQuery = this.insertTypeormMetadataSql({
+ table: table.name,
+ type: MetadataTableType.GENERATED_COLUMN,
+ name: column.name,
+ value: column.asExpression,
+ });
+
+ upQueries.push(deleteQuery);
+ downQueries.push(insertQuery);
+ }
+
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Creates a new view.
+ */
+ async createView(view: View, syncWithMetadata: boolean = false): Promise {
+ const upQueries: Query[] = [];
+ const downQueries: Query[] = [];
+ upQueries.push(this.createViewSql(view));
+ if (syncWithMetadata) upQueries.push(this.insertViewDefinitionSql(view));
+ downQueries.push(this.dropViewSql(view));
+ if (syncWithMetadata) downQueries.push(this.deleteViewDefinitionSql(view));
+ await this.executeQueries(upQueries, downQueries);
+ }
+
+ /**
+ * Drops the view.
+ */
+ async dropView(target: View | string): Promise