mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
Merge branch 'next' into schema-builder-refactoring
Conflicts: CHANGELOG.md docs/entities.md gulpfile.ts package-lock.json src/connection/Connection.ts src/driver/oracle/OracleDriver.ts src/driver/postgres/PostgresDriver.ts src/metadata-args/MetadataArgsStorage.ts src/metadata/EntityMetadata.ts src/persistence/SubjectExecutor.ts src/query-builder/UpdateQueryBuilder.ts test/functional/sqljs/auto-save.ts
This commit is contained in:
commit
d6edc142fd
23
CHANGELOG.md
23
CHANGELOG.md
@ -17,19 +17,18 @@ feel free to ask us and community.
|
||||
* now relation id can be set directly to relation, e.g. `Post { @ManyToOne(type => Tag) tag: Tag|number }` with `post.tag = 1` usage.
|
||||
* now you can disable persistence on any relation by setting `@OneToMany(type => Post, post => tag, { persistence: false })`. This can dramatically improve entity save performance.
|
||||
* `loadAllRelationIds` method of `QueryBuilder` now accepts list of relation paths that needs to be loaded, also `disableMixedMap` option is now by default set to false, but you can enable it via new method parameter `options`
|
||||
* lot of changes affect closure table pattern which is planned for fix in 0.3.0
|
||||
* now `returning` and `output` statements of `InsertQueryBuilder` support array of columns as argument
|
||||
* now when many-to-many and one-to-many relation set to `null` all items from that relation are removed, just like it would be set to empty array
|
||||
* fixed issues with relation updation from one-to-one non-owner side
|
||||
* now version column is updated on the database level, not by ORM anymore
|
||||
* now created date and update date columns is set on the database level, not by ORM anymore (e.g. using `CURRENT_TIMESTAMP` as a default value)
|
||||
* now `InsertQueryBuilder`, `UpdateQueryBuilder` and `DeleteQueryBuilder` automatically update entities after execution.
|
||||
This only happens if real entity objects are passed.
|
||||
This only happens if real entity objects are passed.
|
||||
Some databases (like mysql and sqlite) requires a separate query to perform this operation.
|
||||
If you want to disable this behavior use `queryBuilder.updateEntity(false)` method.
|
||||
This feature is convenient for users who have uuid, create/update date, version columns or columns with DEFAULT value set.
|
||||
* now `InsertQueryBuilder`, `UpdateQueryBuilder` and `DeleteQueryBuilder` call subscribers and listeners.
|
||||
You can disable this behavior by setting `queryBuilder.callListeners(false)` method.
|
||||
* now `InsertQueryBuilder`, `UpdateQueryBuilder` and `DeleteQueryBuilder` call subscribers and listeners.
|
||||
You can disable this behavior by setting `queryBuilder.callListeners(false)` method.
|
||||
* `Repository` and `EntityManager` method `.findOne` is deprecated and will be removed in next 0.3.0 version.
|
||||
Use `findOne(id)` method instead now.
|
||||
* `InsertQueryBuilder` now returns `InsertResult` which contains extended information and metadata about runned query
|
||||
@ -53,12 +52,28 @@ Use `findOne(id)` method instead now.
|
||||
* `skipSync` in entity options has been renamed to `synchronize`. Now if it set to false schema synchronization for the entity will be disabled.
|
||||
By default its true.
|
||||
* now array initializations for relations are forbidden and ORM throws an error if there are entities with initialized relation arrays.
|
||||
* `@ClosureEntity` decorator has been removed. Instead `@Entity` + `@Tree("closure-table")` must be used
|
||||
* added support for nested set and materialized path tree hierarchy patterns
|
||||
* added `@Unique` decorator. Accepts custom unique constraint name and columns to be unique. Used only on as
|
||||
composite unique constraint, on table level. E.g. `@Unique("uq_id_name", ["id", "name"])`
|
||||
|
||||
## 0.1.13
|
||||
* added simple-json column type ([#1448](https://github.com/typeorm/typeorm/pull/1488))
|
||||
* fixed transform behaviour for timestamp columns ([#1140](https://github.com/typeorm/typeorm/issues/1140))
|
||||
|
||||
## 0.1.12
|
||||
|
||||
* EntitySubscriber now fires events on subclass entity ([#1369](https://github.com/typeorm/typeorm/issues/1369))
|
||||
* fixed error with entity schema validator being async ([#1448](https://github.com/typeorm/typeorm/issues/1448))
|
||||
|
||||
## 0.1.11
|
||||
|
||||
* postgres extensions now gracefully handled when user does not have rights to use them ([#1407](https://github.com/typeorm/typeorm/issues/1407))
|
||||
|
||||
## 0.1.10
|
||||
|
||||
* `sqljs` driver now enforces FK integrity by default (same behavior as `sqlite`)
|
||||
* fixed issue that broke browser support in 0.1.8 because of the debug package ([#1344](https://github.com/typeorm/typeorm/pull/1344))
|
||||
|
||||
## 0.1.9
|
||||
|
||||
|
||||
34
ISSUE_TEMPLATE.md
Normal file
34
ISSUE_TEMPLATE.md
Normal file
@ -0,0 +1,34 @@
|
||||
**Issue type:**
|
||||
|
||||
[ ] question
|
||||
[ ] bug report
|
||||
[ ] feature request
|
||||
[ ] documentation issue
|
||||
|
||||
**Database system/driver:**
|
||||
|
||||
[ ] `cordova`
|
||||
[ ] `mongodb`
|
||||
[ ] `mssql`
|
||||
[ ] `mysql` / `mariadb`
|
||||
[ ] `oracle`
|
||||
[ ] `postgres`
|
||||
[ ] `sqlite`
|
||||
[ ] `sqljs`
|
||||
[ ] `websql`
|
||||
|
||||
**TypeORM version:**
|
||||
|
||||
[ ] `latest`
|
||||
[ ] `@next`
|
||||
[ ] `0.x.x` (or put your version here)
|
||||
|
||||
**Steps to reproduce or a small repository showing the problem:**
|
||||
|
||||
<!--
|
||||
To answer those questions you need to put "x" inside the square brackets, for example:
|
||||
[x] `mysql`
|
||||
[ ] `postgres`
|
||||
|
||||
Also, please format your code properly (by taking code blocks into ```ts .... ```)
|
||||
!>
|
||||
74
README.md
74
README.md
@ -178,7 +178,7 @@ await timber.remove();
|
||||
|
||||
* for **MySQL** or **MariaDB**
|
||||
|
||||
`npm install mysql --save`
|
||||
`npm install mysql --save` (you can install `mysql2` instead as well)
|
||||
|
||||
* for **PostgreSQL**
|
||||
|
||||
@ -309,15 +309,16 @@ creating more entities.
|
||||
## Step-by-Step Guide
|
||||
|
||||
What are you expecting from ORM?
|
||||
First of all you are expecting it will create a database tables for you
|
||||
and find / insert / update / delete your data without pain and having to write lot of hardly maintainable SQL queries.
|
||||
This guide will show you how to setup TypeORM from scratch and make it to do what you are 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 setup TypeORM from scratch and make it do what you are expecting from ORM.
|
||||
|
||||
### Create a model
|
||||
|
||||
Working with database starts from creating a tables.
|
||||
Working with database starts from creating tables.
|
||||
How do you tell TypeORM to create a database table?
|
||||
Answer is - thought the models.
|
||||
Answer is - through the models.
|
||||
Your models in your app - are your database tables.
|
||||
|
||||
For example, you have a `Photo` model:
|
||||
@ -333,14 +334,14 @@ export class Photo {
|
||||
```
|
||||
|
||||
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 defined as *entities*.
|
||||
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 `@Entity` decorator.
|
||||
Database table will be created for such model.
|
||||
*Entity* is your model decorated by an `@Entity` decorator.
|
||||
A database table will be created for such models.
|
||||
You work with entities everywhere with TypeORM.
|
||||
You can load/insert/update/remove and perform other operations with them.
|
||||
|
||||
@ -360,13 +361,13 @@ export class Photo {
|
||||
}
|
||||
```
|
||||
|
||||
Now database table will be created for `Photo` entity and we'll be able to work with it anywhere in our app.
|
||||
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 entity's properties you want to make a 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
|
||||
@ -398,17 +399,16 @@ export class Photo {
|
||||
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 support by implicitly specify a column type into `@Column` decorator.
|
||||
But you can use any column type your database supports by implicitly 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 primary key.
|
||||
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 column.
|
||||
This is requirement and you can't avoid it.
|
||||
To make a column a primary you need to use `@PrimaryColumn` decorator.
|
||||
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 `@PrimaryColumn` decorator.
|
||||
|
||||
```typescript
|
||||
import {Entity, Column, PrimaryColumn} from "typeorm";
|
||||
@ -439,7 +439,7 @@ export class Photo {
|
||||
### 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 `@PrimaryColumn` decorator to `@PrimaryGeneratedColumn` decorator:
|
||||
To do that, you need to change the `@PrimaryColumn` decorator to a `@PrimaryGeneratedColumn` decorator:
|
||||
|
||||
```typescript
|
||||
import {Entity, Column, PrimaryGeneratedColumn} from "typeorm";
|
||||
@ -503,8 +503,8 @@ export class Photo {
|
||||
```
|
||||
|
||||
Column types are database-specific.
|
||||
You can set any column type your database support.
|
||||
More information on supported column types you can find [here](./docs/entities.md#column-types).
|
||||
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 connection to the database
|
||||
|
||||
@ -618,8 +618,8 @@ createConnection(/*...*/).then(connection => {
|
||||
```
|
||||
|
||||
Once your entity is saved it will get a newly generated id.
|
||||
`save` method returns instance of same object you pass to it.
|
||||
Its not a new copy of an object, it modifies its "id" and returns it.
|
||||
`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 async/await syntax
|
||||
|
||||
@ -809,10 +809,10 @@ export class PhotoMetadata {
|
||||
}
|
||||
```
|
||||
|
||||
Here, we are used a new decorator called `@OneToOne`. It allows us to create a one-to-one relation between two entities.
|
||||
`type => Photo` is a function that returns the class of the entity with which we want to make our relation.
|
||||
We are forced to use a function that returns a class, instead of using class directly, because of the language specifics.
|
||||
We can also write it as a `() => Photo`, but we use `type => Photo` as convention to increase code readability.
|
||||
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.
|
||||
@ -947,11 +947,11 @@ createConnection(/*...*/).then(async connection => {
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
Here photos will contain an array of photos from the database, and each photo will contain its photo metadata.
|
||||
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 more complex query you should use `QueryBuilder` instead.
|
||||
`QueryBuilder` allows to use more complex queries in an elegant way:
|
||||
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 {createConnection} from "typeorm";
|
||||
@ -971,9 +971,9 @@ createConnection(/*...*/).then(async connection => {
|
||||
}).catch(error => console.log(error));
|
||||
```
|
||||
|
||||
`QueryBuilder` allows to create and execute SQL query of almost any complexity.
|
||||
When you work with `QueryBuilder` think like you are creating SQL query.
|
||||
In this example "photo" and "metadata" are aliases applied to selected photos.
|
||||
`QueryBuilder` allows 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
|
||||
@ -992,7 +992,7 @@ export class Photo {
|
||||
}
|
||||
```
|
||||
|
||||
Using `cascade` allows us not to separately save photo and separately save metadata objects now.
|
||||
Using `cascade` allows us not to separately save photo 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
|
||||
@ -1206,7 +1206,7 @@ const loadedPhoto = await connection
|
||||
|
||||
### Using QueryBuilder
|
||||
|
||||
You can use QueryBuilder to build SQL query of almost any complexity. For example, you can do this:
|
||||
You can use QueryBuilder to build SQL queries of almost any complexity. For example, you can do this:
|
||||
|
||||
```typescript
|
||||
let photos = await connection
|
||||
|
||||
@ -6,11 +6,11 @@
|
||||
|
||||
## What is the Active Record pattern?
|
||||
|
||||
In TypeORM you can use both, the Active Record and the Data Mapper patterns.
|
||||
In TypeORM you can use both the Active Record and the Data Mapper patterns.
|
||||
|
||||
Using the Active Record approach, you define all your query methods inside the model itself, and you save, remove and load objects using model methods.
|
||||
Using the Active Record approach, you define all your query methods inside the model itself, and you save, remove, and load objects using model methods.
|
||||
|
||||
Simply said the Active Record pattern is an approach to access your database within your models.
|
||||
Simply said, the Active Record pattern is an approach to access your database within your models.
|
||||
You can read more about the Active Record pattern on [Wikipedia](https://en.wikipedia.org/wiki/Active_record_pattern).
|
||||
|
||||
Example:
|
||||
@ -36,8 +36,8 @@ export class User extends BaseEntity {
|
||||
}
|
||||
```
|
||||
|
||||
All active-record entities must extend the `BaseEntity` class which provides methods to work with the entity.
|
||||
Example how to work with such entity:
|
||||
All active-record entities must extend the `BaseEntity` class, which provides methods to work with the entity.
|
||||
Example of how to work with such entity:
|
||||
|
||||
```typescript
|
||||
|
||||
@ -57,11 +57,11 @@ const newUsers = await User.find({ isActive: true });
|
||||
const timber = await User.findOne({ firstName: "Timber", lastName: "Saw" });
|
||||
```
|
||||
|
||||
`BaseEntity` has most of methods standard `Repository` has.
|
||||
Most of the times you don't need to use `Repository` or `EntityManager` with active record entities.
|
||||
`BaseEntity` has most of the methods of the standard `Repository`.
|
||||
Most of the time you don't need to use `Repository` or `EntityManager` with active record entities.
|
||||
|
||||
Now let's say we want to create a function that returns users by first and last names.
|
||||
We can create such function as a static method in a `User` class:
|
||||
Now let's say we want to create a function that returns users by first and last name.
|
||||
We can create such functions as a static method in a `User` class:
|
||||
|
||||
```typescript
|
||||
import {BaseEntity, Entity, PrimaryGeneratedColumn, Column} from "typeorm";
|
||||
@ -99,13 +99,13 @@ const timber = await User.findByName("Timber", "Saw");
|
||||
|
||||
## What is the Data Mapper pattern?
|
||||
|
||||
In TypeORM you can use both Active Record and Data Mapper patterns.
|
||||
In TypeORM you can use both the Active Record and Data Mapper patterns.
|
||||
|
||||
Using the Data Mapper, approach you define all your query methods separate classes called "repositories",
|
||||
and you save, remove, load objects using repositories.
|
||||
Using the Data Mapper approach, you define all your query methods in separate classes called "repositories",
|
||||
and you save, remove, and load objects using repositories.
|
||||
In data mapper your entities are very dumb - they just define their properties and may have some "dummy" methods.
|
||||
|
||||
Simply said data mapper is an approach to access your database within repositories instead of models.
|
||||
Simply said, data mapper is an approach to access your database within repositories instead of models.
|
||||
You can read more about data mapper on [Wikipedia](https://en.wikipedia.org/wiki/Data_mapper_pattern).
|
||||
|
||||
Example:
|
||||
@ -130,7 +130,7 @@ export class User {
|
||||
|
||||
}
|
||||
```
|
||||
Example how to work with such entity:
|
||||
Example of how to work with such entity:
|
||||
|
||||
```typescript
|
||||
const userRepository = connection.getRepository(User);
|
||||
@ -151,8 +151,8 @@ const newUsers = await userRepository.find({ isActive: true });
|
||||
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });
|
||||
```
|
||||
|
||||
Now let's say we want to create a function that returns users by first and last names.
|
||||
We can create such function in a "custom repository".
|
||||
Now let's say we want to create a function that returns users by first and last name.
|
||||
We can create such a function in a "custom repository".
|
||||
|
||||
```typescript
|
||||
import {EntityRepository, Repository} from "typeorm";
|
||||
@ -186,6 +186,6 @@ The decision is up to you.
|
||||
Both strategies have their own cons and pros.
|
||||
|
||||
One thing we should always keep in mind in software development is how we are going to maintain it.
|
||||
The `Data Mapper` approach helps you to keep maintainability of your software which is more effective in bigger apps.
|
||||
The `Active record` approach helps you to keep things simple which work good in a small apps.
|
||||
And simplicity is always a key to better maintainability.
|
||||
The `Data Mapper` approach helps you with maintainability of your software which is more effective in bigger apps.
|
||||
The `Active record` approach helps you to keep things simple which works good in small apps.
|
||||
And simplicity is always a key to better maintainability.
|
||||
|
||||
@ -39,12 +39,12 @@ const users = await connection
|
||||
});
|
||||
```
|
||||
|
||||
This will execute a query to fetch all admin users and cache its result.
|
||||
Next time when you execute same code it will get admin users from the cache.
|
||||
Default cache time is equal to `1000 ms`, e.g. 1 second.
|
||||
This means cache will be invalid 1 second after you called the query builder code.
|
||||
In practice, it means that if users open user page 150 times within 3 seconds only three queries will be executed during this period.
|
||||
All users inserted during the 1 second of caching won't be returned to the user.
|
||||
This will execute a query to fetch all admin users and cache the results.
|
||||
Next time you execute the same code, it will get all admin users from the cache.
|
||||
Default cache lifetime is equal to `1000 ms`, e.g. 1 second.
|
||||
This means the cache will be invalid 1 second after the query builder code is called.
|
||||
In practice, this means that if users open the user page 150 times within 3 seconds, only three queries will be executed during this period.
|
||||
Any users inserted during the 1 second cache window won't be returned to the user.
|
||||
|
||||
You can change cache time manually via `QueryBuilder`:
|
||||
|
||||
@ -104,8 +104,8 @@ const users = await connection
|
||||
});
|
||||
```
|
||||
|
||||
It will allow you to granular control your cache,
|
||||
for example, clear cached results when you insert a new user:
|
||||
This gives you granular control of your cache,
|
||||
for example, clearing cached results when you insert a new user:
|
||||
|
||||
```typescript
|
||||
await connection.queryResultCache.remove(["users_admins"]);
|
||||
|
||||
@ -77,7 +77,7 @@ Be careful with this option and don't use this in production - otherwise you'll
|
||||
This option is useful during debug and development.
|
||||
|
||||
* `synchronize` - 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 loose production data.
|
||||
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.
|
||||
As an 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.
|
||||
@ -155,7 +155,7 @@ See [SSL options](https://github.com/mysqljs/mysql#ssl-options).
|
||||
|
||||
* `host` - Database host.
|
||||
|
||||
* `port` - Database host port. Default mysql port is `5432`.
|
||||
* `port` - Database host port. Default postgres port is `5432`.
|
||||
|
||||
* `username` - Database username.
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ and you can access any method created inside it and any method in the standard e
|
||||
|
||||
## Custom repository extends standard AbstractRepository
|
||||
|
||||
Second way to create a custom repository is to extend `AbstractRepository`:
|
||||
The second way to create a custom repository is to extend `AbstractRepository`:
|
||||
|
||||
```typescript
|
||||
import {EntityRepository, AbstractRepository} from "typeorm";
|
||||
@ -88,15 +88,15 @@ await userRepository.createAndSave("Timber", "Saw");
|
||||
const timber = await userRepository.findByName("Timber", "Saw");
|
||||
```
|
||||
|
||||
The difference between this type of repository and the previous one is, that it does not expose all methods `Repository` has.
|
||||
The difference between this type of repository and the previous one is that it does not expose all the methods `Repository` has.
|
||||
`AbstractRepository` does not have any public methods,
|
||||
it only has protected methods like `manager` and `repository` which you can use in your own
|
||||
it only has protected methods, like `manager` and `repository`, which you can use in your own
|
||||
public methods.
|
||||
Extending `AbstractRepository` is useful if you don't want to expose all methods the standard `Repository` has to the public.
|
||||
|
||||
## Custom repository without extends
|
||||
|
||||
Third way to create a repository is to not extend anything,
|
||||
The third way to create a repository is to not extend anything,
|
||||
but define a constructor which always accepts an entity manager instance:
|
||||
|
||||
```typescript
|
||||
@ -136,13 +136,13 @@ const timber = await userRepository.findByName("Timber", "Saw");
|
||||
|
||||
This type of repository does not extend anything - you only need to define a constructor
|
||||
which must accept `EntityManager`. Then you can use it everywhere in your repository methods.
|
||||
Also this type of repository is not bound to a specific entity.
|
||||
Thus you can operate with multiple entities inside them.
|
||||
Also, this type of repository is not bound to a specific entity,
|
||||
thus, you can operate with multiple entities inside them.
|
||||
|
||||
## Using custom repositories in transactions or why custom repositories cannot be services
|
||||
|
||||
Custom repositories cannot be services.
|
||||
Because there isn't a single instance of a custom repository (just like regular repositories or entity manager) in the app.
|
||||
Custom repositories cannot be services,
|
||||
because there isn't a single instance of a custom repository (just like regular repositories or entity manager) in the app.
|
||||
Besides the fact that there can be multiple connections in your app (where entity manager and repositories are different)
|
||||
repositories and managers are different in transactions as well.
|
||||
For example:
|
||||
|
||||
@ -49,12 +49,12 @@ This code will create a database table named "users".
|
||||
|
||||
You can also specify some additional entity options:
|
||||
|
||||
* `name` - table name. If not specified then table name is generated from entity class name
|
||||
* `database` - database name in selected DB server
|
||||
* `schema` - schema name
|
||||
* `engine` - database engine to be set during table creation (works only in some databases)
|
||||
* `skipSync` - entities marked with this decorator are skipped from schema updates
|
||||
* `orderBy` - specifies default ordering for entities when using `find` operations and `QueryBuilder`
|
||||
* `name` - table name. If not specified, then table name is generated from entity class name.
|
||||
* `database` - database name in selected DB server.
|
||||
* `schema` - schema name.
|
||||
* `engine` - database engine to be set during table creation (works only in some databases).
|
||||
* `skipSync` - entities marked with this decorator are skipped from schema updates.
|
||||
* `orderBy` - specifies default ordering for entities when using `find` operations and `QueryBuilder`.
|
||||
|
||||
Example:
|
||||
|
||||
@ -106,13 +106,13 @@ export class User {
|
||||
* `type: ColumnType` - Column type. One of the [supported column types](entities.md#column-types).
|
||||
* `name: string` - Column name in the database table.
|
||||
By default the column name is generated from the name of the property.
|
||||
You can change it by specifying your own name
|
||||
* `length: string|number` - Column type's length. For example if you want to create `varchar(150)` type
|
||||
You can change it by specifying your own name.
|
||||
* `length: string|number` - Column type's length. For example, if you want to create `varchar(150)` type
|
||||
you specify column type and length options.
|
||||
* `nullable: boolean` - Makes column `NULL` or `NOT NULL` in the database.
|
||||
By default column is `nullable: false`.
|
||||
* `default: string` - Adds database-level column's `DEFAULT` value.
|
||||
* `primary: boolean` - Marks column as primary. Same if you use `@PrimaryColumn`.
|
||||
* `primary: boolean` - Marks column as primary. Same as using `@PrimaryColumn`.
|
||||
* `unique: boolean` - Marks column as unique column (creates unique constraint).
|
||||
* `comment: string` - Database's column comment. Not supported by all database types.
|
||||
* `precision: number` - The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
|
||||
@ -124,7 +124,7 @@ Used in some column types.
|
||||
* `collation: string` - Defines a column collation.
|
||||
* `enum: string[]|AnyEnum` - Used in `enum` column type to specify list of allowed enum values.
|
||||
You can specify array of values or specify a enum class.
|
||||
* `array: boolean` - Used for postgres column types which can be array (for example int[])
|
||||
* `array: boolean` - Used for postgres column types which can be array (for example int[]).
|
||||
|
||||
Learn more about [entity columns](entities.md#entity-columns).
|
||||
|
||||
@ -148,7 +148,7 @@ Learn more about [entity columns](entities.md#entity-columns).
|
||||
|
||||
#### `@PrimaryGeneratedColumn`
|
||||
|
||||
Marks a property in your entity as a table generated primary column.
|
||||
Marks a property in your entity as a table-generated primary column.
|
||||
Column it creates is primary and its value is auto-generated.
|
||||
Example:
|
||||
|
||||
@ -164,10 +164,10 @@ export class User {
|
||||
|
||||
There are two generation strategies:
|
||||
|
||||
* `increment` - uses AUTO_INCREMENT / SERIAL / SEQUENCE (depend on database type) to generate incremental number
|
||||
* `uuid` - generates unique `uuid` string
|
||||
* `increment` - uses AUTO_INCREMENT / SERIAL / SEQUENCE (depend on database type) to generate incremental number.
|
||||
* `uuid` - generates unique `uuid` string.
|
||||
|
||||
Default generation strategy is `increment`, to change it to `uuid` simple pass it as first argument to decorator:
|
||||
Default generation strategy is `increment`, to change it to `uuid`, simply pass it as the first argument to decorator:
|
||||
|
||||
```typescript
|
||||
@Entity()
|
||||
@ -219,7 +219,7 @@ export class User {
|
||||
#### `@UpdateDateColumn`
|
||||
|
||||
Special column that is automatically set to the entity's update time
|
||||
each time you call `save` of entity manager or repository.
|
||||
each time you call `save` from entity manager or repository.
|
||||
You don't need to write a value into this column - it will be automatically set.
|
||||
|
||||
```typescript
|
||||
@ -235,7 +235,7 @@ export class User {
|
||||
#### `@VersionColumn`
|
||||
|
||||
Special column that is automatically set to the entity's version (incremental number)
|
||||
each time you call `save` of entity manager or repository.
|
||||
each time you call `save` from entity manager or repository.
|
||||
You don't need to write a value into this column - it will be automatically set.
|
||||
|
||||
```typescript
|
||||
@ -250,7 +250,7 @@ export class User {
|
||||
|
||||
#### `@Generated`
|
||||
|
||||
Marks column to have a generated value. For example:
|
||||
Marks column to be a generated value. For example:
|
||||
|
||||
```typescript
|
||||
@Entity()
|
||||
@ -263,7 +263,7 @@ export class User {
|
||||
}
|
||||
```
|
||||
|
||||
Value will be generated only once before inserting the entity into the database.
|
||||
Value will be generated only once, before inserting the entity into the database.
|
||||
|
||||
## Relation decorators
|
||||
|
||||
@ -380,8 +380,8 @@ Learn more about [many-to-many relations](many-to-many-relations.md).
|
||||
|
||||
#### `@JoinColumn`
|
||||
|
||||
Defines which side of the relation contains join column with foreign key and
|
||||
allows to customize join column name and referenced column name.
|
||||
Defines which side of the relation contains the join column with a foreign key and
|
||||
allows you to customize the join column name and referenced column name.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -401,8 +401,8 @@ export class Post {
|
||||
#### `@JoinTable`
|
||||
|
||||
Used for `many-to-many` relations and describes join columns of the "junction" table.
|
||||
Junction table is a special separate table created automatically by TypeORM with columns referenced to the related entities.
|
||||
You can change the column names inside the junction table and their referenced columns as easy as with `@JoinColumn` decorator. You can also change the name of the generated "junction" table.
|
||||
Junction table is a special, separate table created automatically by TypeORM with columns referenced to the related entities.
|
||||
You can change the column names inside the junction table and their referenced columns with the `@JoinColumn` decorator. You can also change the name of the generated "junction" table.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -427,13 +427,13 @@ export class Post {
|
||||
```
|
||||
|
||||
If the destination table has composite primary keys,
|
||||
then array of properties must be send to `@JoinTable` decorator.
|
||||
then an array of properties must be sent to the `@JoinTable` decorator.
|
||||
|
||||
#### `@RelationId`
|
||||
|
||||
Loads id (or ids) of specific relation into property.
|
||||
For example if you have many-to-one `category` in your `Post` entity
|
||||
you can have category id by marking a new property with `@RelationId`.
|
||||
Loads id (or ids) of specific relations into properties.
|
||||
For example, if you have a many-to-one `category` in your `Post` entity,
|
||||
you can have a new category id by marking a new property with `@RelationId`.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -449,7 +449,7 @@ export class Post {
|
||||
}
|
||||
```
|
||||
|
||||
This functionality works for all kind of relations including `many-to-many`:
|
||||
This functionality works for all kind of relations, including `many-to-many`:
|
||||
|
||||
```typescript
|
||||
@Entity()
|
||||
@ -465,7 +465,7 @@ export class Post {
|
||||
```
|
||||
|
||||
Relation id is used only for representation.
|
||||
The underlying relation is not added/removed/changed when chaning the value.
|
||||
The underlying relation is not added/removed/changed when chaining the value.
|
||||
|
||||
## Subscriber and listener decorators
|
||||
|
||||
@ -605,8 +605,8 @@ Learn more about [listeners](listeners-and-subscribers.md).
|
||||
|
||||
#### `@EventSubscriber`
|
||||
|
||||
Marks a class as an event subscriber which can listen to specific entity events or any entity events.
|
||||
Events are firing using `QueryBuilder` and repository/manager methods.
|
||||
Marks a class as an event subscriber which can listen to specific entity events or any entity's events.
|
||||
Events are fired using `QueryBuilder` and repository/manager methods.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -632,7 +632,7 @@ export class PostSubscriber implements EntitySubscriberInterface<Post> {
|
||||
```
|
||||
|
||||
You can implement any method from `EntitySubscriberInterface`.
|
||||
To listen to any entity you just omit `listenTo` method and use `any`:
|
||||
To listen to any entity, you just omit the `listenTo` method and use `any`:
|
||||
|
||||
```typescript
|
||||
@EventSubscriber()
|
||||
@ -654,11 +654,11 @@ Learn more about [subscribers](listeners-and-subscribers.md).
|
||||
|
||||
#### `@Index`
|
||||
|
||||
This decorator allows to create database index for a specific column or columns.
|
||||
It also allows to mark column or columns to be unique.
|
||||
Decorator can be applied to columns or entity itself.
|
||||
Use it on a column when index on a single column is needed.
|
||||
And use it on the entity when a single index on multiple columns is required.
|
||||
This decorator allows you to create a database index for a specific column or columns.
|
||||
It also allows you to mark column or columns to be unique.
|
||||
This decorator can be applied to columns or an entity itself.
|
||||
Use it on a column when an index on a single column is needed
|
||||
and use it on the entity when a single index on multiple columns is required.
|
||||
Examples:
|
||||
|
||||
```typescript
|
||||
@ -697,7 +697,7 @@ Learn more about [indices](indices.md).
|
||||
#### `@Transaction`, `@TransactionManager` and `@TransactionRepository`
|
||||
|
||||
`@Transaction` is used on a method and wraps all its execution into a single database transaction.
|
||||
All database queries must be performed using the by `@TransactionManager` provided manager
|
||||
All database queries must be performed using the `@TransactionManager` provided manager
|
||||
or with the transaction repositories injected with `@TransactionRepository`.
|
||||
Examples:
|
||||
|
||||
@ -730,7 +730,7 @@ Learn more about [transactions](transactions.md).
|
||||
|
||||
#### `@EntityRepository`
|
||||
|
||||
Marks custom class as entity repository.
|
||||
Marks a custom class as an entity repository.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -749,6 +749,6 @@ Learn more about [custom entity repositories](working-with-entity-manager.md).
|
||||
|
||||
----
|
||||
|
||||
Note: some decorators (like `@ClosureEntity`, `@SingleEntityChild`, `@ClassEntityChild`, `@DiscriminatorColumn`, etc.) aren't
|
||||
Note: some decorators (like `@Tree`, `@ChildEntity`, etc.) aren't
|
||||
documented in this reference because they are treated as experimental at the moment.
|
||||
Expect to see their documentation in the future.
|
||||
Expect to see their documentation in the future.
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
* [Column types for `sqlite` / `websql`](#column-types-for-sqlite--websql--cordova)
|
||||
* [Column types for `mssql`](#column-types-for-mssql)
|
||||
* [`simple-array` column type](#simple-array-column-type)
|
||||
* [`simple-json` column type](#simple-json-column-type)
|
||||
* [Columns with generated values](#columns-with-generated-values)
|
||||
* [Column options](#column-options)
|
||||
|
||||
@ -236,34 +237,33 @@ or
|
||||
### Column types for `mysql` / `mariadb`
|
||||
|
||||
`int`, `tinyint`, `smallint`, `mediumint`, `bigint`, `decimal`, `float`, `double`,
|
||||
`decimal`, `real`, `datetime`, `time`, `timestamp`, `int`, `tinyint`, `smallint`, `mediumint`, `bigint`,
|
||||
`character`, `varchar`, `char`, `tinyblob`, `tinytext`, `mediumblob`, `mediumtext`, `blob`, `text`,
|
||||
`longblob`, `longtext`, `date`, `year`, `enum`, `json`
|
||||
`real`, `datetime`, `time`, `timestamp`, `character`, `varchar`, `char`, `tinyblob`,
|
||||
`tinytext`, `mediumblob`, `mediumtext`, `blob`, `text`, `longblob`, `longtext`, `date`,
|
||||
`year`, `enum`, `json`
|
||||
|
||||
### Column types for `postgres`
|
||||
|
||||
`int2`, `int2`, `int4`, `int8`, `integer`, `smallint`, `bigint`, `decimal`, `numeric`, `decimal`,
|
||||
`numeric`, `real`, `double precision`, `time`, `time with time zone`, `time without time zone`,
|
||||
`timestamp`, `timestamp without time zone`, `timestamp with time zone`, `int`, `smallint`, `bigint`,
|
||||
`character varying`, `character`, `varchar`, `char`, `int2`, `integer`, `int4`, `int8`,
|
||||
`float4`, `float8`, `smallserial`, `serial2`, `serial`, `serial4`, `bigserial`, `serial8`,
|
||||
`money`, `boolean`, `bool`, `text`, `citext`, `bytea`, `date`, `interval`, `point`, `line`, `lseg`, `box`,
|
||||
`int`, `int2`, `int4`, `int8`, `integer`, `smallint`, `bigint`, `float4`, `float8`,
|
||||
`numeric`, `decimal`, `real`, `double precision`, `time`, `time with time zone`,
|
||||
`time without time zone`, `timestamp`, `timestamp without time zone`, `timestamp with time zone`,
|
||||
`character varying`, `character`, `varchar`, `char`, `text`, `citext`,
|
||||
`smallserial`, `serial2`, `serial`, `serial4`, `bigserial`, `serial8`,
|
||||
`money`, `boolean`, `bool` `bytea`, `date`, `interval`, `point`, `line`, `lseg`, `box`,
|
||||
`circle`, `path`, `polygon`, `cidr`, `enum`, `inet`, `macaddr`, `bit`, `bit varying`,
|
||||
`varbit`, `tsvector`, `tsquery`, `uuid`, `xml`, `json`, `jsonb`
|
||||
|
||||
### Column types for `sqlite` / `websql` / `cordova`
|
||||
|
||||
`int`, `int2`, `int2`, `int8`, `integer`, `tinyint`, `smallint`, `mediumint`, `bigint`, `decimal`,
|
||||
`numeric`, `float`, `double`, `decimal`, `numeric`, `real`, `double precision`, `datetime`,
|
||||
`int`, `tinyint`, `smallint`, `mediumint`, `bigint`, `varying character`, `character`, `native character`,
|
||||
`varchar`, `nchar`, `nvarchar2`, `int2`, `integer`, `int8`, `unsigned big int`, `boolean`,
|
||||
`int`, `int2`, `int8`, `integer`, `tinyint`, `smallint`, `mediumint`, `bigint`, `decimal`,
|
||||
`numeric`, `float`, `double`, `real`, `double precision`, `datetime`, `varying character`,
|
||||
`character`, `native character`, `varchar`, `nchar`, `nvarchar2`, `unsigned big int`, `boolean`,
|
||||
`blob`, `text`, `clob`, `date`
|
||||
|
||||
### Column types for `mssql`
|
||||
|
||||
`int`, `tinyint`, `smallint`, `bigint`, `dec`, `decimal`, `numeric`, `float`, `dec`, `decimal`,
|
||||
`numeric`, `real`, `datetime`, `datetime2`, `datetimeoffset`, `time`, `timestamp`,
|
||||
`int`, `tinyint`, `smallint`, `bigint`, `nvarchar`, `varchar`, `char`, `nchar`, `binary`, `varbinary`,
|
||||
`nvarchar`, `varchar`, `char`, `nchar`, `binary`, `varbinary`,
|
||||
`bit`, `smallmoney`, `money`, `text`, `ntext`, `image`, `smalldatetime`, `date`, `xml`, `varbinary`,
|
||||
`cursor`, `hierarchyid`, `sql_variant`, `table`
|
||||
|
||||
@ -300,6 +300,34 @@ just like you stored them.
|
||||
|
||||
Note you **MUST NOT** have any comma in values you write.
|
||||
|
||||
### `simple-json` column type
|
||||
|
||||
There is a special column type called `simple-json` which can store any values which can be stored in database
|
||||
via JSON.stringify.
|
||||
Very useful when you do not have json type in your database and you want to store and load object
|
||||
without any hustle.
|
||||
For example:
|
||||
|
||||
```typescript
|
||||
@Entity()
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column("simple-json")
|
||||
profile: { name: string, nickname: string };
|
||||
|
||||
}
|
||||
```
|
||||
```typescript
|
||||
const user = new User();
|
||||
user.profile = { name: "John", nickname: "Malkovich" };
|
||||
```
|
||||
|
||||
Will be stored in a single database column as `{"name":"John","nickname":"Malkovich"}` value.
|
||||
When you'll load data from the database, you will have your object/array/primitive back via JSON.parse
|
||||
|
||||
### Columns with generated values
|
||||
|
||||
You can create column with generated value using `@Generated` decorator. For example:
|
||||
@ -365,6 +393,7 @@ You can specify array of values or specify a enum class.
|
||||
* `array: boolean` - Used for postgres column types which can be array (for example int[])
|
||||
* `transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType }` - Used to
|
||||
marshal properties of arbitrary type `EntityType` into a type `DatabaseType` supported by the database.
|
||||
* `select: boolean` - Defines whether or not to hide this column by default when making queries. When set to `false`, the column data will not show with a standard query. By default column is `select: true`
|
||||
|
||||
Note: most of those column options are RDBMS-specific and aren't available in `MongoDB`.
|
||||
|
||||
@ -516,9 +545,10 @@ To learn more about closure table take a look at [this awesome presentation by B
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import {ClosureEntity, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
|
||||
@ClosureEntity()
|
||||
@Entity()
|
||||
@Tree("closure-table")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
|
||||
@ -57,7 +57,7 @@ const userId = manager.getId(user); // userId === 1
|
||||
```
|
||||
|
||||
* `create` - Creates a new instance of `User`. Optionally accepts an object literal with user properties
|
||||
which will be written into newly created user object
|
||||
which will be written into newly created user object.
|
||||
|
||||
```typescript
|
||||
const user = manager.create(User); // same as const user = new User();
|
||||
@ -68,15 +68,15 @@ const user = manager.create(User, {
|
||||
}); // same as const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";
|
||||
```
|
||||
|
||||
* `merge` - Merges multiple entities into a single entity
|
||||
* `merge` - Merges multiple entities into a single entity.
|
||||
|
||||
```typescript
|
||||
const user = new User();
|
||||
manager.merge(User, user, { firstName: "Timber" }, { lastName: "Saw" }); // same as user.firstName = "Timber"; user.lastName = "Saw";
|
||||
```
|
||||
|
||||
* `preload` - Creates a new entity from the given plan javascript object. If entity already exist in the database, then
|
||||
it loads it (and everything related to it), replaces all values with the new ones from the given object
|
||||
* `preload` - Creates a new entity from the given plain javascript object. If the entity already exist in the database, then
|
||||
it loads it (and everything related to it), replaces all values with the new ones from the given object,
|
||||
and returns the new entity. The new entity is actually loaded from the database entity with all properties
|
||||
replaced from the new object.
|
||||
|
||||
@ -94,10 +94,10 @@ const user = await manager.preload(User, partialUser);
|
||||
```
|
||||
|
||||
* `save` - Saves a given entity or array of entities.
|
||||
If the entity already exist in the database then it's updated.
|
||||
If the entity does not exist in the database yet it's inserted.
|
||||
If the entity already exists in the database, then it's updated.
|
||||
If the entity does not exist in the database yet, it's inserted.
|
||||
It saves all given entities in a single transaction (in the case of entity manager is not transactional).
|
||||
Also supports partial updating since all undefined properties are skipped.
|
||||
Also supports partial updating since all undefined properties are skipped. In order to make a value `NULL`, you must manually set the property to equal `null`.
|
||||
|
||||
```typescript
|
||||
await manager.save(user);
|
||||
@ -109,7 +109,7 @@ await manager.save([
|
||||
```
|
||||
|
||||
* `remove` - Removes a given entity or array of entities.
|
||||
It removes all given entities in a single transaction (in the case of entity manager is not transactional).
|
||||
It removes all given entities in a single transaction (in the case of entity, manager is not transactional).
|
||||
|
||||
```typescript
|
||||
await manager.remove(user);
|
||||
@ -209,7 +209,7 @@ const categoryRepository = manager.getTreeRepository(Category);
|
||||
```
|
||||
|
||||
* `getMongoRepository` - Gets `MongoRepository` to perform operations on a specific entity.
|
||||
Learn more about [MongoDB](./mongodb.md) documentation.
|
||||
Learn more about [MongoDB](./mongodb.md).
|
||||
|
||||
```typescript
|
||||
const userRepository = manager.getMongoRepository(User);
|
||||
@ -222,9 +222,9 @@ const userRepository = manager.getMongoRepository(User);
|
||||
const myUserRepository = manager.getCustomRepository(UserRepository);
|
||||
```
|
||||
|
||||
* `release` - Releases query runner of a entity manager.
|
||||
* `release` - Releases query runner of an entity manager.
|
||||
Used only when query runner was created and managed manually.
|
||||
|
||||
```typescript
|
||||
await manager.release();
|
||||
```
|
||||
```
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
## Initial setup
|
||||
|
||||
Lets create a simple application called "user" which stores users in the database
|
||||
and allow to create, update, remove, get a list of all users and a single user by id
|
||||
Let's create a simple application called "user" which stores users in the database
|
||||
and allows us to create, update, remove, and get a list of all users, as well as a single user by id
|
||||
within web api.
|
||||
|
||||
First, create a directory called "user":
|
||||
@ -32,7 +32,7 @@ npm i typescript --save-dev
|
||||
```
|
||||
|
||||
Then let's create a `tsconfig.json` file which contains the configuration required for the application to
|
||||
compile and run. Create it using your favorite editor and put following configuration:
|
||||
compile and run. Create it using your favorite editor and put the following configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -47,7 +47,7 @@ compile and run. Create it using your favorite editor and put following configur
|
||||
}
|
||||
```
|
||||
|
||||
Now lets create main application endpoint - `app.ts` inside `src` directory:
|
||||
Now let's create a main application endpoint - `app.ts` inside the `src` directory:
|
||||
|
||||
```
|
||||
mkdir src
|
||||
@ -55,30 +55,30 @@ cd src
|
||||
touch app.ts
|
||||
```
|
||||
|
||||
Let's add simple `console.log` inside it:
|
||||
Let's add a simple `console.log` inside it:
|
||||
|
||||
```typescript
|
||||
console.log("Application is up and running");
|
||||
```
|
||||
|
||||
Now its time to run our application.
|
||||
To run it you need to compile your typescript project first:
|
||||
Now it's time to run our application.
|
||||
To run it, you need to compile your typescript project first:
|
||||
|
||||
```
|
||||
tsc
|
||||
```
|
||||
|
||||
Once you compile it you should have `src/app.js` file generated.
|
||||
You can run it:
|
||||
Once you compile it, you should have a `src/app.js` file generated.
|
||||
You can run it using:
|
||||
|
||||
```
|
||||
node src/app.js
|
||||
```
|
||||
|
||||
You should see "Application is up and running" message in your console just right after you run the application.
|
||||
You should see the, "Application is up and running" message in your console right after you run the application.
|
||||
|
||||
You must compile your files each time you make a change.
|
||||
Alternatively you can setup watcher or install [ts-node](http://github.com/ts-node/ts-node) to avoid manual compilation each time.
|
||||
Alternatively, you can setup watcher or install [ts-node](http://github.com/ts-node/ts-node) to avoid manual compilation each time.
|
||||
|
||||
## Adding Express to the application
|
||||
|
||||
@ -93,7 +93,7 @@ npm i express body-parser @types/express @types/body-parser --save
|
||||
* `@types/express` is used to have a type information when using express
|
||||
* `@types/body-parser` is used to have a type information when using body parser
|
||||
|
||||
Let's edit `src/app.ts` file and add express-related logic:
|
||||
Let's edit the `src/app.ts` file and add express-related logic:
|
||||
|
||||
```typescript
|
||||
import * as express from "express";
|
||||
@ -131,13 +131,13 @@ app.listen(3000);
|
||||
```
|
||||
|
||||
Now you can compile and run your project.
|
||||
You should have a express server running now with working routes.
|
||||
However those routes do not return any content yet.
|
||||
You should have an express server running now with working routes.
|
||||
However, those routes do not return any content yet.
|
||||
|
||||
## Adding TypeORM to the application
|
||||
|
||||
Finally lets add TypeORM to the application.
|
||||
In this example we will use `mysql` driver.
|
||||
Finally, let's add TypeORM to the application.
|
||||
In this example, we will use `mysql` driver.
|
||||
Setup process for other drivers is similar.
|
||||
|
||||
Let's install the required packages first:
|
||||
@ -242,7 +242,7 @@ export function UsersListAction(req: Request, res: Response) {
|
||||
}
|
||||
```
|
||||
|
||||
You even don't need `getConnection` in this example - you can directly use `getRepository` function:
|
||||
You don't even need `getConnection` in this example - you can directly use the `getRepository` function:
|
||||
|
||||
```typescript
|
||||
import {getRepository} from "typeorm";
|
||||
|
||||
58
docs/faq.md
58
docs/faq.md
@ -13,8 +13,8 @@
|
||||
|
||||
## How do I update a database schema?
|
||||
|
||||
One of the main responsibility of TypeORM is to keep your database tables in sync with your entities.
|
||||
There are two ways that help you to achieve this:
|
||||
One of the main responsibilities of TypeORM is to keep your database tables in sync with your entities.
|
||||
There are two ways that help you achieve this:
|
||||
|
||||
* Use `synchronize: true` in your connection options:
|
||||
|
||||
@ -26,7 +26,7 @@ There are two ways that help you to achieve this:
|
||||
});
|
||||
```
|
||||
|
||||
This option automatically synces your database tables with the given entities each time you run this code.
|
||||
This option automatically syncs your database tables with the given entities each time you run this code.
|
||||
This option is perfect during development, but in production you may not want this option to be enabled.
|
||||
|
||||
* Use command line tools and run schema sync manually in the command line:
|
||||
@ -36,15 +36,15 @@ There are two ways that help you to achieve this:
|
||||
```
|
||||
|
||||
This command will execute schema synchronization.
|
||||
Note, to make command line tools to work, you must create a ormconfig.json file.
|
||||
Note, to make command line tools work, you must create an ormconfig.json file.
|
||||
|
||||
Schema sync is extremely fast.
|
||||
If you are considering to disable synchronize option during development because of performance issues,
|
||||
If you are considering the disable synchronize option during development because of performance issues,
|
||||
first check how fast it is.
|
||||
|
||||
## How do I change a column name in the database?
|
||||
|
||||
By default column names are generated from property names.
|
||||
By default, column names are generated from property names.
|
||||
You can simply change it by specifying a `name` column option:
|
||||
|
||||
```typescript
|
||||
@ -108,36 +108,36 @@ export class Photo {
|
||||
```
|
||||
|
||||
This example does not have a `@JoinColumn` which is incorrect.
|
||||
Why? Because to make a real relation we need to create a column in the database.
|
||||
Why? Because to make a real relation, we need to create a column in the database.
|
||||
We need to create a column `userId` in `photo` or `photoId` in `user`.
|
||||
But which column should be created - `userId` or `photoId`?
|
||||
TypeORM cannot decide it for you.
|
||||
To make a decision you must use `@JoinColumn` on one of the sides.
|
||||
TypeORM cannot decide for you.
|
||||
To make a decision, you must use `@JoinColumn` on one of the sides.
|
||||
If you put `@JoinColumn` in `Photo` then a column called `userId` will be created in the `photo` table.
|
||||
If you put `@JoinColumn` in `User` then a column called `photoId` will be created in the `user` table.
|
||||
The side with `@JoinColumn` will be called "owner side of the relationship".
|
||||
The other side of the relation, without `@JoinColumn` is called "inverse (non-owner) side of relationship".
|
||||
The side with `@JoinColumn` will be called the "owner side of the relationship".
|
||||
The other side of the relation, without `@JoinColumn`, is called the "inverse (non-owner) side of relationship".
|
||||
|
||||
Same in a `@ManyToMany` relation you use `@JoinTable` to show the owner side of the relation.
|
||||
It is the same in a `@ManyToMany` relation. You use `@JoinTable` to show the owner side of the relation.
|
||||
|
||||
In `@ManyToOne` or `@OneToMany` relations `@JoinColumn` is not necessary because
|
||||
both decorators are different and where you put `@ManyToOne` decorator that table will have relational column.
|
||||
In `@ManyToOne` or `@OneToMany` relations, `@JoinColumn` is not necessary because
|
||||
both decorators are different, and the table where you put the `@ManyToOne` decorator will have the relational column.
|
||||
|
||||
`@JoinColumn` and `@JoinTable` decorators can also be used to specify additional
|
||||
join column / junction table settings, like join column name or junction table name.
|
||||
|
||||
## How do I add extra columns into many-to-many (junction) table?
|
||||
|
||||
Its not possible to add extra columns into the table created by a many-to-many relation.
|
||||
You'll need to create a separate entity and bind it using two many-to-one relations with target entities
|
||||
(effect will be same as creating a many-to-many table),
|
||||
It's not possible to add extra columns into a table created by a many-to-many relation.
|
||||
You'll need to create a separate entity and bind it using two many-to-one relations with the target entities
|
||||
(the effect will be same as creating a many-to-many table),
|
||||
and add extra columns in there.
|
||||
|
||||
## How to use TypeORM with a dependency injection tool?
|
||||
|
||||
In TypeORM you can use service containers. Service containers allow you to inject custom services in some places, like in subscribers or custom naming strategies. Or for example, you can get access to ConnectionManager from any place using a service container.
|
||||
In TypeORM you can use service containers. Service containers allow you to inject custom services in some places, like in subscribers or custom naming strategies. For example, you can get access to ConnectionManager from any place using a service container.
|
||||
|
||||
Here is a example for how you can setup typedi service containers with TypeORM. But note, that you can setup any service container with TypeORM.
|
||||
Here is an example for how you can set up typedi service containers with TypeORM. Note: you can setup any service container with TypeORM.
|
||||
|
||||
```typescript
|
||||
import {useContainer, createConnection} from "typeorm";
|
||||
@ -150,20 +150,20 @@ createConnection({/* ... */});
|
||||
|
||||
## How to handle outDir TypeScript compiler option?
|
||||
|
||||
When you are using the `outDir` compiler option don't forget to copy assets and resources your app is using into the output directory.
|
||||
Or make sure to setup correct paths to those assets.
|
||||
When you are using the `outDir` compiler option, don't forget to copy assets and resources your app is using into the output directory.
|
||||
Otherwise, make sure to setup correct paths to those assets.
|
||||
|
||||
One important thing to know is, that when you remove or move entities, the old entities are left untouched inside the ouput directory.
|
||||
For example you create a `Post` entity and rename it to `Blog`.
|
||||
You no longer have `Post.ts` in your project, however `Post.js` is left inside the output directory.
|
||||
Now when TypeORM reads entities from your output directory it sees two entities - `Post` and `Blog`.
|
||||
One important thing to know is that when you remove or move entities, the old entities are left untouched inside the ouput directory.
|
||||
For example, you create a `Post` entity and rename it to `Blog`,
|
||||
you no longer have `Post.ts` in your project. However, `Post.js` is left inside the output directory.
|
||||
Now, when TypeORM reads entities from your output directory, it sees two entities - `Post` and `Blog`.
|
||||
This may be a source of bugs.
|
||||
That's why when you remove and move entities with `outDir` enabled its strongly recommended to remove your output directory and recompile the project again.
|
||||
That's why when you remove and move entities with `outDir` enabled, it's strongly recommended to remove your output directory and recompile the project again.
|
||||
|
||||
## How to use TypeORM with ts-node?
|
||||
|
||||
You can prevent compiling files each time using [ts-node](https://github.com/TypeStrong/ts-node).
|
||||
If you are using ts-node you can specify `ts` entities inside your connection options:
|
||||
If you are using ts-node, you can specify `ts` entities inside your connection options:
|
||||
|
||||
```
|
||||
{
|
||||
@ -174,9 +174,9 @@ If you are using ts-node you can specify `ts` entities inside your connection op
|
||||
|
||||
Also, if you are compiling js files into the same folder where your typescript files are,
|
||||
make sure to use the `outDir` compiler option to prevent
|
||||
[this issues](https://github.com/TypeStrong/ts-node/issues/432).
|
||||
[this issue](https://github.com/TypeStrong/ts-node/issues/432).
|
||||
|
||||
Also if you want to use the ts-node CLI you can execute TypeORM the following way:
|
||||
Also, if you want to use the ts-node CLI, you can execute TypeORM the following way:
|
||||
|
||||
```
|
||||
ts-node ./node_modules/bin/typeorm schema:sync
|
||||
|
||||
@ -71,7 +71,7 @@ export class Post {
|
||||
### `@BeforeUpdate`
|
||||
|
||||
You can define a method with any name in the entity and mark it with `@BeforeUpdate`
|
||||
and TypeORM will call it before an existing entity is updated using repository/manager `save`.
|
||||
and TypeORM will call it before an existing entity is updated using repository/manager `save`. Keep in mind, however, that this will occur only when information is changed in the model. If you run `save` without modifying anything from the model, `@BeforeUpdate` and `@AfterUpdate` will not run.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
|
||||
@ -48,12 +48,12 @@ If you want to enable logging of failed queries only then only add `error`:
|
||||
|
||||
There are other options you can use:
|
||||
|
||||
* `query` - log all queries
|
||||
* `error` - log all failed queries and error
|
||||
* `schema` - log the schema build process
|
||||
* `warn` - log internal orm warnings
|
||||
* `info` - log internal orm informative messages
|
||||
* `log` - log internal orm log messages
|
||||
* `query` - logs all queries.
|
||||
* `error` - logs all failed queries and errors.
|
||||
* `schema` - logs the schema build process.
|
||||
* `warn` - logs internal orm warnings.
|
||||
* `info` - logs internal orm informative messages.
|
||||
* `log` - logs internal orm log messages.
|
||||
|
||||
You can specify as many options as needed.
|
||||
If you want to enable all logging you can simply specify `logging: "all"`:
|
||||
@ -68,7 +68,7 @@ If you want to enable all logging you can simply specify `logging: "all"`:
|
||||
|
||||
## Log long-running queries
|
||||
|
||||
If you have performance issues you can log queries that take too much time to execute
|
||||
If you have performance issues, you can log queries that take too much time to execute
|
||||
by setting `maxQueryExecutionTime` in connection options:
|
||||
|
||||
```typescript
|
||||
@ -85,12 +85,12 @@ This code will log all queries which run more then `1 second`.
|
||||
|
||||
TypeORM ships with 4 different types of logger:
|
||||
|
||||
* `advanced-console` - this is default which logs all messages onto the console using color
|
||||
and sql syntax highlighting (using [chalk](https://github.com/chalk/chalk))
|
||||
* `advanced-console` - this is the default logger which logs all messages into the console using color
|
||||
and sql syntax highlighting (using [chalk](https://github.com/chalk/chalk)).
|
||||
* `simple-console` - this is a simple console logger which is exactly the same as the advanced logger, but it does not use any color highlighting.
|
||||
This logger can be used if you have problems / or don't like colorized logs
|
||||
* `file` - this logger writes all logs into `ormlogs.log` in the root folder of your project (near `package.json` and `ormconfig.json`)
|
||||
* `debug` - this logger uses [debug package](https://github.com/visionmedia/debug), to turn on logging set env variable `DEBUG=typeorm:*` (note logging option has no effect on this logger)
|
||||
This logger can be used if you have problems / or don't like colorized logs.
|
||||
* `file` - this logger writes all logs into `ormlogs.log` in the root folder of your project (near `package.json` and `ormconfig.json`).
|
||||
* `debug` - this logger uses [debug package](https://github.com/visionmedia/debug), to turn on logging set your env variable `DEBUG=typeorm:*` (note logging option has no effect on this logger).
|
||||
|
||||
You can enable any of them in connection options:
|
||||
|
||||
@ -135,8 +135,8 @@ createConnection({
|
||||
});
|
||||
```
|
||||
|
||||
If you defined your connection options in `ormconfig` file,
|
||||
then you can use it and override following way:
|
||||
If you defined your connection options in the `ormconfig` file,
|
||||
then you can use it and override it in the following way:
|
||||
|
||||
```typescript
|
||||
import {createConnection, getConnectionOptions} from "typeorm";
|
||||
@ -152,8 +152,8 @@ getConnectionOptions().then(connectionOptions => {
|
||||
});
|
||||
```
|
||||
|
||||
Logger methods can accept `QueryRunner` when its available. Its helpful if you want to log additional data.
|
||||
Also via query runner you can get access to additional data passed during persist/remove. For example:
|
||||
Logger methods can accept `QueryRunner` when it's available. It's helpful if you want to log additional data.
|
||||
Also, via query runner, you can get access to additional data passed during persist/remove. For example:
|
||||
|
||||
```typescript
|
||||
// user sends request during entity save
|
||||
@ -164,4 +164,4 @@ logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
|
||||
const requestUrl = queryRunner && queryRunner.data["request"] ? "(" + queryRunner.data["request"].url + ") " : "";
|
||||
console.log(requestUrl + "executing query: " + sql);
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@ -71,8 +71,8 @@ Before creating a new migration you need to setup your connection options proper
|
||||
|
||||
Here we setup two options:
|
||||
|
||||
* `"migrations": ["migration/*.js"]` - indicates that typeorm must load migrations from the given "migration" directory
|
||||
* `"cli": { "migrationsDir": "migration" }` - indicates that the CLI must create new migrations in the "migration" directory
|
||||
* `"migrations": ["migration/*.js"]` - indicates that typeorm must load migrations from the given "migration" directory.
|
||||
* `"cli": { "migrationsDir": "migration" }` - indicates that the CLI must create new migrations in the "migration" directory.
|
||||
|
||||
Once you setup connection options you can create a new migration using CLI:
|
||||
|
||||
@ -80,12 +80,12 @@ Once you setup connection options you can create a new migration using CLI:
|
||||
typeorm migrations:create -n PostRefactoring
|
||||
```
|
||||
|
||||
To use CLI commands you need to install typeorm globally (`npm i typeorm -g`).
|
||||
Also make sure your locally typeorm version matches the global version.
|
||||
To use CLI commands, you need to install typeorm globally (`npm i typeorm -g`).
|
||||
Also, make sure your local typeorm version matches the global version.
|
||||
Learn more about the [TypeORM CLI](./using-cli.md).
|
||||
|
||||
Here, `PostRefactoring` is the name of the migration - you can specify any name you want.
|
||||
After you run the command you can see a new file generated in "migration" directory,
|
||||
After you run the command you can see a new file generated in the "migration" directory
|
||||
named `{TIMESTAMP}-PostRefactoring.ts` where `{TIMESTAMP}` is the current timestamp when the migration was generated.
|
||||
Now you can open the file and add your migration sql queries there.
|
||||
|
||||
@ -114,7 +114,7 @@ There are two methods you must fill with your migration code: `up` and `down`.
|
||||
`down` method is used to revert the last migration.
|
||||
|
||||
Inside both `up` and `down` you have a `QueryRunner` object.
|
||||
All database operations are executing using this object.
|
||||
All database operations are executed using this object.
|
||||
Learn more about [query runner](./query-runner.md).
|
||||
|
||||
Let's see what the migration looks like with our `Post` changes:
|
||||
@ -138,7 +138,7 @@ export class PostRefactoringTIMESTAMP implements MigrationInterface {
|
||||
|
||||
## Running and reverting migrations
|
||||
|
||||
Once you have a migration to run on production you can run them using a CLI command:
|
||||
Once you have a migration to run on production, you can run them using a CLI command:
|
||||
|
||||
```
|
||||
typeorm migrations:run
|
||||
@ -161,14 +161,14 @@ If you need to revert multiple migrations you must call this command multiple ti
|
||||
|
||||
TypeORM is able to automatically generate migration files with schema changes you made.
|
||||
|
||||
Let's say you have a `Post` entity with a `title` column. And you have changed the name `title` to `name`.
|
||||
Let's say you have a `Post` entity with a `title` column, and you have changed the name `title` to `name`.
|
||||
You can run following command:
|
||||
|
||||
```
|
||||
typeorm migrations:generate -n PostRefactoring
|
||||
```
|
||||
|
||||
And it will generate a new migration called `{TIMESTAMP}-PostRefactoring.ts` with following content:
|
||||
And it will generate a new migration called `{TIMESTAMP}-PostRefactoring.ts` with the following content:
|
||||
|
||||
```typescript
|
||||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
@ -190,7 +190,7 @@ const timber = await manager.findOne(User, { firstName: "Timber", lastName: "Saw
|
||||
Just like separate like `MongoEntityManager` there is a `MongoRepository` with extended `Repository`:
|
||||
|
||||
```typescript
|
||||
import {getMongoManager} from "typeorm";
|
||||
import {getMongoRepository} from "typeorm";
|
||||
|
||||
const userRepository = getMongoRepository(User); // or connection.getMongoManager
|
||||
const timber = await userRepository.findOne({ firstName: "Timber", lastName: "Saw" });
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# Working with Relations
|
||||
|
||||
`RelationQueryBuilder` is special type of `QueryBuilder` which allows you to work with your relations.
|
||||
Using it you can bind entities to each other in the database without need to load any entities.
|
||||
Or you can load related entities easily.
|
||||
`RelationQueryBuilder` is a special type of `QueryBuilder` which allows you to work with your relations.
|
||||
Using it, you can bind entities to each other in the database without the need to load any entities,
|
||||
or you can load related entities easily.
|
||||
Examples:
|
||||
|
||||
For example we have a `Post` entity and it has a many-to-many relation to `Category` called `categories`.
|
||||
For example, we have a `Post` entity and it has a many-to-many relation to `Category` called `categories`.
|
||||
Let's add a new category to this many-to-many relation:
|
||||
|
||||
```typescript
|
||||
@ -29,17 +29,17 @@ post.categories.push(category);
|
||||
await postRepository.save(post);
|
||||
```
|
||||
|
||||
But more efficient because it does a minimal number of operations and binds entities in the database,
|
||||
unlike calling bulky `save` method call.
|
||||
But more efficient, because it does a minimal number of operations, and binds entities in the database,
|
||||
unlike calling a bulky `save` method call.
|
||||
|
||||
Also, other benefit of such an approach is that you don't need to load every related entity before pushing into it.
|
||||
For example if you have ten thousand categories inside a single post, adding a new post to this list may become problematic for you,
|
||||
because the standard way of doing this, is to load the post with all ten thousand categories, push a new category
|
||||
and save it. This results in very heavy performance and basically inapplicable in production results.
|
||||
Also, another benefit of such an approach is that you don't need to load every related entity before pushing into it.
|
||||
For example, if you have ten thousand categories inside a single post, adding new posts to this list may become problematic for you,
|
||||
because the standard way of doing this is to load the post with all ten thousand categories, push a new category,
|
||||
and save it. This results in very heavy performance costs and is basically inapplicable in production results.
|
||||
However, using `RelationQueryBuilder` solves this problem.
|
||||
|
||||
Also, there is no real need to use entities when you "bind" things, you can use entity ids instead.
|
||||
For example, lets add a category with id = 3 into post with id = 1:
|
||||
Also, there is no real need to use entities when you "bind" things, since you can use entity ids instead.
|
||||
For example, let's add a category with id = 3 into post with id = 1:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -51,7 +51,7 @@ await getConnection()
|
||||
.add(3);
|
||||
```
|
||||
|
||||
If you are using composite primary keys you have to pass them as id map, for example:
|
||||
If you are using composite primary keys, you have to pass them as an id map, for example:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -63,7 +63,7 @@ await getConnection()
|
||||
.add({ firstCategoryId: 2, secondCategoryId: 4 });
|
||||
```
|
||||
|
||||
Same way you add new entities, you can remove them:
|
||||
You can remove entities the same way you add them:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -104,8 +104,8 @@ await getConnection()
|
||||
```
|
||||
|
||||
Besides updating relations, the relational query builder also allows you to load relational entities.
|
||||
For example, lets say inside a `Post` entity we have a many-to-many `categories` relation and a many-to-one `user` relation.
|
||||
To load those relations you can use following code:
|
||||
For example, lets say inside a `Post` entity we have a many-to-many `categories` relation and a many-to-one `user` relation,
|
||||
to load those relations you can use following code:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
|
||||
@ -86,20 +86,20 @@ question.categories = [category1, category2];
|
||||
await connection.manager.save(question);
|
||||
```
|
||||
|
||||
As you can see in this example we did not called `save` for `category1` and `category2`.
|
||||
As you can see in this example we did not call `save` for `category1` and `category2`.
|
||||
They will be automatically inserted, because we set `cascade` to true.
|
||||
|
||||
Keep in mind - great power comes with great responsibility.
|
||||
Cascades may seem a good and easy way to work with relations,
|
||||
Cascades may seem like a good and easy way to work with relations,
|
||||
but they may also bring bugs and security issues when some undesired object is being saved into the database.
|
||||
Also, they provide a less explicit way of saving new objects into the database.
|
||||
|
||||
## `@JoinColumn` options
|
||||
|
||||
`@JoinColumn` not only defines which side of the relation contains the join column with a foreign key,
|
||||
but also allows to customize join column name and referenced column name.
|
||||
but also allows you to customize join column name and referenced column name.
|
||||
|
||||
When we set `@JoinColumn` it creates a column in the database automatically named `propertyName + referencedColumnName`.
|
||||
When we set `@JoinColumn`, it automtically creates a column in the database named `propertyName + referencedColumnName`.
|
||||
For example:
|
||||
|
||||
```typescript
|
||||
@ -117,7 +117,7 @@ If you want to change this name in the database you can specify a custom join co
|
||||
category: Category;
|
||||
```
|
||||
|
||||
Join columns are always a reference to some columns (using a foreign key).
|
||||
Join columns are always a reference to some other columns (using a foreign key).
|
||||
By default your relation always refers to the primary column of the related entity.
|
||||
If you want to create relation with other columns of the related entity -
|
||||
you can specify them in `@JoinColumn` as well:
|
||||
@ -129,14 +129,14 @@ category: Category;
|
||||
```
|
||||
|
||||
The relation now refers to `name` of the `Category` entity, instead of `id`.
|
||||
Column name for such relation will become `categoryName`
|
||||
Column name for that relation will become `categoryName`
|
||||
|
||||
## `@JoinTable` options
|
||||
|
||||
`@JoinTable` is used for `many-to-many` relations and describes join columns of the "junction" table.
|
||||
A junction table is a special separate table created automatically by TypeORM with columns that refer to the related entities.
|
||||
You can change column names inside junction tables and their referenced columns with `@JoinColumn`:
|
||||
You can also change the ame of the generated "junction" table.
|
||||
You can also change the name of the generated "junction" table.
|
||||
|
||||
```typescript
|
||||
@ManyToMany(type => Category)
|
||||
@ -154,5 +154,5 @@ You can also change the ame of the generated "junction" table.
|
||||
categories: Category[];
|
||||
```
|
||||
|
||||
If destination table has composite primary keys,
|
||||
then array of properties must be send to `@JoinTable`.
|
||||
If the destination table has composite primary keys,
|
||||
then an array of properties must be sent to `@JoinTable`.
|
||||
|
||||
@ -70,16 +70,16 @@ const user = repository.create({
|
||||
}); // same as const user = new User(); user.firstName = "Timber"; user.lastName = "Saw";
|
||||
```
|
||||
|
||||
* `merge` - Merges multiple entities into a single entity
|
||||
* `merge` - Merges multiple entities into a single entity.
|
||||
|
||||
```typescript
|
||||
const user = new User();
|
||||
repository.merge(user, { firstName: "Timber" }, { lastName: "Saw" }); // same as user.firstName = "Timber"; user.lastName = "Saw";
|
||||
```
|
||||
|
||||
* `preload` - Creates a new entity from the given plain javascript object. If the entity already exist in the database, then
|
||||
it loads it (and everything related to it), replaces all values with the new ones from the given object
|
||||
and returns the new entity. The new entity is actually an entity loaded from the db with all properties
|
||||
* `preload` - Creates a new entity from the given plain javascript object. If the entity already exists in the database, then
|
||||
it loads it (and everything related to it), replaces all values with the new ones from the given object,
|
||||
and returns the new entity. The new entity is actually an entity loaded from the database with all properties
|
||||
replaced from the new object.
|
||||
|
||||
```typescript
|
||||
@ -98,7 +98,7 @@ const user = await repository.preload(partialUser);
|
||||
* `save` - Saves a given entity or array of entities.
|
||||
If the entity already exist in the database, it is updated.
|
||||
If the entity does not exist in the database, it is inserted.
|
||||
It saves all given entities in a single transaction (in the case of entity manager is not transactional).
|
||||
It saves all given entities in a single transaction (in the case of entity, manager is not transactional).
|
||||
Also supports partial updating since all undefined properties are skipped.
|
||||
|
||||
```typescript
|
||||
@ -111,7 +111,7 @@ await repository.save([
|
||||
```
|
||||
|
||||
* `remove` - Removes a given entity or array of entities.
|
||||
It removes all given entities in a single transaction (in the case of entity manager is not transactional).
|
||||
It removes all given entities in a single transaction (in the case of entity, manager is not transactional).
|
||||
|
||||
```typescript
|
||||
await repository.remove(user);
|
||||
@ -125,9 +125,9 @@ await repository.remove([
|
||||
* `insert` - Inserts a new entity.
|
||||
|
||||
```typescript
|
||||
await repository.insert({
|
||||
firstName: "Timber",
|
||||
lastName: "Timber"
|
||||
await repository.insert({
|
||||
firstName: "Timber",
|
||||
lastName: "Timber"
|
||||
});
|
||||
```
|
||||
|
||||
@ -204,79 +204,8 @@ await repository.clear();
|
||||
|
||||
## `TreeRepository` API
|
||||
|
||||
* `findTrees` - Gets complete tree for all roots in the table.
|
||||
|
||||
```typescript
|
||||
const treeCategories = await repository.findTrees();
|
||||
// returns root categories with sub categories inside
|
||||
```
|
||||
|
||||
* `findRoots` - Roots are entities that have no ancestors. Finds them all.
|
||||
Does not load children leafs.
|
||||
|
||||
```typescript
|
||||
const rootCategories = await repository.findRoots();
|
||||
// returns root categories without sub categories inside
|
||||
```
|
||||
|
||||
* `findDescendants` - Gets all children (descendants) of the given entity. Returns them all in a flat array.
|
||||
|
||||
```typescript
|
||||
const childrens = await repository.findDescendants(parentCategory);
|
||||
// returns all direct subcategories (without its nested categories) of a parentCategory
|
||||
```
|
||||
|
||||
* `findDescendantsTree` - Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.
|
||||
|
||||
```typescript
|
||||
const childrensTree = await repository.findDescendantsTree(parentCategory);
|
||||
// returns all direct subcategories (with its nested categories) of a parentCategory
|
||||
```
|
||||
|
||||
* `createDescendantsQueryBuilder` - Creates a query builder used to get descendants of the entities in a tree.
|
||||
|
||||
```typescript
|
||||
const childrens = await repository
|
||||
.createDescendantsQueryBuilder("category", "categoryClosure", parentCategory)
|
||||
.andWhere("category.type = 'secondary'")
|
||||
.getMany();
|
||||
```
|
||||
|
||||
* `countDescendants` - Gets number of descendants of the entity.
|
||||
|
||||
```typescript
|
||||
const childrenCount = await repository.countDescendants(parentCategory);
|
||||
```
|
||||
|
||||
* `findAncestors` - Gets all parent (ancestors) of the given entity. Returns them all in a flat array.
|
||||
|
||||
```typescript
|
||||
const parents = await repository.findAncestors(childCategory);
|
||||
// returns all direct childCategory's parent categories (without "parent of parents")
|
||||
```
|
||||
|
||||
* `findAncestorsTree` - Gets all parent (ancestors) of the given entity. Returns them in a tree - nested into each other.
|
||||
|
||||
```typescript
|
||||
const parentsTree = await repository.findAncestorsTree(childCategory);
|
||||
// returns all direct childCategory's parent categories (with "parent of parents")
|
||||
```
|
||||
|
||||
* `createAncestorsQueryBuilder` - Creates a query builder used to get ancestors of the entities in a tree.
|
||||
|
||||
```typescript
|
||||
const parents = await repository
|
||||
.createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
|
||||
.andWhere("category.type = 'secondary'")
|
||||
.getMany();
|
||||
```
|
||||
|
||||
* `countAncestors` - Gets the number of ancestors of the entity.
|
||||
|
||||
```typescript
|
||||
const parentsCount = await repository.countAncestors(childCategory);
|
||||
```
|
||||
For `TreeRepository` API refer to [the Tree Entities documentation](./tree-entities.md#working-with-tree-entities).
|
||||
|
||||
## `MongoRepository` API
|
||||
|
||||
For `MongoRepository` API refer to [the MongoDB documentation](./mongodb.md).
|
||||
For `MongoRepository` API refer to [the MongoDB documentation](./mongodb.md).
|
||||
|
||||
@ -4,18 +4,15 @@ See what amazing new features we are expecting to land in the next TypeORM versi
|
||||
|
||||
## Note on 1.0.0 release
|
||||
|
||||
We are planning to release a final stable `1.0.0` version somewhere in summer 2018.
|
||||
However TypeORM is already actively used in number of big production systems.
|
||||
Main API is already very stable, there are only few issues currently we have in following areas:
|
||||
`class and single table inheritance`, `naming strategy`, `subscribers`, `tree tables`.
|
||||
All issues in those areas are planning to be fixed in next minor versions.
|
||||
Your donations and contribution play a big role in achieving this goal.
|
||||
TypeORM follows a semantic versioning and until `1.0.0` breaking changes may appear in `0.x.x` versions,
|
||||
however since API is already quite stable we don't expect too much breaking changes.
|
||||
We are planning to release a final stable `1.0.0` version somewhere in Summer 2018.
|
||||
However, TypeORM is already actively used in a number of big production systems.
|
||||
The main API is already very stable.
|
||||
TypeORM follows a semantic versioning and until `1.0.0`, breaking changes may appear in `0.x.x` versions.
|
||||
However, since the API is already quite stable we don't expect too many breaking changes.
|
||||
|
||||
## How to install latest development version?
|
||||
|
||||
To install latest development version use following command:
|
||||
To install latest development version use the following command:
|
||||
|
||||
```
|
||||
npm i typeorm@next
|
||||
@ -23,15 +20,11 @@ npm i typeorm@next
|
||||
|
||||
## 0.3.0
|
||||
|
||||
- [ ] fix Oracle driver issues and make oracle stable and ready for production use
|
||||
- [ ] add `@Select` and `@Where` decorators
|
||||
- [ ] add `addSelectAndMap` functionality to `QueryBuilder`
|
||||
- [ ] research NativeScript support
|
||||
- [ ] research internationalization features
|
||||
- [ ] implement soft deletion
|
||||
- [ ] research ability to create one-to-many relations without inverse sides
|
||||
- [ ] research ability to create a single relation with multiple entities at once
|
||||
- [ ] add more tree-table features: nested set and materialized path; more repository methods
|
||||
- [ ] cli: create database backup command
|
||||
- [ ] extend `query` method functionality
|
||||
- [ ] better support for entity schemas, support inheritance, add xml and yml formats support
|
||||
@ -41,6 +34,10 @@ npm i typeorm@next
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- [ ] research NativeScript support
|
||||
- [x] implement soft deletion
|
||||
- [x] add more tree-table features: nested set and materialized path; more repository methods
|
||||
- [ ] fix Oracle driver issues and make oracle stable and ready for production use
|
||||
- [ ] implement migrations generator for all drivers
|
||||
- [ ] create example how to use TypeORM in Electron apps
|
||||
- [ ] finish naming strategy implementation
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
* [Set locking](#set-locking)
|
||||
* [Partial selection](#partial-selection)
|
||||
* [Using subqueries](#using-subqueries)
|
||||
* [Hidden Columns](#hidden-columns)
|
||||
|
||||
## What is `QueryBuilder`
|
||||
|
||||
@ -100,9 +101,9 @@ There are several ways how you can create a `Query Builder`:
|
||||
.getOne();
|
||||
```
|
||||
|
||||
There are 5 diffrent `QueryBuilder`s available:
|
||||
There are 5 different `QueryBuilder` types available:
|
||||
|
||||
* `SelectQueryBuilder` used to build and execute `SELECT` queries. Example:
|
||||
* `SelectQueryBuilder` - used to build and execute `SELECT` queries. Example:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -115,7 +116,7 @@ There are 5 diffrent `QueryBuilder`s available:
|
||||
.getOne();
|
||||
```
|
||||
|
||||
* `InsertQueryBuilder` used to build and execute `INSERT` queries. Example:
|
||||
* `InsertQueryBuilder` - used to build and execute `INSERT` queries. Example:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -131,7 +132,7 @@ There are 5 diffrent `QueryBuilder`s available:
|
||||
.execute();
|
||||
```
|
||||
|
||||
* `UpdateQueryBuilder` used to build and execute `UPDATE` queries. Example:
|
||||
* `UpdateQueryBuilder` - used to build and execute `UPDATE` queries. Example:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -143,7 +144,7 @@ There are 5 diffrent `QueryBuilder`s available:
|
||||
.where("id = :id", { id: 1 })
|
||||
.execute();
|
||||
```
|
||||
* `DeleteQueryBuilder` used to build and execute `DELETE` queries. Example:
|
||||
* `DeleteQueryBuilder` - used to build and execute `DELETE` queries. Example:
|
||||
|
||||
```typescript
|
||||
import {getConnection} from "typeorm";
|
||||
@ -156,15 +157,15 @@ There are 5 diffrent `QueryBuilder`s available:
|
||||
.execute();
|
||||
```
|
||||
|
||||
* `RelationQueryBuilder` used to build and execute relation-specific operations [TBD].
|
||||
* `RelationQueryBuilder` - used to build and execute relation-specific operations [TBD].
|
||||
|
||||
You can switch between different types of query builder within any of them,
|
||||
once you do it - you will get a new instance of query builder (unlike all other methods).
|
||||
once you do, you will get a new instance of query builder (unlike all other methods).
|
||||
|
||||
## Getting values using `QueryBuilder`
|
||||
|
||||
To get a single result from the database,
|
||||
for example to get a user by id or name you must use `getOne`:
|
||||
for example to get a user by id or name, you must use `getOne`:
|
||||
|
||||
```typescript
|
||||
const timber = await getRepository(User)
|
||||
@ -174,7 +175,7 @@ const timber = await getRepository(User)
|
||||
```
|
||||
|
||||
To get multiple results from the database,
|
||||
for example to get all users from the database use `getMany`:
|
||||
for example, to get all users from the database, use `getMany`:
|
||||
|
||||
```typescript
|
||||
const users = await getRepository(User)
|
||||
@ -183,11 +184,11 @@ const users = await getRepository(User)
|
||||
```
|
||||
|
||||
There are two types of results you can get using select query builder: **entities** or **raw results**.
|
||||
Most of the times you need to select real entities from your database, for example users.
|
||||
For this purpose you use `getOne` and `getMany`.
|
||||
Most of the time, you need to select real entities from your database, for example, users.
|
||||
For this purpose, you use `getOne` and `getMany`.
|
||||
But sometimes you need to select some specific data, let's say the *sum of all user photos*.
|
||||
This data is not an entity, its called raw data.
|
||||
To get raw data you use `getRawOne` and `getRawMany`.
|
||||
This data is not an entity, it's called raw data.
|
||||
To get raw data, you use `getRawOne` and `getRawMany`.
|
||||
Examples:
|
||||
|
||||
```typescript
|
||||
@ -213,7 +214,7 @@ const photosSums = await getRepository(User)
|
||||
|
||||
We used `createQueryBuilder("user")`. But what is "user"?
|
||||
It's just a regular SQL alias.
|
||||
We use aliases everywhere in except when we work with selected data.
|
||||
We use aliases everywhere, except when we work with selected data.
|
||||
|
||||
`createQueryBuilder("user")` is equivalent to:
|
||||
|
||||
@ -223,13 +224,13 @@ createQueryBuilder()
|
||||
.from(User, "user")
|
||||
```
|
||||
|
||||
Which will result into following sql query:
|
||||
Which will result in the following sql query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user
|
||||
```
|
||||
|
||||
In this SQL query `users` is the table name and `user` is an alias we assign to this table.
|
||||
In this SQL query, `users` is the table name, and `user` is an alias we assign to this table.
|
||||
Later we use this alias to access the table:
|
||||
|
||||
```typescript
|
||||
@ -239,15 +240,15 @@ createQueryBuilder()
|
||||
.where("user.name = :name", { name: "Timber" })
|
||||
```
|
||||
|
||||
Which produce following SQL query:
|
||||
Which produces the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user WHERE user.name = 'Timber'
|
||||
```
|
||||
|
||||
See, we used the users table using the `user` alias we assigned when we created a query builder.
|
||||
See, we used the users table by using the `user` alias we assigned when we created a query builder.
|
||||
|
||||
One query builder is not limited to one alias, they can have are multiple aliases.
|
||||
One query builder is not limited to one alias, they can have multiple aliases.
|
||||
Each select can have its own alias,
|
||||
you can select from multiple tables each with its own alias,
|
||||
you can join multiple tables each with its own alias.
|
||||
@ -256,9 +257,9 @@ You can use those aliases to access tables are you selecting (or data you are se
|
||||
## Using parameters to escape data
|
||||
|
||||
We used `where("user.name = :name", { name: "Timber" })`.
|
||||
What does `{ name: "Timber" }` stands for? It's a parameter we used to prevent SQL injection.
|
||||
What does `{ name: "Timber" }` stand for? It's a parameter we used to prevent SQL injection.
|
||||
We could have written: `where("user.name = '" + name + "')`,
|
||||
however this is not safe as it opens the code to SQL injections.
|
||||
however this is not safe, as it opens the code to SQL injections.
|
||||
The safe way is to use this special syntax: `where("user.name = :name", { name: "Timber" })`,
|
||||
where `:name` is a parameter name and the value is specified in an object: `{ name: "Timber" }`.
|
||||
|
||||
@ -282,7 +283,7 @@ createQueryBuilder("user")
|
||||
.where("user.name = :name", { name: "Timber" })
|
||||
```
|
||||
|
||||
Will produce:
|
||||
Which will produce:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user WHERE user.name = 'Timber'
|
||||
@ -296,13 +297,13 @@ createQueryBuilder("user")
|
||||
.andWhere("user.lastName = :lastName", { lastName: "Saw" });
|
||||
```
|
||||
|
||||
Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'
|
||||
```
|
||||
|
||||
You can add `OR` into an exist `WHERE` expression:
|
||||
You can add `OR` into an existing `WHERE` expression:
|
||||
|
||||
```typescript
|
||||
createQueryBuilder("user")
|
||||
@ -310,7 +311,7 @@ createQueryBuilder("user")
|
||||
.orWhere("user.lastName = :lastName", { lastName: "Saw" });
|
||||
```
|
||||
|
||||
Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'
|
||||
@ -319,9 +320,9 @@ SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'S
|
||||
You can combine as many `AND` and `OR` expressions as you need.
|
||||
If you use `.where` more than once you'll override all previous `WHERE` expressions.
|
||||
|
||||
Note: be careful with `orWhere` - if you use complex expressions with both `AND` and `OR` expressions
|
||||
Note: be careful with `orWhere` - if you use complex expressions with both `AND` and `OR` expressions,
|
||||
keep in mind that they are stacked without any pretences.
|
||||
Sometimes you'll need to create a where string instead and avoid using `orWhere`.
|
||||
Sometimes you'll need to create a where string instead, and avoid using `orWhere`.
|
||||
|
||||
## Adding `HAVING` expression
|
||||
|
||||
@ -332,7 +333,7 @@ createQueryBuilder("user")
|
||||
.having("user.name = :name", { name: "Timber" })
|
||||
```
|
||||
|
||||
Will produce following SQL query:
|
||||
Which will produce following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user HAVING user.name = 'Timber'
|
||||
@ -346,7 +347,7 @@ createQueryBuilder("user")
|
||||
.andHaving("user.lastName = :lastName", { lastName: "Saw" });
|
||||
```
|
||||
|
||||
Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'
|
||||
@ -360,7 +361,7 @@ createQueryBuilder("user")
|
||||
.orHaving("user.lastName = :lastName", { lastName: "Saw" });
|
||||
```
|
||||
|
||||
Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user HAVING user.firstName = 'Timber' OR user.lastName = 'Saw'
|
||||
@ -371,20 +372,20 @@ If you use `.having` more than once you'll override all previous `HAVING` expres
|
||||
|
||||
## Adding `ORDER BY` expression
|
||||
|
||||
Adding a `ORDER BY` expression is easy as:
|
||||
Adding an `ORDER BY` expression is easy as:
|
||||
|
||||
```typescript
|
||||
createQueryBuilder("user")
|
||||
.orderBy("user.id")
|
||||
```
|
||||
|
||||
Will produce:
|
||||
Which will produce:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user ORDER BY user.id
|
||||
```
|
||||
|
||||
You can change the order direction from ascendant to descendant (or versa):
|
||||
You can change the ordering direction from ascending to descending (or versa):
|
||||
|
||||
```typescript
|
||||
createQueryBuilder("user")
|
||||
@ -402,7 +403,7 @@ createQueryBuilder("user")
|
||||
.addOrderBy("user.id");
|
||||
```
|
||||
|
||||
You can also usea map of order-by fields:
|
||||
You can also use a map of order-by fields:
|
||||
|
||||
```typescript
|
||||
createQueryBuilder("user")
|
||||
@ -423,7 +424,7 @@ createQueryBuilder("user")
|
||||
.groupBy("user.id")
|
||||
```
|
||||
|
||||
This Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user GROUP BY user.id
|
||||
@ -453,28 +454,28 @@ Which will produce the following SQL query:
|
||||
SELECT ... FROM users user LIMIT 10
|
||||
```
|
||||
|
||||
The resulting SQL query depends of database type.
|
||||
Note LIMIT may not work as you may expect if you are using complex queries with joins or subqueries.
|
||||
If you are using pagination its recommended to use `take` instead.
|
||||
The resulting SQL query depends on the type of database (SQL, mySQL, Postgres, etc).
|
||||
Note: LIMIT may not work as you may expect if you are using complex queries with joins or subqueries.
|
||||
If you are using pagination, it's recommended to use `take` instead.
|
||||
|
||||
## Adding `OFFSET` expression
|
||||
|
||||
Adding SQL `OFFSET` expression is easy as:
|
||||
Adding an SQL `OFFSET` expression is easy as:
|
||||
|
||||
```typescript
|
||||
createQueryBuilder("user")
|
||||
.offset(10)
|
||||
```
|
||||
|
||||
Will produce the following SQL query:
|
||||
Which will produce the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT ... FROM users user OFFSET 10
|
||||
```
|
||||
|
||||
The resulting SQL query depends of database type.
|
||||
Note OFFSET may not work as you may expect if you are using complex queries with joins or subqueries.
|
||||
If you are using pagination its recommended to use `skip` instead.
|
||||
The resulting SQL query depends on the type of database (SQL, mySQL, Postgres, etc).
|
||||
Note: OFFSET may not work as you may expect if you are using complex queries with joins or subqueries.
|
||||
If you are using pagination, it's recommended to use `skip` instead.
|
||||
|
||||
## Joining relations
|
||||
|
||||
@ -516,7 +517,7 @@ export class Photo {
|
||||
}
|
||||
```
|
||||
|
||||
Now let's say you want to load user "Timber" with all his photos:
|
||||
Now let's say you want to load user "Timber" with all of his photos:
|
||||
|
||||
```typescript
|
||||
const user = await createQueryBuilder("user")
|
||||
@ -525,7 +526,7 @@ const user = await createQueryBuilder("user")
|
||||
.getOne();
|
||||
```
|
||||
|
||||
You'll get following result:
|
||||
You'll get the following result:
|
||||
|
||||
```typescript
|
||||
{
|
||||
@ -541,10 +542,10 @@ You'll get following result:
|
||||
}
|
||||
```
|
||||
|
||||
As you can see `leftJoinAndSelect` automatically loaded all of timber's photos.
|
||||
As you can see `leftJoinAndSelect` automatically loaded all of Timber's photos.
|
||||
The first argument is the relation you want to load and the second argument is an alias you assign to this relation's table.
|
||||
You can use this alias anywhere in query builder.
|
||||
For example, lets take all timber's photos which aren't removed.
|
||||
For example, let's take all Timber's photos which aren't removed.
|
||||
|
||||
```typescript
|
||||
const user = await createQueryBuilder("user")
|
||||
@ -571,7 +572,7 @@ const user = await createQueryBuilder("user")
|
||||
.getOne();
|
||||
```
|
||||
|
||||
This will generate following sql query:
|
||||
This will generate the following sql query:
|
||||
|
||||
```sql
|
||||
SELECT user.*, photo.* FROM users user
|
||||
@ -581,7 +582,7 @@ SELECT user.*, photo.* FROM users user
|
||||
|
||||
## Inner and left joins
|
||||
|
||||
If you want to use `INNER JOIN` instead of `JEFT JOIN` just use `innerJoinAndSelect` instead:
|
||||
If you want to use `INNER JOIN` instead of `LEFT JOIN` just use `innerJoinAndSelect` instead:
|
||||
|
||||
```typescript
|
||||
const user = await createQueryBuilder("user")
|
||||
@ -598,14 +599,14 @@ SELECT user.*, photo.* FROM users user
|
||||
WHERE user.name = 'Timber'
|
||||
```
|
||||
|
||||
Difference between `LEFT JOIN` and `INNER JOIN` is that `INNER JOIN` won't return a user if it does not have any photos.
|
||||
The difference between `LEFT JOIN` and `INNER JOIN` is that `INNER JOIN` won't return a user if it does not have any photos.
|
||||
`LEFT JOIN` will return you the user even if it doesn't have photos.
|
||||
To learn more about different join types refer to the SQL documentation.
|
||||
To learn more about different join types, refer to the [SQL documentation](https://msdn.microsoft.com/en-us/library/zt8wzxy4.aspx).
|
||||
|
||||
## Join without selection
|
||||
|
||||
You can join data without its selection.
|
||||
To do that use `leftJoin` or `innerJoin`:
|
||||
To do that, use `leftJoin` or `innerJoin`:
|
||||
|
||||
```typescript
|
||||
const user = await createQueryBuilder("user")
|
||||
@ -622,11 +623,11 @@ SELECT user.* FROM users user
|
||||
WHERE user.name = 'Timber'
|
||||
```
|
||||
|
||||
This will select timber if he has photos, but won't return his photos.
|
||||
This will select Timber if he has photos, but won't return his photos.
|
||||
|
||||
## Joining any entity or table
|
||||
|
||||
You can not only join relations, but also other not related entities or tables.
|
||||
You can join not only relations, but also other unrelated entities or tables.
|
||||
Examples:
|
||||
|
||||
```typescript
|
||||
@ -660,14 +661,14 @@ const user = await createQueryBuilder("user")
|
||||
.getOne();
|
||||
```
|
||||
|
||||
This will load timber's profile photo and set it to `user.profilePhoto`.
|
||||
This will load Timber's profile photo and set it to `user.profilePhoto`.
|
||||
If you want to load and map a single entity use `leftJoinAndMapOne`.
|
||||
If you want to load and map multiple entities use `leftJoinAndMapMany`.
|
||||
|
||||
## Getting the generated query
|
||||
|
||||
Sometimes you may want to get the SQL query generated by `QueryBuilder`.
|
||||
To do it use `getSql`:
|
||||
To do so, use `getSql`:
|
||||
|
||||
```typescript
|
||||
const sql = createQueryBuilder("user")
|
||||
@ -691,11 +692,11 @@ This query will return users and print the used sql statement to the console.
|
||||
## Getting raw results
|
||||
|
||||
There are two types of results you can get using select query builder: **entities** and **raw results**.
|
||||
Most of times you need to select real entities from your database, for example users.
|
||||
For this purpose you use `getOne` and `getMany`.
|
||||
But sometimes you need to select specific data, let's say the *sum of all user photos*.
|
||||
Such data is not a entity, its called raw data.
|
||||
To get raw data you use `getRawOne` and `getRawMany`.
|
||||
Most of the time, you need to select real entities from your database, for example, users.
|
||||
For this purpose, you use `getOne` and `getMany`.
|
||||
However, sometimes you need to select specific data, like the *sum of all user photos*.
|
||||
Such data is not a entity, it's called raw data.
|
||||
To get raw data, you use `getRawOne` and `getRawMany`.
|
||||
Examples:
|
||||
|
||||
```typescript
|
||||
@ -719,8 +720,8 @@ const photosSums = await getRepository(User)
|
||||
|
||||
## Streaming result data
|
||||
|
||||
You can use `stream` which returns you stream.
|
||||
Streaming returns you raw data and you must handle entities transformation manually:
|
||||
You can use `stream` which returns you a stream.
|
||||
Streaming returns you raw data and you must handle entity transformation manually:
|
||||
|
||||
```typescript
|
||||
const stream = await getRepository(User)
|
||||
@ -731,8 +732,8 @@ const stream = await getRepository(User)
|
||||
|
||||
## Using pagination
|
||||
|
||||
Most of the times when you develope an application you need pagination functionality.
|
||||
This is used if you have pagination, page slider or infinite scroll components in your application.
|
||||
Most of the time when you develop an application, you need pagination functionality.
|
||||
This is used if you have pagination, page slider, or infinite scroll components in your application.
|
||||
|
||||
```typescript
|
||||
const users = await getRepository(User)
|
||||
@ -752,7 +753,7 @@ const users = await getRepository(User)
|
||||
.getMany();
|
||||
```
|
||||
|
||||
This will give you all users with their photos except first 10.
|
||||
This will give you all except the first 10 users with their photos.
|
||||
You can combine those methods:
|
||||
|
||||
```typescript
|
||||
@ -767,14 +768,14 @@ const users = await getRepository(User)
|
||||
This will skip the first 5 users and take 10 users after them.
|
||||
|
||||
|
||||
`take` and `skip` may look like we are using `limit` and `offset`, but they don't.
|
||||
`take` and `skip` may look like we are using `limit` and `offset`, but they aren't.
|
||||
`limit` and `offset` may not work as you expect once you have more complicated queries with joins or subqueries.
|
||||
Using `take` and `skip` will prevent those issues.
|
||||
|
||||
## Set locking
|
||||
|
||||
QueryBuilder supports both optimistic and pessimistic locking.
|
||||
To use pessimistic read locking use following method:
|
||||
To use pessimistic read locking use the following method:
|
||||
|
||||
```typescript
|
||||
const users = await getRepository(User)
|
||||
@ -783,7 +784,7 @@ const users = await getRepository(User)
|
||||
.getMany();
|
||||
```
|
||||
|
||||
To use pessimistic write locking use following method:
|
||||
To use pessimistic write locking use the following method:
|
||||
|
||||
```typescript
|
||||
const users = await getRepository(User)
|
||||
@ -792,7 +793,7 @@ const users = await getRepository(User)
|
||||
.getMany();
|
||||
```
|
||||
|
||||
To use optimistic locking use following method:
|
||||
To use optimistic locking use the following method:
|
||||
|
||||
```typescript
|
||||
const users = await getRepository(User)
|
||||
@ -801,11 +802,11 @@ const users = await getRepository(User)
|
||||
.getMany();
|
||||
```
|
||||
|
||||
Optimistic locking works in conjunction with `@Version` and `@UpdatedDate` decorators.
|
||||
Optimistic locking works in conjunction with both `@Version` and `@UpdatedDate` decorators.
|
||||
|
||||
## Partial selection
|
||||
|
||||
If you want to select only some entity properties you can use the following syntax:
|
||||
If you want to select only some entity properties, you can use the following syntax:
|
||||
|
||||
````typescript
|
||||
const users = await getRepository(User)
|
||||
@ -817,7 +818,7 @@ const users = await getRepository(User)
|
||||
.getMany();
|
||||
````
|
||||
|
||||
This will only select `id` and `name` of `User`.
|
||||
This will only select the `id` and `name` of `User`.
|
||||
|
||||
## Using subqueries
|
||||
|
||||
@ -832,7 +833,7 @@ const posts = qb
|
||||
.getMany();
|
||||
```
|
||||
|
||||
More elegant way to do the same:
|
||||
A more elegant way to do the same:
|
||||
|
||||
```typescript
|
||||
const posts = await connection.getRepository(Post)
|
||||
@ -897,7 +898,7 @@ const posts = await connection
|
||||
|
||||
If you want to add a subselect as a "second from" use `addFrom`.
|
||||
|
||||
You can use subselects in a `SELECT` statements as well:
|
||||
You can use subselects in `SELECT` statements as well:
|
||||
|
||||
```typescript
|
||||
const posts = await connection
|
||||
@ -912,3 +913,37 @@ const posts = await connection
|
||||
.from(Post, "post")
|
||||
.getRawMany();
|
||||
```
|
||||
## Hidden Columns
|
||||
|
||||
If the model you are querying has a column with a `select: false` column, you must use the `addSelect` function in order to retreive the information from the column.
|
||||
|
||||
Let's say you have the following entity:
|
||||
|
||||
```typescript
|
||||
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
export class User {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column({select: false})
|
||||
password: string;
|
||||
}
|
||||
```
|
||||
|
||||
Using a standard `find` or query, you will not recieve the `name` property for the model. However, if you do the following:
|
||||
|
||||
```typescript
|
||||
const users = await connection.getRepository(User)
|
||||
.createQueryBuilder()
|
||||
.select("user.id", "id")
|
||||
.addSelect("user.password")
|
||||
.getMany();
|
||||
```
|
||||
|
||||
You will get the property `password` in your query.
|
||||
|
||||
@ -138,9 +138,9 @@ export class Task {
|
||||
}
|
||||
```
|
||||
|
||||
Its highly recommended to define one entity class per file.
|
||||
It's highly recommended to define one entity class per file.
|
||||
TypeORM allows you to use your classes as database models
|
||||
and provides you a declarative way to define what part of your model
|
||||
and provides a declarative way to define what part of your model
|
||||
will become part of your database table.
|
||||
The power of TypeScript gives you type hinting and other useful features that you can use in classes.
|
||||
|
||||
@ -257,13 +257,13 @@ or
|
||||
const employee = Employee.create({ name: "John Doe", title: "senior engineer" });
|
||||
```
|
||||
|
||||
if you want to load an exist entity from the database and replace some of its properties you can use following method:
|
||||
if you want to load an existing entity from the database and replace some of its properties you can use the following method:
|
||||
|
||||
```typescript
|
||||
const employee = await Employee.preload({ id: 1, name: "John Doe" });
|
||||
```
|
||||
|
||||
To access properties in sequelize you do following:
|
||||
To access properties in sequelize you do the following:
|
||||
|
||||
```typescript
|
||||
console.log(employee.get('name'));
|
||||
@ -295,4 +295,4 @@ In TypeORM you do:
|
||||
@Index(["firstName", "lastName"], { unique: true })
|
||||
export class User {
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
@ -2,18 +2,18 @@
|
||||
|
||||
### Found a bug or want to propose a new feature?
|
||||
|
||||
If you found a bug, issue, or you just want to propose a new feature create [an issue on github](https://github.com/typeorm/typeorm/issues).
|
||||
If you found a bug, issue, or you just want to propose a new feature, create [an issue on github](https://github.com/typeorm/typeorm/issues).
|
||||
|
||||
### Have a question?
|
||||
|
||||
If you have a question you can ask it on [StackOverflow](https://stackoverflow.com/questions/tagged/typeorm).
|
||||
If you have a question, you can ask it on [StackOverflow](https://stackoverflow.com/questions/tagged/typeorm).
|
||||
|
||||
### Want a community support?
|
||||
### Want community support?
|
||||
|
||||
If you want a community support or simply want to chat with friendly TypeORM enthusiasts and users you can do it in [gitter](https://gitter.im/typeorm/typeorm).
|
||||
If you want community support, or simply want to chat with friendly TypeORM enthusiasts and users, you can do it in [gitter](https://gitter.im/typeorm/typeorm).
|
||||
|
||||
### Want a professional commercial support?
|
||||
### Want professional commercial support?
|
||||
|
||||
The TypeORM core team is always ready to provide professional commercial support.
|
||||
We are ready to work with any team in any part of the world.
|
||||
[Please contact us](mailto:support@typeorm.io).
|
||||
[Please contact us](mailto:support@typeorm.io).
|
||||
|
||||
@ -81,8 +81,8 @@ using the `@TransactionRepository() customRepository: CustomRepository`.
|
||||
|
||||
`QueryRunner` provides a single database connection.
|
||||
Transactions are organized using query runners.
|
||||
Single transaction can be established only on a single query runner.
|
||||
You can manally create a query runner instance and control transaction state manually using it.
|
||||
Single transactions can only be established on a single query runner.
|
||||
You can manually create a query runner instance and use it to manually control transaction state.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -125,8 +125,8 @@ try {
|
||||
There are 3 methods to control transactions in `QueryRunner`:
|
||||
|
||||
|
||||
* `startTransaction` - starts a new transaction inside the query runner instance
|
||||
* `commitTransaction` - commits all changes made using the query runner instance
|
||||
* `rollbackTransaction` - rolls all changes made using the query runner instance back
|
||||
* `startTransaction` - starts a new transaction inside the query runner instance.
|
||||
* `commitTransaction` - commits all changes made using the query runner instance.
|
||||
* `rollbackTransaction` - rolls all changes made using the query runner instance back.
|
||||
|
||||
Learn more about [Query Runner](./query-runner.md).
|
||||
Learn more about [Query Runner](./query-runner.md).
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
# Tree Entities
|
||||
|
||||
TypeORM supports the Adjacency list and Closure table patterns for storing tree structures.
|
||||
To learn more about hierarchy table take a look at [this awesome presentation by Bill Karwin](https://www.slideshare.net/billkarwin/models-for-hierarchical-data).
|
||||
|
||||
* [Adjacency list](#adjacency-list)
|
||||
* [Nested set](#nested-set)
|
||||
* [Materialized Path (aka Path Enumeration)](#nested-set-aka-path-enumeration)
|
||||
* [Closure table](#closure-table)
|
||||
* [Working with tree entities](#working-with-tree-entities)
|
||||
|
||||
### Adjacency list
|
||||
## Adjacency list
|
||||
|
||||
Adjacency list is a simple model with self-referencing.
|
||||
The benefit of this approach is simplicity,
|
||||
drawback is that you can't load big tree in once because of join limitations.
|
||||
drawback is that you can't load big trees in all at once because of join limitations.
|
||||
To learn more about the benefits and use of Adjacency Lists look at [this article by Matthew Schinckel](http://schinckel.net/2014/09/13/long-live-adjacency-lists/).
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -36,18 +41,18 @@ export class Category {
|
||||
|
||||
```
|
||||
|
||||
### Closure table
|
||||
## Nested set
|
||||
|
||||
|
||||
Closure table stores relations between parent and child in a separate table in a special way.
|
||||
Its efficient in both reads and writes.
|
||||
To learn more about closure table take a look at [this awesome presentation by Bill Karwin](https://www.slideshare.net/billkarwin/models-for-hierarchical-data).
|
||||
Nested set is another pattern of storing tree structures in the database.
|
||||
Its very efficient for reads, but bad for writes.
|
||||
You cannot have multiple roots in nested set.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import {ClosureEntity, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
|
||||
@ClosureEntity()
|
||||
@Entity()
|
||||
@Tree("nested-set")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
@ -56,16 +61,201 @@ export class Category {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@TreeChildren()
|
||||
children: Category[];
|
||||
|
||||
@TreeParent()
|
||||
parent: Category;
|
||||
}
|
||||
```
|
||||
|
||||
## Materialized Path (aka Path Enumeration)
|
||||
|
||||
Materialized Path (also called Path Enumeration) is another pattern of storing tree structures in the database.
|
||||
Its simple and effective.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
@Tree("materialized-path")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
description: string;
|
||||
name: string;
|
||||
|
||||
@TreeChildren()
|
||||
children: Category[];
|
||||
|
||||
@TreeParent()
|
||||
parent: Category;
|
||||
|
||||
@TreeLevelColumn()
|
||||
level: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Closure table
|
||||
|
||||
Closure table stores relations between parent and child in a separate table in a special way.
|
||||
It's efficient in both reads and writes.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
|
||||
|
||||
@Entity()
|
||||
@Tree("closure-table")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@TreeChildren()
|
||||
children: Category[];
|
||||
|
||||
@TreeParent()
|
||||
parent: Category;
|
||||
}
|
||||
```
|
||||
|
||||
## Working with tree entities
|
||||
|
||||
To make bind tree entities to each other its important to set to children entities their parent and save them,
|
||||
for example:
|
||||
|
||||
```typescript
|
||||
const manager = getManager();
|
||||
|
||||
const a1 = new Category("a1");
|
||||
a1.name = "a1";
|
||||
await manager.save(a1);
|
||||
|
||||
const a11 = new Category();
|
||||
a11.name = "a11";
|
||||
a11.parent = a1;
|
||||
await manager.save(a11);
|
||||
|
||||
const a12 = new Category();
|
||||
a12.name = "a12";
|
||||
a12.parent = a1;
|
||||
await manager.save(a12);
|
||||
|
||||
const a111 = new Category();
|
||||
a111.name = "a111";
|
||||
a111.parent = a11;
|
||||
await manager.save(a111);
|
||||
|
||||
const a112 = new Category();
|
||||
a112.name = "a112";
|
||||
a112.parent = a11;
|
||||
await manager.save(a112);
|
||||
```
|
||||
|
||||
To load such a tree use `TreeRepository`:
|
||||
|
||||
```typescript
|
||||
const manager = getManager();
|
||||
const trees = await manager.getTreeRepository(Category).findTrees();
|
||||
```
|
||||
|
||||
`trees` will be following:
|
||||
|
||||
```json
|
||||
[{
|
||||
"id": 1,
|
||||
"name": "a1",
|
||||
"children": [{
|
||||
"id": 2,
|
||||
"name": "a11",
|
||||
"children": [{
|
||||
"id": 4,
|
||||
"name": "a111"
|
||||
}, {
|
||||
"id": 5,
|
||||
"name": "a112"
|
||||
}]
|
||||
}, {
|
||||
"id": 3,
|
||||
"name": "a12"
|
||||
}]
|
||||
}]
|
||||
```
|
||||
|
||||
There are other special methods to work with tree entities thought `TreeRepository`:
|
||||
|
||||
* `findTrees` - Returns all trees in the database with all their children, children of children, etc.
|
||||
|
||||
```typescript
|
||||
const treeCategories = await repository.findTrees();
|
||||
// returns root categories with sub categories inside
|
||||
```
|
||||
|
||||
* `findRoots` - Roots are entities that have no ancestors. Finds them all.
|
||||
Does not load children leafs.
|
||||
|
||||
```typescript
|
||||
const rootCategories = await repository.findRoots();
|
||||
// returns root categories without sub categories inside
|
||||
```
|
||||
|
||||
* `findDescendants` - Gets all children (descendants) of the given entity. Returns them all in a flat array.
|
||||
|
||||
```typescript
|
||||
const childrens = await repository.findDescendants(parentCategory);
|
||||
// returns all direct subcategories (without its nested categories) of a parentCategory
|
||||
```
|
||||
|
||||
* `findDescendantsTree` - Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other.
|
||||
|
||||
```typescript
|
||||
const childrensTree = await repository.findDescendantsTree(parentCategory);
|
||||
// returns all direct subcategories (with its nested categories) of a parentCategory
|
||||
```
|
||||
|
||||
* `createDescendantsQueryBuilder` - Creates a query builder used to get descendants of the entities in a tree.
|
||||
|
||||
```typescript
|
||||
const childrens = await repository
|
||||
.createDescendantsQueryBuilder("category", "categoryClosure", parentCategory)
|
||||
.andWhere("category.type = 'secondary'")
|
||||
.getMany();
|
||||
```
|
||||
|
||||
* `countDescendants` - Gets number of descendants of the entity.
|
||||
|
||||
```typescript
|
||||
const childrenCount = await repository.countDescendants(parentCategory);
|
||||
```
|
||||
|
||||
* `findAncestors` - Gets all parent (ancestors) of the given entity. Returns them all in a flat array.
|
||||
|
||||
```typescript
|
||||
const parents = await repository.findAncestors(childCategory);
|
||||
// returns all direct childCategory's parent categories (without "parent of parents")
|
||||
```
|
||||
|
||||
* `findAncestorsTree` - Gets all parent (ancestors) of the given entity. Returns them in a tree - nested into each other.
|
||||
|
||||
```typescript
|
||||
const parentsTree = await repository.findAncestorsTree(childCategory);
|
||||
// returns all direct childCategory's parent categories (with "parent of parents")
|
||||
```
|
||||
|
||||
* `createAncestorsQueryBuilder` - Creates a query builder used to get ancestors of the entities in a tree.
|
||||
|
||||
```typescript
|
||||
const parents = await repository
|
||||
.createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
|
||||
.andWhere("category.type = 'secondary'")
|
||||
.getMany();
|
||||
```
|
||||
|
||||
* `countAncestors` - Gets the number of ancestors of the entity.
|
||||
|
||||
```typescript
|
||||
const parentsCount = await repository.countAncestors(childCategory);
|
||||
@ -33,8 +33,8 @@ It creates all files needed for a basic project with TypeORM:
|
||||
* src/index.ts
|
||||
|
||||
Then you can run `npm install` to install all dependencies.
|
||||
Once all dependencies are installed you need to modify `ormconfig.json` and insert your own database settings.
|
||||
After that you can run your application by running `npm start`.
|
||||
Once all dependencies are installed, you need to modify `ormconfig.json` and insert your own database settings.
|
||||
After that, you can run your application by running `npm start`.
|
||||
|
||||
All files are generated in the current directory.
|
||||
If you want to generate them in a special directory you can use `--name`:
|
||||
@ -72,9 +72,9 @@ You can create a new entity using CLI:
|
||||
typeorm entity:create -n User
|
||||
```
|
||||
|
||||
where `User` is entity file and class name.
|
||||
where `User` is an entity file and class name.
|
||||
Running the command will create a new empty entity in `entitiesDir` of the project.
|
||||
To setup `entitiesDir` of the project you must add it in connection options:
|
||||
To setup the `entitiesDir` of the project you must add it in connection options:
|
||||
|
||||
```
|
||||
{
|
||||
@ -103,8 +103,8 @@ You can create a new subscriber using CLI:
|
||||
typeorm subscriber:create -n UserSubscriber
|
||||
```
|
||||
|
||||
where `UserSubscriber` is subscriber file and class name.
|
||||
Running following command will create a new empty subscriber in the `subscribersDir` of the project.
|
||||
where `UserSubscriber` is a subscriber file and class name.
|
||||
Running the following command will create a new empty subscriber in the `subscribersDir` of the project.
|
||||
To setup `subscribersDir` you must add it in connection options:
|
||||
|
||||
```
|
||||
@ -134,7 +134,7 @@ You can create a new migration using CLI:
|
||||
typeorm migration:create -n UserMigration
|
||||
```
|
||||
|
||||
where `UserMigration` is migration file and class name.
|
||||
where `UserMigration` is a migration file and class name.
|
||||
Running the command will create a new empty migration in the `migrationsDir` of the project.
|
||||
To setup `migrationsDir` you must add it in connection options:
|
||||
|
||||
@ -165,7 +165,7 @@ and writes all sql queries that must be executed to update the database.
|
||||
typeorm migration:generate -n UserMigration
|
||||
```
|
||||
|
||||
Rule of thumb is to generate a migration after each entity change.
|
||||
The rule of thumb is to generate a migration after each entity change.
|
||||
|
||||
Learn more about [Migrations](./migrations.md).
|
||||
|
||||
@ -181,7 +181,7 @@ Learn more about [Migrations](./migrations.md).
|
||||
|
||||
## Revert migrations
|
||||
|
||||
To revert last executed migration use following command:
|
||||
To revert the last executed migration use the following command:
|
||||
|
||||
```
|
||||
typeorm migrations:revert
|
||||
@ -199,8 +199,8 @@ typeorm schema:sync
|
||||
```
|
||||
|
||||
Be careful running this command in production -
|
||||
schema sync may bring you data loose if you don't use it wisely.
|
||||
Check sql queries it will run before running this query on production.
|
||||
schema sync may cause data loss if you don't use it wisely.
|
||||
Check which sql queries it will run before running on production.
|
||||
|
||||
## Log sync database schema queries without actual running them
|
||||
|
||||
@ -212,13 +212,13 @@ typeorm schema:log
|
||||
|
||||
## Drop database schema
|
||||
|
||||
To complete drop a database schema use:
|
||||
To completely drop a database schema use:
|
||||
|
||||
```
|
||||
typeorm schema:drop
|
||||
```
|
||||
|
||||
Be careful with this command on production since it completely remove data from your database.
|
||||
Be careful with this command on production since it completely removes data from your database.
|
||||
|
||||
## Run any sql query
|
||||
|
||||
@ -243,4 +243,4 @@ You can check what typeorm version you have installed (both local and global) by
|
||||
|
||||
```
|
||||
typeorm version
|
||||
```
|
||||
```
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
`Repository` is just like `EntityManager` but its operations are limited to a concrete entity.
|
||||
|
||||
You can access repository via `getRepository(Entity)`
|
||||
or from `Connection#getRepository` or from `EntityManager#getRepository`.
|
||||
You can access repository via `getRepository(Entity)`,
|
||||
`Connection#getRepository`, or `EntityManager#getRepository`.
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
@ -17,8 +17,8 @@ await userRepository.save(user);
|
||||
```
|
||||
|
||||
There are 3 types of repositories:
|
||||
* `Repository` - Regular repository for any entity
|
||||
* `Repository` - Regular repository for any entity.
|
||||
* `TreeRepository` - Repository, extensions of `Repository` used for tree-entities
|
||||
(like entities marked with `@ClosureEntity` decorator).
|
||||
(like entities marked with `@Tree` decorator).
|
||||
Has special methods to work with tree structures.
|
||||
* `MongoRepository` - Repository with special functions used only with MongoDB.
|
||||
|
||||
@ -40,12 +40,6 @@ exports.Column = Column;
|
||||
}
|
||||
exports.CreateDateColumn = CreateDateColumn;
|
||||
|
||||
/* export */ function DiscriminatorColumn(discriminatorOptions) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.DiscriminatorColumn = DiscriminatorColumn;
|
||||
|
||||
/* export */
|
||||
function ObjectIdColumn(typeOrOptions, options) {
|
||||
return function (object, propertyName) {
|
||||
@ -185,35 +179,11 @@ exports.RelationId = RelationId;
|
||||
|
||||
// entities
|
||||
|
||||
/* export */ function AbstractEntity() {
|
||||
/* export */ function ChildEntity(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.AbstractEntity = AbstractEntity;
|
||||
|
||||
/* export */ function ClassEntityChild(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClassEntityChild = ClassEntityChild;
|
||||
|
||||
/* export */ function ClosureEntity(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClosureEntity = ClosureEntity;
|
||||
|
||||
/* export */ function EmbeddableEntity() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.EmbeddableEntity = EmbeddableEntity;
|
||||
|
||||
/* export */ function SingleEntityChild() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.SingleEntityChild = SingleEntityChild;
|
||||
exports.ChildEntity = ChildEntity;
|
||||
|
||||
/* export */ function Entity(name, options) {
|
||||
return function (object) {
|
||||
@ -227,46 +197,14 @@ exports.Entity = Entity;
|
||||
}
|
||||
exports.TableInheritance = TableInheritance;
|
||||
|
||||
// tables (deprecated)
|
||||
|
||||
/* export */ function AbstractTable() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.AbstractTable = AbstractTable;
|
||||
|
||||
/* export */ function ClassTableChild(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClassTableChild = ClassTableChild;
|
||||
|
||||
/* export */ function ClosureTable(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClosureTable = ClosureTable;
|
||||
|
||||
/* export */ function EmbeddableTable() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.EmbeddableTable = EmbeddableTable;
|
||||
|
||||
/* export */ function SingleTableChild() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.SingleTableChild = SingleTableChild;
|
||||
|
||||
/* export */ function Table(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.Table = Table;
|
||||
|
||||
// tree
|
||||
|
||||
/* export */ function Tree(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.Tree = Tree;
|
||||
|
||||
/* export */ function TreeChildren(options) {
|
||||
return function (object, propertyName) {
|
||||
class_transformer_1.Type(typeFunction)(object, propertyName);
|
||||
@ -274,6 +212,13 @@ exports.Table = Table;
|
||||
}
|
||||
exports.TreeChildren = TreeChildren;
|
||||
|
||||
/* export */ function TreeChildrenCount(options) {
|
||||
return function (object, propertyName) {
|
||||
class_transformer_1.Type(typeFunction)(object, propertyName);
|
||||
};
|
||||
}
|
||||
exports.TreeChildrenCount = TreeChildrenCount;
|
||||
|
||||
/* export */ function TreeLevelColumn() {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
@ -289,11 +234,11 @@ exports.TreeParent = TreeParent;
|
||||
|
||||
// other
|
||||
|
||||
/* export */ function DiscriminatorValue(options) {
|
||||
/* export */ function Generated(options) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.DiscriminatorValue = DiscriminatorValue;
|
||||
exports.Generated = Generated;
|
||||
|
||||
/* export */ function Index() {
|
||||
return function (object, propertyName) {
|
||||
|
||||
@ -35,12 +35,6 @@ exports.Column = Column;
|
||||
}
|
||||
exports.CreateDateColumn = CreateDateColumn;
|
||||
|
||||
/* export */ function DiscriminatorColumn(discriminatorOptions) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.DiscriminatorColumn = DiscriminatorColumn;
|
||||
|
||||
/* export */ function ObjectIdColumn(columnOptions) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
@ -73,35 +67,11 @@ exports.VersionColumn = VersionColumn;
|
||||
|
||||
// entities
|
||||
|
||||
/* export */ function AbstractEntity() {
|
||||
/* export */ function ChildEntity(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.AbstractEntity = AbstractEntity;
|
||||
|
||||
/* export */ function ClassEntityChild(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClassEntityChild = ClassEntityChild;
|
||||
|
||||
/* export */ function ClosureEntity(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClosureEntity = ClosureEntity;
|
||||
|
||||
/* export */ function EmbeddableEntity() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.EmbeddableEntity = EmbeddableEntity;
|
||||
|
||||
/* export */ function SingleEntityChild() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.SingleEntityChild = SingleEntityChild;
|
||||
exports.ChildEntity = ChildEntity;
|
||||
|
||||
/* export */ function Entity(name, options) {
|
||||
return function (object) {
|
||||
@ -215,52 +185,26 @@ exports.RelationCount = RelationCount;
|
||||
}
|
||||
exports.RelationId = RelationId;
|
||||
|
||||
// tables (deprecated)
|
||||
|
||||
/* export */ function AbstractTable() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.AbstractTable = AbstractTable;
|
||||
|
||||
/* export */ function ClassTableChild(tableName, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClassTableChild = ClassTableChild;
|
||||
|
||||
/* export */ function ClosureTable(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.ClosureTable = ClosureTable;
|
||||
|
||||
/* export */ function EmbeddableTable() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.EmbeddableTable = EmbeddableTable;
|
||||
|
||||
/* export */ function SingleTableChild() {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.SingleTableChild = SingleTableChild;
|
||||
|
||||
/* export */ function Table(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.Table = Table;
|
||||
|
||||
// tree
|
||||
|
||||
/* export */ function Tree(name, options) {
|
||||
return function (object) {
|
||||
};
|
||||
}
|
||||
exports.Tree = Tree;
|
||||
|
||||
/* export */ function TreeChildren(options) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.TreeChildren = TreeChildren;
|
||||
|
||||
/* export */ function TreeChildrenCount(options) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.TreeChildrenCount = TreeChildrenCount;
|
||||
|
||||
/* export */ function TreeLevelColumn() {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
@ -275,11 +219,11 @@ exports.TreeParent = TreeParent;
|
||||
|
||||
// other
|
||||
|
||||
/* export */ function DiscriminatorValue(options) {
|
||||
/* export */ function Generated(options) {
|
||||
return function (object, propertyName) {
|
||||
};
|
||||
}
|
||||
exports.DiscriminatorValue = DiscriminatorValue;
|
||||
exports.Generated = Generated;
|
||||
|
||||
/* export */ function Index(options) {
|
||||
return function (object, propertyName) {
|
||||
|
||||
50
gulpfile.ts
50
gulpfile.ts
@ -29,11 +29,11 @@ export class Gulpfile {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a delay and resolves after 30 seconds.
|
||||
* Creates a delay and resolves after 15 seconds.
|
||||
*/
|
||||
@Task()
|
||||
wait(cb: Function) {
|
||||
setTimeout(() => cb(), 30000);
|
||||
setTimeout(() => cb(), 15000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -325,8 +325,17 @@ export class Gulpfile {
|
||||
/**
|
||||
* Runs post coverage operations.
|
||||
*/
|
||||
@Task("coveragePost", ["coveragePre"])
|
||||
@Task()
|
||||
coveragePost() {
|
||||
return gulp.src(["./build/compiled/test/**/*.js"])
|
||||
.pipe(istanbul.writeReports());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs mocha tests.
|
||||
*/
|
||||
@Task()
|
||||
runTests() {
|
||||
chai.should();
|
||||
chai.use(require("sinon-chai"));
|
||||
chai.use(require("chai-as-promised"));
|
||||
@ -335,24 +344,7 @@ export class Gulpfile {
|
||||
.pipe(mocha({
|
||||
bail: true,
|
||||
grep: !!args.grep ? new RegExp(args.grep) : undefined,
|
||||
timeout: 15000
|
||||
}))
|
||||
.pipe(istanbul.writeReports());
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs tests the quick way.
|
||||
*/
|
||||
@Task()
|
||||
quickTests() {
|
||||
chai.should();
|
||||
chai.use(require("sinon-chai"));
|
||||
chai.use(require("chai-as-promised"));
|
||||
|
||||
return gulp.src(["./build/compiled/test/**/*.js"])
|
||||
.pipe(mocha({
|
||||
bail: true,
|
||||
timeout: 35000
|
||||
timeout: 25000
|
||||
}));
|
||||
}
|
||||
|
||||
@ -370,7 +362,8 @@ export class Gulpfile {
|
||||
tests() {
|
||||
return [
|
||||
"compile",
|
||||
"tslint",
|
||||
"coveragePre",
|
||||
"runTests",
|
||||
"coveragePost",
|
||||
"coverageRemap"
|
||||
];
|
||||
@ -382,22 +375,17 @@ export class Gulpfile {
|
||||
@SequenceTask("ci-tests")
|
||||
ciTests() {
|
||||
return [
|
||||
"wait",
|
||||
"clean",
|
||||
"compile",
|
||||
"tslint",
|
||||
"wait",
|
||||
"coveragePre",
|
||||
"runTests",
|
||||
"coveragePost",
|
||||
"coverageRemap"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the code and runs only mocha tests.
|
||||
*/
|
||||
@SequenceTask()
|
||||
mocha() {
|
||||
return ["compile", "quickTests"];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CI tasks
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -72,7 +72,6 @@
|
||||
"skip": false,
|
||||
"name": "sqljs",
|
||||
"type": "sqljs",
|
||||
"logging": false,
|
||||
"autoSave": false
|
||||
"logging": false
|
||||
}
|
||||
]
|
||||
1023
package-lock.json
generated
1023
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"private": true,
|
||||
"version": "0.2.0-alpha.12",
|
||||
"version": "0.2.0-alpha.18",
|
||||
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL, MongoDB databases.",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
@ -74,7 +74,7 @@
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "^5.6.0",
|
||||
"tslint-stylish": "^2.1.0",
|
||||
"typescript": "^2.6.1"
|
||||
"typescript": "^2.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"app-root-path": "^2.0.1",
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import {Column, PrimaryGeneratedColumn} from "../../../src/index";
|
||||
import {TreeLevelColumn} from "../../../src/decorator/tree/TreeLevelColumn";
|
||||
import {ClosureEntity} from "../../../src/decorator/entity/ClosureEntity";
|
||||
import {TreeParent} from "../../../src/decorator/tree/TreeParent";
|
||||
import {TreeChildren} from "../../../src/decorator/tree/TreeChildren";
|
||||
import {Tree} from "../../../src/decorator/tree/Tree";
|
||||
import {Entity} from "../../../src/decorator/entity/Entity";
|
||||
|
||||
@ClosureEntity("sample22_category")
|
||||
@Entity("sample22_category")
|
||||
@Tree("closure-table")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
@ -23,7 +25,7 @@ export class Category {
|
||||
level: number;
|
||||
|
||||
// todo:
|
||||
// @RelationsCountColumn()
|
||||
// @TreeChildrenCount()
|
||||
// categoriesCount: number;
|
||||
|
||||
}
|
||||
@ -70,7 +70,7 @@ export interface BaseConnectionOptions {
|
||||
|
||||
/**
|
||||
* 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 loose production data.
|
||||
* 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.
|
||||
*
|
||||
@ -87,7 +87,7 @@ export interface BaseConnectionOptions {
|
||||
|
||||
/**
|
||||
* Drops the schema each time connection is being established.
|
||||
* Be careful with this option and don't use this in production - otherwise you'll loose all production data.
|
||||
* 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;
|
||||
@ -158,4 +158,4 @@ export interface BaseConnectionOptions {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import {QueryResultCacheFactory} from "../cache/QueryResultCacheFactory";
|
||||
import {QueryResultCache} from "../cache/QueryResultCache";
|
||||
import {SqljsEntityManager} from "../entity-manager/SqljsEntityManager";
|
||||
import {RelationLoader} from "../query-builder/RelationLoader";
|
||||
import {RelationIdLoader} from "../query-builder/RelationIdLoader";
|
||||
import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
|
||||
import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {PromiseUtils} from "../";
|
||||
@ -104,6 +105,11 @@ export class Connection {
|
||||
*/
|
||||
readonly relationLoader: RelationLoader;
|
||||
|
||||
/**
|
||||
* Used to load relation ids of specific entity relations.
|
||||
*/
|
||||
readonly relationIdLoader: RelationIdLoader;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
@ -117,6 +123,7 @@ export class Connection {
|
||||
this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy();
|
||||
this.queryResultCache = options.cache ? new QueryResultCacheFactory(this).create() : undefined;
|
||||
this.relationLoader = new RelationLoader(this);
|
||||
this.relationIdLoader = new RelationIdLoader(this);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -310,7 +317,7 @@ export class Connection {
|
||||
|
||||
/**
|
||||
* Gets tree repository for the given entity class or name.
|
||||
* Only tree-type entities can have a TreeRepository, like ones decorated with @ClosureEntity decorator.
|
||||
* Only tree-type entities can have a TreeRepository, like ones decorated with @Tree decorator.
|
||||
*/
|
||||
getTreeRepository<Entity>(target: ObjectType<Entity>|string): TreeRepository<Entity> {
|
||||
return this.manager.getTreeRepository(target);
|
||||
|
||||
@ -52,6 +52,15 @@ export class ConnectionOptionsReader {
|
||||
return targetOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there is a TypeORM configuration file.
|
||||
*/
|
||||
async has(name: string): Promise<boolean> {
|
||||
const allOptions = await this.all();
|
||||
const targetOptions = allOptions.find(options => options.name === name || (name === "default" && !options.name));
|
||||
return !!targetOptions;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import {getMetadataArgsStorage} from "../../index";
|
||||
import {TableMetadataArgs} from "../../metadata-args/TableMetadataArgs";
|
||||
import {EntityOptions} from "../options/EntityOptions";
|
||||
|
||||
/**
|
||||
* Used on a entities that stores its children in a tree using closure design pattern.
|
||||
*/
|
||||
export function ClosureEntity(name?: string, options?: EntityOptions) {
|
||||
return function (target: Function) {
|
||||
const args: TableMetadataArgs = {
|
||||
target: target,
|
||||
name: name,
|
||||
type: "closure",
|
||||
orderBy: options && options.orderBy ? options.orderBy : undefined,
|
||||
synchronize: options && options.synchronize === false ? false : true
|
||||
};
|
||||
getMetadataArgsStorage().tables.push(args);
|
||||
};
|
||||
}
|
||||
@ -14,7 +14,7 @@ export interface RelationOptions {
|
||||
*
|
||||
* cascade: ["insert", "update"] // include or exclude one of them
|
||||
*/
|
||||
cascade?: boolean|("insert"|"update")[];
|
||||
cascade?: boolean|("insert"|"update"|"remove")[];
|
||||
|
||||
/**
|
||||
* Indicates if relation column value can be nullable or not.
|
||||
|
||||
16
src/decorator/tree/Tree.ts
Normal file
16
src/decorator/tree/Tree.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {getMetadataArgsStorage} from "../../index";
|
||||
import {TreeMetadataArgs} from "../../metadata-args/TreeMetadataArgs";
|
||||
import {TreeType} from "../../metadata/types/TreeTypes";
|
||||
|
||||
/**
|
||||
* Marks entity to work like a tree.
|
||||
*/
|
||||
export function Tree(type: TreeType): Function {
|
||||
return function (target: Function) {
|
||||
const args: TreeMetadataArgs = {
|
||||
target: target,
|
||||
type: type
|
||||
};
|
||||
getMetadataArgsStorage().trees.push(args);
|
||||
};
|
||||
}
|
||||
@ -5,12 +5,12 @@ import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
|
||||
/**
|
||||
* Marks a specific property of the class as a children of the tree.
|
||||
*/
|
||||
export function TreeChildren(options?: RelationOptions): Function {
|
||||
export function TreeChildren(options?: { cascade?: boolean|("insert"|"update"|"remove")[] }): Function {
|
||||
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;
|
||||
let isLazy = 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")
|
||||
|
||||
@ -5,9 +5,9 @@ import {RelationMetadataArgs} from "../../metadata-args/RelationMetadataArgs";
|
||||
/**
|
||||
* Marks a specific property of the class as a parent of the tree.
|
||||
*/
|
||||
export function TreeParent(options?: RelationOptions): Function {
|
||||
export function TreeParent(): Function {
|
||||
return function (object: Object, propertyName: string) {
|
||||
if (!options) options = {} as RelationOptions;
|
||||
const options = { } as RelationOptions;
|
||||
|
||||
// now try to determine it its lazy relation
|
||||
let isLazy = options && options.lazy === true ? true : false;
|
||||
|
||||
@ -93,6 +93,8 @@ export interface Driver {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
@ -196,7 +196,7 @@ export class MysqlDriver implements Driver {
|
||||
async connect(): Promise<void> {
|
||||
|
||||
if (this.options.replication) {
|
||||
this.poolCluster = this.mysql.createPoolCluster();
|
||||
this.poolCluster = this.mysql.createPoolCluster(this.options.replication);
|
||||
this.options.replication.slaves.forEach((slave, index) => {
|
||||
this.poolCluster.add("SLAVE" + index, this.createConnectionOptions(this.options, slave));
|
||||
});
|
||||
@ -318,6 +318,9 @@ export class MysqlDriver implements Driver {
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.simpleArrayToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
return DateUtils.simpleJsonToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -327,31 +330,34 @@ export class MysqlDriver implements Driver {
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
if (columnMetadata.type === Boolean) {
|
||||
return value ? true : false;
|
||||
value = value ? true : false;
|
||||
|
||||
} else if (columnMetadata.type === "datetime" || columnMetadata.type === Date) {
|
||||
return DateUtils.normalizeHydratedDate(value);
|
||||
value = DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === "date") {
|
||||
return DateUtils.mixedDateToDateString(value);
|
||||
value = DateUtils.mixedDateToDateString(value);
|
||||
|
||||
} else if (columnMetadata.type === "json") {
|
||||
return typeof value === "string" ? JSON.parse(value) : value;
|
||||
value = typeof value === "string" ? JSON.parse(value) : value;
|
||||
|
||||
} else if (columnMetadata.type === "time") {
|
||||
return DateUtils.mixedTimeToString(value);
|
||||
value = DateUtils.mixedTimeToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.stringToSimpleArray(value);
|
||||
value = DateUtils.stringToSimpleArray(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
value = DateUtils.stringToSimpleJson(value);
|
||||
}
|
||||
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -381,6 +387,9 @@ export class MysqlDriver implements Driver {
|
||||
} else if (column.type === "simple-array") {
|
||||
return "text";
|
||||
|
||||
} else if (column.type === "simple-json") {
|
||||
return "text";
|
||||
|
||||
} else {
|
||||
return column.type as string || "";
|
||||
}
|
||||
@ -569,7 +578,16 @@ export class MysqlDriver implements Driver {
|
||||
protected loadDependencies(): void {
|
||||
try {
|
||||
this.mysql = PlatformTools.load("mysql"); // try to load first supported package
|
||||
|
||||
/*
|
||||
* Some frameworks (such as Jest) may mess up Node's require cache and provide garbage for the 'mysql' module
|
||||
* if it was not installed. We check that the object we got actually contains something otherwise we treat
|
||||
* it as if the `require` call failed.
|
||||
*
|
||||
* @see https://github.com/typeorm/typeorm/issues/1373
|
||||
*/
|
||||
if (Object.keys(this.mysql).length === 0) {
|
||||
throw new Error("'mysql' was found but it is empty. Falling back to 'mysql2'.");
|
||||
}
|
||||
} catch (e) {
|
||||
try {
|
||||
this.mysql = PlatformTools.load("mysql2"); // try to load second supported package
|
||||
|
||||
@ -321,6 +321,9 @@ export class OracleDriver implements Driver {
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.simpleArrayToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
return DateUtils.simpleJsonToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -330,9 +333,6 @@ export class OracleDriver implements Driver {
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
@ -340,7 +340,13 @@ export class OracleDriver implements Driver {
|
||||
return value === 1 ? true : false;
|
||||
|
||||
} else if (columnMetadata.type === "date") {
|
||||
return DateUtils.mixedDateToDateString(value);
|
||||
value = DateUtils.mixedDateToDateString(value);
|
||||
|
||||
} else if (columnMetadata.type === "time") {
|
||||
value = DateUtils.mixedTimeToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "datetime") {
|
||||
value = DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === Date
|
||||
|| columnMetadata.type === "timestamp"
|
||||
@ -348,10 +354,19 @@ export class OracleDriver implements Driver {
|
||||
|| columnMetadata.type === "timestamp with local time zone") {
|
||||
return DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === "json") {
|
||||
value = JSON.parse(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.stringToSimpleArray(value);
|
||||
value = DateUtils.stringToSimpleArray(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
value = DateUtils.stringToSimpleJson(value);
|
||||
}
|
||||
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -382,6 +397,9 @@ export class OracleDriver implements Driver {
|
||||
} else if (column.type === "simple-array") {
|
||||
return "clob";
|
||||
|
||||
} else if (column.type === "simple-json") {
|
||||
return "clob";
|
||||
|
||||
} else {
|
||||
return column.type as string || "";
|
||||
}
|
||||
|
||||
@ -250,14 +250,26 @@ export class PostgresDriver implements Driver {
|
||||
await Promise.all([this.master, ...this.slaves].map(pool => {
|
||||
return new Promise((ok, fail) => {
|
||||
pool.connect(async (err: any, connection: any, release: Function) => {
|
||||
const { logger } = this.connection;
|
||||
if (err) return fail(err);
|
||||
if (hasUuidColumns)
|
||||
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
||||
try {
|
||||
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
||||
} catch (_) {
|
||||
logger.log("warn", "At least one of the entities has uuid column, but the 'uuid-ossp' extension cannot be installed automatically. Please install it manually using superuser rights");
|
||||
}
|
||||
if (hasCitextColumns)
|
||||
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "citext"`);
|
||||
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)
|
||||
await this.executeQuery(connection, `CREATE EXTENSION IF NOT EXISTS "hstore"`);
|
||||
|
||||
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");
|
||||
}
|
||||
release();
|
||||
ok();
|
||||
});
|
||||
@ -335,6 +347,9 @@ export class PostgresDriver implements Driver {
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.simpleArrayToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
return DateUtils.simpleJsonToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -344,27 +359,24 @@ export class PostgresDriver implements Driver {
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
if (columnMetadata.type === Boolean) {
|
||||
return value ? true : false;
|
||||
value = value ? true : false;
|
||||
|
||||
} 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.normalizeHydratedDate(value);
|
||||
value = DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === "date") {
|
||||
return DateUtils.mixedDateToDateString(value);
|
||||
value = DateUtils.mixedDateToDateString(value);
|
||||
|
||||
} else if (columnMetadata.type === "time") {
|
||||
return DateUtils.mixedTimeToString(value);
|
||||
value = DateUtils.mixedTimeToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "hstore") {
|
||||
if (columnMetadata.hstoreType === "object") {
|
||||
@ -382,9 +394,15 @@ export class PostgresDriver implements Driver {
|
||||
}
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.stringToSimpleArray(value);
|
||||
value = DateUtils.stringToSimpleArray(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
value = DateUtils.stringToSimpleJson(value);
|
||||
}
|
||||
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -461,6 +479,9 @@ export class PostgresDriver implements Driver {
|
||||
} else if (column.type === "simple-array") {
|
||||
return "text";
|
||||
|
||||
} else if (column.type === "simple-json") {
|
||||
return "text";
|
||||
|
||||
} else if (column.type === "int2") {
|
||||
return "smallint";
|
||||
|
||||
|
||||
@ -227,6 +227,9 @@ export abstract class AbstractSqliteDriver implements Driver {
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.simpleArrayToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
return DateUtils.simpleJsonToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -236,28 +239,31 @@ export abstract class AbstractSqliteDriver implements Driver {
|
||||
* Prepares given value to a value to be hydrated, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
if (columnMetadata.type === Boolean || columnMetadata.type === "boolean") {
|
||||
return value ? true : false;
|
||||
value = value ? true : false;
|
||||
|
||||
} else if (columnMetadata.type === "datetime" || columnMetadata.type === Date) {
|
||||
return DateUtils.normalizeHydratedDate(value);
|
||||
value = DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === "date") {
|
||||
return DateUtils.mixedDateToDateString(value);
|
||||
value = DateUtils.mixedDateToDateString(value);
|
||||
|
||||
} else if (columnMetadata.type === "time") {
|
||||
return DateUtils.mixedTimeToString(value);
|
||||
value = DateUtils.mixedTimeToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.stringToSimpleArray(value);
|
||||
value = DateUtils.stringToSimpleArray(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
value = DateUtils.stringToSimpleJson(value);
|
||||
}
|
||||
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -333,6 +339,9 @@ export abstract class AbstractSqliteDriver implements Driver {
|
||||
} else if (column.type === "simple-array") {
|
||||
return "text";
|
||||
|
||||
} else if (column.type === "simple-json") {
|
||||
return "text";
|
||||
|
||||
} else {
|
||||
return column.type as string || "";
|
||||
}
|
||||
|
||||
@ -54,9 +54,14 @@ export class SqljsDriver extends AbstractSqliteDriver {
|
||||
*/
|
||||
async disconnect(): Promise<void> {
|
||||
return new Promise<void>((ok, fail) => {
|
||||
this.queryRunner = undefined;
|
||||
this.databaseConnection.close();
|
||||
ok();
|
||||
try {
|
||||
this.queryRunner = undefined;
|
||||
this.databaseConnection.close();
|
||||
ok();
|
||||
}
|
||||
catch (e) {
|
||||
fail(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -323,6 +323,9 @@ export class SqlServerDriver implements Driver {
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.simpleArrayToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
return DateUtils.simpleJsonToString(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -332,32 +335,35 @@ export class SqlServerDriver implements Driver {
|
||||
* Prepares given value to a value to be persisted, based on its column type or metadata.
|
||||
*/
|
||||
prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
if (columnMetadata.type === Boolean) {
|
||||
return value ? true : false;
|
||||
value = value ? true : false;
|
||||
|
||||
} else if (columnMetadata.type === "datetime"
|
||||
|| columnMetadata.type === Date
|
||||
|| columnMetadata.type === "datetime2"
|
||||
|| columnMetadata.type === "smalldatetime"
|
||||
|| columnMetadata.type === "datetimeoffset") {
|
||||
return DateUtils.normalizeHydratedDate(value);
|
||||
value = DateUtils.normalizeHydratedDate(value);
|
||||
|
||||
} else if (columnMetadata.type === "date") {
|
||||
return DateUtils.mixedDateToDateString(value);
|
||||
value = DateUtils.mixedDateToDateString(value);
|
||||
|
||||
} else if (columnMetadata.type === "time") {
|
||||
return DateUtils.mixedTimeToString(value);
|
||||
value = DateUtils.mixedTimeToString(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-array") {
|
||||
return DateUtils.stringToSimpleArray(value);
|
||||
value = DateUtils.stringToSimpleArray(value);
|
||||
|
||||
} else if (columnMetadata.type === "simple-json") {
|
||||
value = DateUtils.stringToSimpleJson(value);
|
||||
}
|
||||
|
||||
if (columnMetadata.transformer)
|
||||
value = columnMetadata.transformer.from(value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -386,6 +392,9 @@ export class SqlServerDriver implements Driver {
|
||||
} else if (column.type === "simple-array") {
|
||||
return "ntext";
|
||||
|
||||
} else if (column.type === "simple-json") {
|
||||
return "ntext";
|
||||
|
||||
} else if (column.type === "integer") {
|
||||
return "int";
|
||||
|
||||
|
||||
@ -68,6 +68,8 @@ export type SimpleColumnType =
|
||||
"simple-array" // typeorm-specific, automatically mapped to string
|
||||
// |"string" // typeorm-specific, automatically mapped to varchar depend on platform
|
||||
|
||||
|"simple-json" // typeorm-specific, automatically mapped to string
|
||||
|
||||
// numeric types
|
||||
|"bit" // mssql
|
||||
|"int2" // postgres, sqlite
|
||||
@ -157,4 +159,4 @@ export type ColumnType = WithPrecisionColumnType
|
||||
|BooleanConstructor
|
||||
|DateConstructor
|
||||
|NumberConstructor
|
||||
|StringConstructor;
|
||||
|StringConstructor;
|
||||
|
||||
@ -113,6 +113,11 @@ export class WebsqlDriver extends AbstractSqliteDriver {
|
||||
if (value instanceof Function) {
|
||||
return value();
|
||||
|
||||
}
|
||||
// Websql doesn't support queries boolean values. Therefore 1 and 0 has to be used.
|
||||
else if ((typeof value) === "boolean") {
|
||||
escapedParameters.push((value ? 1 : 0));
|
||||
return "?";
|
||||
} else {
|
||||
if (value instanceof ArrayParameter) value = value.value;
|
||||
escapedParameters.push(value);
|
||||
|
||||
@ -25,7 +25,6 @@ import {RepositoryNotFoundError} from "../error/RepositoryNotFoundError";
|
||||
import {RepositoryNotTreeError} from "../error/RepositoryNotTreeError";
|
||||
import {RepositoryFactory} from "../repository/RepositoryFactory";
|
||||
import {TreeRepositoryNotSupportedError} from "../error/TreeRepositoryNotSupportedError";
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {QueryPartialEntity} from "../query-builder/QueryPartialEntity";
|
||||
import {EntityPersistExecutor} from "../persistence/EntityPersistExecutor";
|
||||
import {ObjectID} from "../driver/mongodb/typings";
|
||||
@ -449,7 +448,7 @@ export class EntityManager {
|
||||
async find<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<Entity[]> {
|
||||
const metadata = this.connection.getMetadata(entityClass);
|
||||
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
|
||||
this.joinEagerRelations(qb, qb.alias, metadata);
|
||||
FindOptionsUtils.joinEagerRelations(qb, qb.alias, metadata);
|
||||
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getMany();
|
||||
}
|
||||
|
||||
@ -475,7 +474,7 @@ export class EntityManager {
|
||||
async findAndCount<Entity>(entityClass: ObjectType<Entity>|string, optionsOrConditions?: FindManyOptions<Entity>|DeepPartial<Entity>): Promise<[Entity[], number]> {
|
||||
const metadata = this.connection.getMetadata(entityClass);
|
||||
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
|
||||
this.joinEagerRelations(qb, qb.alias, metadata);
|
||||
FindOptionsUtils.joinEagerRelations(qb, qb.alias, metadata);
|
||||
return FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions).getManyAndCount();
|
||||
}
|
||||
|
||||
@ -503,7 +502,7 @@ export class EntityManager {
|
||||
const metadata = this.connection.getMetadata(entityClass);
|
||||
const qb = this.createQueryBuilder(entityClass, FindOptionsUtils.extractFindManyOptionsAlias(optionsOrConditions) || metadata.name);
|
||||
FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, optionsOrConditions);
|
||||
this.joinEagerRelations(qb, qb.alias, metadata);
|
||||
FindOptionsUtils.joinEagerRelations(qb, qb.alias, metadata);
|
||||
return qb.andWhereInIds(ids).getMany();
|
||||
}
|
||||
|
||||
@ -536,7 +535,7 @@ export class EntityManager {
|
||||
}
|
||||
const qb = this.createQueryBuilder(entityClass, alias);
|
||||
|
||||
this.joinEagerRelations(qb, qb.alias, metadata);
|
||||
FindOptionsUtils.joinEagerRelations(qb, qb.alias, metadata);
|
||||
|
||||
if (maybeOptions) {
|
||||
FindOptionsUtils.applyOptionsToQueryBuilder(qb, maybeOptions);
|
||||
@ -692,20 +691,4 @@ export class EntityManager {
|
||||
|
||||
return this.queryRunner.release();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Joins all eager relations recursively.
|
||||
*/
|
||||
protected joinEagerRelations(qb: SelectQueryBuilder<any>, alias: string, metadata: EntityMetadata) {
|
||||
metadata.eagerRelations.forEach(relation => {
|
||||
const relationAlias = alias + "_" + relation.propertyPath.replace(".", "_");
|
||||
qb.leftJoinAndSelect(alias + "." + relation.propertyPath, relationAlias);
|
||||
this.joinEagerRelations(qb, relationAlias, relation.inverseEntityMetadata);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
import {ColumnType} from "../driver/types/ColumnTypes";
|
||||
|
||||
export interface EntitySchemaColumn {
|
||||
|
||||
|
||||
/**
|
||||
* Indicates if this column is a primary column.
|
||||
*/
|
||||
primary?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if this column is of type ObjectID
|
||||
*/
|
||||
objectId?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if this column is a created date column.
|
||||
*/
|
||||
@ -96,4 +101,4 @@ export interface EntitySchemaColumn {
|
||||
*/
|
||||
collation?: string; // todo: looks like this is not used
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ export interface EntitySchemaRelation {
|
||||
* If set to true then it means that related object can be allowed to be inserted / updated / removed to the db.
|
||||
* This is option a shortcut if you would like to set cascadeInsert, cascadeUpdate and cascadeRemove to true.
|
||||
*/
|
||||
cascade?: boolean|("insert"|"update")[];
|
||||
cascade?: boolean|("insert"|"update"|"remove")[];
|
||||
|
||||
/**
|
||||
* Default database value.
|
||||
|
||||
@ -53,6 +53,8 @@ export class EntitySchemaTransformer {
|
||||
mode = "treeChildrenCount";
|
||||
if (tableColumn.treeLevel)
|
||||
mode = "treeLevel";
|
||||
if (tableColumn.objectId)
|
||||
mode = "objectId";
|
||||
|
||||
const columnAgrs: ColumnMetadataArgs = {
|
||||
target: schema.target || schema.name,
|
||||
@ -157,12 +159,12 @@ export class EntitySchemaTransformer {
|
||||
sparse: tableIndex.sparse,
|
||||
columns: tableIndex.columns
|
||||
};
|
||||
metadataArgsStorage.indices.push(indexAgrs);
|
||||
metadataArgsStorage.indices.push(indexAgrs);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return metadataArgsStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/error/CannotAttachTreeChildrenEntityError.ts
Normal file
11
src/error/CannotAttachTreeChildrenEntityError.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Thrown when user saves tree children entity but its parent is not saved yet.
|
||||
*/
|
||||
export class CannotAttachTreeChildrenEntityError extends Error {
|
||||
|
||||
constructor(entityName: string) {
|
||||
super(`Cannot attach entity "${entityName}" to its parent. Please make sure parent is saved in the database before saving children nodes.`);
|
||||
Object.setPrototypeOf(this, CannotAttachTreeChildrenEntityError.prototype);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/error/LimitOnUpdateNotSupportedError.ts
Normal file
12
src/error/LimitOnUpdateNotSupportedError.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Thrown when user tries to build an UPDATE query with LIMIT but the database does not support it.
|
||||
*/
|
||||
export class LimitOnUpdateNotSupportedError extends Error {
|
||||
|
||||
constructor() {
|
||||
super(`Your database does not support LIMIT on UPDATE statements.`);
|
||||
Object.setPrototypeOf(this, LimitOnUpdateNotSupportedError.prototype);
|
||||
this.name = "LimitOnUpdateNotSupportedError";
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ export class LockNotSupportedOnGivenDriverError extends Error {
|
||||
constructor() {
|
||||
super();
|
||||
Object.setPrototypeOf(this, LockNotSupportedOnGivenDriverError.prototype);
|
||||
this.message = `Locking not supported on giver driver.`;
|
||||
this.message = `Locking not supported on given driver.`;
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ export class RepositoryNotTreeError extends Error {
|
||||
constructor(entityClass: Function|string) {
|
||||
super();
|
||||
const targetName = typeof entityClass === "function" && (<any> entityClass).name ? (<any> entityClass).name : entityClass;
|
||||
this.message = `Repository of the "${targetName}" class is not a TreeRepository. Try to use @ClosureEntity decorator instead of @Entity.`;
|
||||
this.message = `Repository of the "${targetName}" class is not a TreeRepository. Try to apply @Tree decorator on your entity.`;
|
||||
this.stack = new Error().stack;
|
||||
}
|
||||
|
||||
|
||||
@ -190,6 +190,12 @@ export class FindOptionsUtils {
|
||||
const selection = alias + "." + relation;
|
||||
qb.leftJoinAndSelect(selection, alias + "_" + relation);
|
||||
|
||||
// join the eager relations of the found relation
|
||||
const relMetadata = metadata.relations.find(metadata => metadata.propertyName === relation);
|
||||
if (relMetadata) {
|
||||
this.joinEagerRelations(qb, alias + "_" + relation, relMetadata.inverseEntityMetadata);
|
||||
}
|
||||
|
||||
// remove added relations from the allRelations array, this is needed to find all not found relations at the end
|
||||
allRelations.splice(allRelations.indexOf(prefix ? prefix + "." + relation : relation), 1);
|
||||
|
||||
@ -199,4 +205,12 @@ export class FindOptionsUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static joinEagerRelations(qb: SelectQueryBuilder<any>, alias: string, metadata: EntityMetadata) {
|
||||
metadata.eagerRelations.forEach(relation => {
|
||||
const relationAlias = alias + "_" + relation.propertyPath.replace(".", "_");
|
||||
qb.leftJoinAndSelect(alias + "." + relation.propertyPath, relationAlias);
|
||||
this.joinEagerRelations(qb, relationAlias, relation.inverseEntityMetadata);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
14
src/index.ts
14
src/index.ts
@ -15,6 +15,7 @@ import {ConnectionOptionsReader} from "./connection/ConnectionOptionsReader";
|
||||
import {PromiseUtils} from "./util/PromiseUtils";
|
||||
import {MongoEntityManager} from "./entity-manager/MongoEntityManager";
|
||||
import {SqljsEntityManager} from "./entity-manager/SqljsEntityManager";
|
||||
import {SelectQueryBuilder} from "./query-builder/SelectQueryBuilder";
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Commonly Used exports
|
||||
@ -55,7 +56,6 @@ export * from "./decorator/relations/OneToOne";
|
||||
export * from "./decorator/relations/RelationCount";
|
||||
export * from "./decorator/relations/RelationId";
|
||||
export * from "./decorator/entity/Entity";
|
||||
export * from "./decorator/entity/ClosureEntity";
|
||||
export * from "./decorator/entity/ChildEntity";
|
||||
export * from "./decorator/entity/TableInheritance";
|
||||
export * from "./decorator/transaction/Transaction";
|
||||
@ -64,6 +64,7 @@ export * from "./decorator/transaction/TransactionRepository";
|
||||
export * from "./decorator/tree/TreeLevelColumn";
|
||||
export * from "./decorator/tree/TreeParent";
|
||||
export * from "./decorator/tree/TreeChildren";
|
||||
export * from "./decorator/tree/Tree";
|
||||
export * from "./decorator/Index";
|
||||
export * from "./decorator/Unique";
|
||||
export * from "./decorator/Generated";
|
||||
@ -259,4 +260,15 @@ export function getCustomRepository<T>(customRepository: ObjectType<T>, connecti
|
||||
*/
|
||||
export function getMongoRepository<Entity>(entityClass: ObjectType<Entity>|string, connectionName: string = "default"): MongoRepository<Entity> {
|
||||
return getConnectionManager().get(connectionName).getMongoRepository<Entity>(entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new query builder.
|
||||
*/
|
||||
export function createQueryBuilder<Entity>(entityClass?: ObjectType<Entity>|string, alias?: string, connectionName: string = "default"): SelectQueryBuilder<Entity> {
|
||||
if (entityClass) {
|
||||
return getRepository(entityClass, connectionName).createQueryBuilder(alias);
|
||||
}
|
||||
|
||||
return getConnection(connectionName).createQueryBuilder();
|
||||
}
|
||||
@ -2,21 +2,21 @@ import {Logger} from "./Logger";
|
||||
import {QueryRunner} from "../";
|
||||
import {PlatformTools} from "../platform/PlatformTools";
|
||||
|
||||
const debug = PlatformTools.load("debug");
|
||||
|
||||
/**
|
||||
* Performs logging of the events in TypeORM via debug library.
|
||||
*/
|
||||
export class DebugLogger implements Logger {
|
||||
private debugQueryLog = debug("typeorm:query:log");
|
||||
private debugQueryError = debug("typeorm:query:error");
|
||||
private debugQuerySlow = debug("typeorm:query:slow");
|
||||
private debugSchemaBuild = debug("typeorm:schema");
|
||||
private debugMigration = debug("typeorm:migration");
|
||||
private debug = PlatformTools.load("debug");
|
||||
|
||||
private debugQueryLog = this.debug("typeorm:query:log");
|
||||
private debugQueryError = this.debug("typeorm:query:error");
|
||||
private debugQuerySlow = this.debug("typeorm:query:slow");
|
||||
private debugSchemaBuild = this.debug("typeorm:schema");
|
||||
private debugMigration = this.debug("typeorm:migration");
|
||||
|
||||
private debugLog = debug("typeorm:log");
|
||||
private debugInfo = debug("typeorm:info");
|
||||
private debugWarn = debug("typeorm:warn");
|
||||
private debugLog = this.debug("typeorm:log");
|
||||
private debugInfo = this.debug("typeorm:info");
|
||||
private debugWarn = this.debug("typeorm:warn");
|
||||
|
||||
/**
|
||||
* Logs query and parameters used in it.
|
||||
|
||||
@ -17,6 +17,7 @@ import {TransactionEntityMetadataArgs} from "./TransactionEntityMetadataArgs";
|
||||
import {TransactionRepositoryMetadataArgs} from "./TransactionRepositoryMetadataArgs";
|
||||
import {MetadataUtils} from "../metadata-builder/MetadataUtils";
|
||||
import {GeneratedMetadataArgs} from "./GeneratedMetadataArgs";
|
||||
import {TreeMetadataArgs} from "./TreeMetadataArgs";
|
||||
import {UniqueMetadataArgs} from "./UniqueMetadataArgs";
|
||||
|
||||
/**
|
||||
@ -31,6 +32,7 @@ export class MetadataArgsStorage {
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
readonly tables: TableMetadataArgs[] = [];
|
||||
readonly trees: TreeMetadataArgs[] = [];
|
||||
readonly entityRepositories: EntityRepositoryMetadataArgs[] = [];
|
||||
readonly transactionEntityManagers: TransactionEntityMetadataArgs[] = [];
|
||||
readonly transactionRepositories: TransactionRepositoryMetadataArgs[] = [];
|
||||
@ -74,6 +76,12 @@ export class MetadataArgsStorage {
|
||||
});
|
||||
}
|
||||
|
||||
findTree(target: (Function|string)|(Function|string)[]): TreeMetadataArgs|undefined {
|
||||
return this.trees.find(tree => {
|
||||
return (target instanceof Array ? target.indexOf(tree.target) !== -1 : tree.target === target);
|
||||
});
|
||||
}
|
||||
|
||||
filterRelations(target: Function|string): RelationMetadataArgs[];
|
||||
filterRelations(target: (Function|string)[]): RelationMetadataArgs[];
|
||||
filterRelations(target: (Function|string)|(Function|string)[]): RelationMetadataArgs[] {
|
||||
|
||||
18
src/metadata-args/TreeMetadataArgs.ts
Normal file
18
src/metadata-args/TreeMetadataArgs.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {TreeType} from "../metadata/types/TreeTypes";
|
||||
|
||||
/**
|
||||
* Stores metadata collected for Tree entities.
|
||||
*/
|
||||
export interface TreeMetadataArgs {
|
||||
|
||||
/**
|
||||
* Entity to which tree is applied.
|
||||
*/
|
||||
target: Function|string;
|
||||
|
||||
/**
|
||||
* Tree type.
|
||||
*/
|
||||
type: TreeType;
|
||||
|
||||
}
|
||||
@ -42,23 +42,27 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
connection: this.connection,
|
||||
entityMetadata: entityMetadata,
|
||||
closureType: "ancestor",
|
||||
referencedColumn: primaryColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "ancestor", // todo: naming strategy
|
||||
propertyName: primaryColumn.propertyName + "_ancestor", // todo: naming strategy
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
type: primaryColumn.type
|
||||
}
|
||||
}
|
||||
}));
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
connection: this.connection,
|
||||
entityMetadata: entityMetadata,
|
||||
closureType: "descendant",
|
||||
referencedColumn: primaryColumn,
|
||||
args: {
|
||||
target: "",
|
||||
mode: "virtual",
|
||||
propertyName: "descendant",
|
||||
propertyName: primaryColumn.propertyName + "_descendant",
|
||||
options: {
|
||||
length: primaryColumn.length,
|
||||
type: primaryColumn.type,
|
||||
@ -89,13 +93,15 @@ export class ClosureJunctionEntityMetadataBuilder {
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[0]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns,
|
||||
// onDelete: "CASCADE" // todo: does not work in mssql for some reason
|
||||
}),
|
||||
new ForeignKeyMetadata({
|
||||
entityMetadata: entityMetadata,
|
||||
referencedEntityMetadata: parentClosureEntityMetadata,
|
||||
columns: [entityMetadata.ownColumns[1]],
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns
|
||||
referencedColumns: parentClosureEntityMetadata.primaryColumns,
|
||||
// onDelete: "CASCADE" // todo: does not work in mssql for some reason
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@ -137,7 +137,7 @@ export class EntityMetadataBuilder {
|
||||
|
||||
// generate closure junction tables for all closure tables
|
||||
entityMetadatas
|
||||
.filter(metadata => metadata.isClosure)
|
||||
.filter(metadata => metadata.treeType === "closure-table")
|
||||
.forEach(entityMetadata => {
|
||||
const closureJunctionEntityMetadata = this.closureJunctionEntityMetadataBuilder.build(entityMetadata);
|
||||
entityMetadata.closureJunctionTable = closureJunctionEntityMetadata;
|
||||
@ -217,6 +217,7 @@ export class EntityMetadataBuilder {
|
||||
: [tableArgs.target]; // todo: implement later here inheritance for string-targets
|
||||
|
||||
const tableInheritance = this.metadataArgsStorage.findInheritanceType(tableArgs.target);
|
||||
const tableTree = this.metadataArgsStorage.findTree(tableArgs.target);
|
||||
|
||||
// if single table inheritance used, we need to copy all children columns in to parent table
|
||||
let singleTableChildrenTargets: any[];
|
||||
@ -233,6 +234,7 @@ export class EntityMetadataBuilder {
|
||||
connection: this.connection,
|
||||
args: tableArgs,
|
||||
inheritanceTree: inheritanceTree,
|
||||
tableTree: tableTree,
|
||||
inheritancePattern: tableInheritance ? tableInheritance.pattern : undefined
|
||||
});
|
||||
}
|
||||
@ -286,7 +288,7 @@ export class EntityMetadataBuilder {
|
||||
mode: "virtual",
|
||||
propertyName: discriminatorColumnName,
|
||||
options: entityInheritance.column || {
|
||||
name: "type",
|
||||
name: discriminatorColumnName,
|
||||
type: "varchar",
|
||||
nullable: false
|
||||
}
|
||||
@ -309,6 +311,60 @@ export class EntityMetadataBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// check if tree is used then we need to add extra columns for specific tree types
|
||||
if (entityMetadata.treeType === "materialized-path") {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
connection: this.connection,
|
||||
entityMetadata: entityMetadata,
|
||||
materializedPath: true,
|
||||
args: {
|
||||
target: entityMetadata.target,
|
||||
mode: "virtual",
|
||||
propertyName: "mpath",
|
||||
options: /*tree.column || */ {
|
||||
name: "mpath",
|
||||
type: "varchar",
|
||||
nullable: false,
|
||||
default: ""
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
} else if (entityMetadata.treeType === "nested-set") {
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
connection: this.connection,
|
||||
entityMetadata: entityMetadata,
|
||||
nestedSetLeft: true,
|
||||
args: {
|
||||
target: entityMetadata.target,
|
||||
mode: "virtual",
|
||||
propertyName: "nsleft",
|
||||
options: /*tree.column || */ {
|
||||
name: "nsleft",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
default: 1
|
||||
}
|
||||
}
|
||||
}));
|
||||
entityMetadata.ownColumns.push(new ColumnMetadata({
|
||||
connection: this.connection,
|
||||
entityMetadata: entityMetadata,
|
||||
nestedSetRight: true,
|
||||
args: {
|
||||
target: entityMetadata.target,
|
||||
mode: "virtual",
|
||||
propertyName: "nsright",
|
||||
options: /*tree.column || */ {
|
||||
name: "nsright",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
default: 2
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
entityMetadata.ownRelations = this.metadataArgsStorage.filterRelations(entityMetadata.inheritanceTree).map(args => {
|
||||
|
||||
// for single table children we reuse relations created for their parents
|
||||
@ -413,6 +469,8 @@ export class EntityMetadataBuilder {
|
||||
entityMetadata.indices = entityMetadata.embeddeds.reduce((columns, embedded) => columns.concat(embedded.indicesFromTree), entityMetadata.ownIndices);
|
||||
entityMetadata.primaryColumns = entityMetadata.columns.filter(column => column.isPrimary);
|
||||
entityMetadata.nonVirtualColumns = entityMetadata.columns.filter(column => !column.isVirtual);
|
||||
entityMetadata.ancestorColumns = entityMetadata.columns.filter(column => column.closureType === "ancestor");
|
||||
entityMetadata.descendantColumns = entityMetadata.columns.filter(column => column.closureType === "descendant");
|
||||
entityMetadata.hasMultiplePrimaryKeys = entityMetadata.primaryColumns.length > 1;
|
||||
entityMetadata.generatedColumns = entityMetadata.columns.filter(column => column.isGenerated || column.isObjectId);
|
||||
entityMetadata.hasUUIDGeneratedColumns = entityMetadata.columns.filter(column => column.isGenerated || column.generationStrategy === "uuid").length > 0;
|
||||
@ -421,6 +479,9 @@ export class EntityMetadataBuilder {
|
||||
entityMetadata.versionColumn = entityMetadata.columns.find(column => column.isVersion);
|
||||
entityMetadata.discriminatorColumn = entityMetadata.columns.find(column => column.isDiscriminator);
|
||||
entityMetadata.treeLevelColumn = entityMetadata.columns.find(column => column.isTreeLevel);
|
||||
entityMetadata.nestedSetLeftColumn = entityMetadata.columns.find(column => column.isNestedSetLeft);
|
||||
entityMetadata.nestedSetRightColumn = entityMetadata.columns.find(column => column.isNestedSetRight);
|
||||
entityMetadata.materializedPathColumn = entityMetadata.columns.find(column => column.isMaterializedPath);
|
||||
entityMetadata.objectIdColumn = entityMetadata.columns.find(column => column.isObjectId);
|
||||
entityMetadata.foreignKeys.forEach(foreignKey => foreignKey.build(this.connection.namingStrategy));
|
||||
entityMetadata.propertiesMap = entityMetadata.createPropertiesMap();
|
||||
|
||||
@ -221,6 +221,30 @@ export class ColumnMetadata {
|
||||
*/
|
||||
transformer?: ValueTransformer;
|
||||
|
||||
/**
|
||||
* Column type in the case if this column is in the closure table.
|
||||
* Column can be ancestor or descendant in the closure tables.
|
||||
*/
|
||||
closureType?: "ancestor"|"descendant";
|
||||
|
||||
/**
|
||||
* Indicates if this column is nested set's left column.
|
||||
* Used only in tree entities with nested-set type.
|
||||
*/
|
||||
isNestedSetLeft: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates if this column is nested set's right column.
|
||||
* Used only in tree entities with nested-set type.
|
||||
*/
|
||||
isNestedSetRight: boolean = false;
|
||||
|
||||
/**
|
||||
* Indicates if this column is materialized path's path column.
|
||||
* Used only in tree entities with materialized path type.
|
||||
*/
|
||||
isMaterializedPath: boolean = false;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------
|
||||
@ -230,7 +254,11 @@ export class ColumnMetadata {
|
||||
entityMetadata: EntityMetadata,
|
||||
embeddedMetadata?: EmbeddedMetadata,
|
||||
referencedColumn?: ColumnMetadata,
|
||||
args: ColumnMetadataArgs
|
||||
args: ColumnMetadataArgs,
|
||||
closureType?: "ancestor"|"descendant",
|
||||
nestedSetLeft?: boolean,
|
||||
nestedSetRight?: boolean,
|
||||
materializedPath?: boolean,
|
||||
}) {
|
||||
this.entityMetadata = options.entityMetadata;
|
||||
this.embeddedMetadata = options.embeddedMetadata!;
|
||||
@ -311,6 +339,14 @@ export class ColumnMetadata {
|
||||
}
|
||||
if (this.isVersion)
|
||||
this.type = options.connection.driver.mappedDataTypes.version;
|
||||
if (options.closureType)
|
||||
this.closureType = options.closureType;
|
||||
if (options.nestedSetLeft)
|
||||
this.isNestedSetLeft = options.nestedSetLeft;
|
||||
if (options.nestedSetRight)
|
||||
this.isNestedSetRight = options.nestedSetRight;
|
||||
if (options.materializedPath)
|
||||
this.isMaterializedPath = options.materializedPath;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
@ -17,6 +17,8 @@ import {SqlServerDriver} from "../driver/sqlserver/SqlServerDriver";
|
||||
import {PostgresConnectionOptions} from "../driver/postgres/PostgresConnectionOptions";
|
||||
import {SqlServerConnectionOptions} from "../driver/sqlserver/SqlServerConnectionOptions";
|
||||
import {CannotCreateEntityIdMapError} from "../error/CannotCreateEntityIdMapError";
|
||||
import {TreeType} from "./types/TreeTypes";
|
||||
import {TreeMetadataArgs} from "../metadata-args/TreeMetadataArgs";
|
||||
import {UniqueMetadata} from "./UniqueMetadata";
|
||||
|
||||
/**
|
||||
@ -168,10 +170,9 @@ export class EntityMetadata {
|
||||
isJunction: boolean = false;
|
||||
|
||||
/**
|
||||
* Checks if this table is a closure table.
|
||||
* Closure table is one of the tree-specific tables that supports closure database pattern.
|
||||
* Indicates if this entity is a tree, what type of tree it is.
|
||||
*/
|
||||
isClosure: boolean = false;
|
||||
treeType?: TreeType;
|
||||
|
||||
/**
|
||||
* Checks if this table is a junction table of the closure table.
|
||||
@ -205,6 +206,16 @@ export class EntityMetadata {
|
||||
*/
|
||||
columns: ColumnMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Ancestor columns used only in closure junction tables.
|
||||
*/
|
||||
ancestorColumns: ColumnMetadata[] = [];
|
||||
|
||||
/**
|
||||
* Descendant columns used only in closure junction tables.
|
||||
*/
|
||||
descendantColumns: ColumnMetadata[] = [];
|
||||
|
||||
/**
|
||||
* All columns except for virtual columns.
|
||||
*/
|
||||
@ -257,6 +268,24 @@ export class EntityMetadata {
|
||||
*/
|
||||
treeLevelColumn?: ColumnMetadata;
|
||||
|
||||
/**
|
||||
* Nested set's left value column.
|
||||
* Used only in tree entities with nested set pattern applied.
|
||||
*/
|
||||
nestedSetLeftColumn?: ColumnMetadata;
|
||||
|
||||
/**
|
||||
* Nested set's right value column.
|
||||
* Used only in tree entities with nested set pattern applied.
|
||||
*/
|
||||
nestedSetRightColumn?: ColumnMetadata;
|
||||
|
||||
/**
|
||||
* Materialized path column.
|
||||
* Used only in tree entities with materialized path pattern applied.
|
||||
*/
|
||||
materializedPathColumn?: ColumnMetadata;
|
||||
|
||||
/**
|
||||
* Gets the primary columns.
|
||||
*/
|
||||
@ -429,12 +458,14 @@ export class EntityMetadata {
|
||||
connection: Connection,
|
||||
inheritanceTree?: Function[],
|
||||
inheritancePattern?: "STI"/*|"CTI"*/,
|
||||
tableTree?: TreeMetadataArgs,
|
||||
parentClosureEntityMetadata?: EntityMetadata,
|
||||
args: TableMetadataArgs
|
||||
}) {
|
||||
this.connection = options.connection;
|
||||
this.inheritanceTree = options.inheritanceTree || [];
|
||||
this.inheritancePattern = options.inheritancePattern;
|
||||
this.treeType = options.tableTree ? options.tableTree.type : undefined;
|
||||
this.parentClosureEntityMetadata = options.parentClosureEntityMetadata!;
|
||||
this.tableMetadataArgs = options.args;
|
||||
this.target = this.tableMetadataArgs.target;
|
||||
@ -669,13 +700,15 @@ export class EntityMetadata {
|
||||
* Examples of usages are primary columns map and join columns map.
|
||||
*/
|
||||
static getValueMap(entity: ObjectLiteral, columns: ColumnMetadata[]): ObjectLiteral|undefined {
|
||||
const map = columns.reduce((map, column) => {
|
||||
if (column.isObjectId)
|
||||
return Object.assign(map, column.getEntityValueMap(entity));
|
||||
return columns.reduce((map, column) => {
|
||||
const value = column.getEntityValueMap(entity);
|
||||
|
||||
return OrmUtils.mergeDeep(map, column.getEntityValueMap(entity));
|
||||
}, {} as ObjectLiteral);
|
||||
return Object.keys(map).length > 0 ? map : undefined;
|
||||
// make sure that none of the values of the columns are not missing
|
||||
if (map === undefined || value === null || value === undefined)
|
||||
return undefined;
|
||||
|
||||
return column.isObjectId ? Object.assign(map, value) : OrmUtils.mergeDeep(map, value);
|
||||
}, {} as ObjectLiteral|undefined);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -707,7 +740,6 @@ export class EntityMetadata {
|
||||
|
||||
this.isJunction = this.tableMetadataArgs.type === "closure-junction" || this.tableMetadataArgs.type === "junction";
|
||||
this.isClosureJunction = this.tableMetadataArgs.type === "closure-junction";
|
||||
this.isClosure = this.tableMetadataArgs.type === "closure";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -261,9 +261,10 @@ export class RelationMetadata {
|
||||
this.isLazy = args.isLazy || false;
|
||||
this.isCascadeInsert = args.options.cascade === true || (args.options.cascade instanceof Array && args.options.cascade.indexOf("insert") !== -1);
|
||||
this.isCascadeUpdate = args.options.cascade === true || (args.options.cascade instanceof Array && args.options.cascade.indexOf("update") !== -1);
|
||||
this.isCascadeRemove = args.options.cascade === true || (args.options.cascade instanceof Array && args.options.cascade.indexOf("remove") !== -1);
|
||||
this.isPrimary = args.options.primary || false;
|
||||
this.isNullable = args.options.nullable === false || this.isPrimary ? false : true;
|
||||
this.onDelete = args.options.onDelete;
|
||||
this.isPrimary = args.options.primary || false;
|
||||
this.isEager = args.options.eager || false;
|
||||
this.persistenceEnabled = args.options.persistence === false ? false : true;
|
||||
this.isTreeParent = args.isTreeParent || false;
|
||||
|
||||
4
src/metadata/types/TreeTypes.ts
Normal file
4
src/metadata/types/TreeTypes.ts
Normal file
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Tree type.
|
||||
*/
|
||||
export type TreeType = "adjacency-list"|"closure-table"|"nested-set"|"materialized-path";
|
||||
@ -83,11 +83,12 @@ export class EntityPersistExecutor {
|
||||
});
|
||||
|
||||
// console.time("building cascades...");
|
||||
// go thought each entity with metadata and create subjects and subjects by cascades for them
|
||||
// go through each entity with metadata and create subjects and subjects by cascades for them
|
||||
const cascadesSubjectBuilder = new CascadesSubjectBuilder(subjects);
|
||||
subjects.forEach(subject => {
|
||||
// next step we build list of subjects we will operate with
|
||||
// these subjects are subjects that we need to insert or update alongside with main persisted entity
|
||||
new CascadesSubjectBuilder(subject, subjects).build();
|
||||
cascadesSubjectBuilder.build(subject);
|
||||
});
|
||||
// console.timeEnd("building cascades...");
|
||||
|
||||
|
||||
@ -33,6 +33,12 @@ export class Subject {
|
||||
*/
|
||||
identifier: ObjectLiteral|undefined = undefined;
|
||||
|
||||
/**
|
||||
* If subject was created by cascades this property will contain subject
|
||||
* from where this subject was created.
|
||||
*/
|
||||
parentSubject?: Subject;
|
||||
|
||||
/**
|
||||
* Gets entity sent to the persistence (e.g. changed entity).
|
||||
* If entity is not set then this subject is created only for the entity loaded from the database,
|
||||
@ -93,6 +99,7 @@ export class Subject {
|
||||
|
||||
constructor(options: {
|
||||
metadata: EntityMetadata,
|
||||
parentSubject?: Subject,
|
||||
entity?: ObjectLiteral,
|
||||
databaseEntity?: ObjectLiteral,
|
||||
canBeInserted?: boolean,
|
||||
@ -104,6 +111,7 @@ export class Subject {
|
||||
this.metadata = options.metadata;
|
||||
this.entity = options.entity;
|
||||
this.databaseEntity = options.databaseEntity;
|
||||
this.parentSubject = options.parentSubject;
|
||||
if (options.canBeInserted !== undefined)
|
||||
this.canBeInserted = options.canBeInserted;
|
||||
if (options.canBeUpdated !== undefined)
|
||||
|
||||
@ -91,6 +91,12 @@ export class SubjectChangedColumnsComputer {
|
||||
// if value is not changed - then do nothing
|
||||
if (entityValue === databaseValue)
|
||||
return;
|
||||
|
||||
// revert entity value back to its original value, because we need to save original value, not a string
|
||||
// we used string only for comparision
|
||||
if (column.type === "json" || column.type === "jsonb") {
|
||||
entityValue = column.getEntityValue(subject.entity!);
|
||||
}
|
||||
}
|
||||
|
||||
// find if there is already a column to be changed
|
||||
|
||||
@ -43,11 +43,11 @@ export class SubjectDatabaseEntityLoader {
|
||||
if (subject.databaseEntity)
|
||||
return;
|
||||
|
||||
// we only need entity id
|
||||
if (!subject.metadata.hasAllPrimaryKeys(subject.entity!)) // can we use getEntityIdMap instead
|
||||
const idMap = subject.metadata.getEntityIdMap(subject.entity!);
|
||||
if (!idMap) // we don't need not-saved entities (entities without id set)
|
||||
return;
|
||||
|
||||
allIds.push(subject.metadata.getEntityIdMap(subject.entity!)!);
|
||||
allIds.push(idMap);
|
||||
});
|
||||
|
||||
// if there no ids found (means all entities are new and have generated ids) - then nothing to load there
|
||||
|
||||
@ -13,6 +13,9 @@ import {SaveOptions} from "../repository/SaveOptions";
|
||||
import {RemoveOptions} from "../repository/RemoveOptions";
|
||||
import {BroadcasterResult} from "../subscriber/BroadcasterResult";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
import {NestedSetSubjectExecutor} from "./tree/NestedSetSubjectExecutor";
|
||||
import {ClosureSubjectExecutor} from "./tree/ClosureSubjectExecutor";
|
||||
import {MaterializedPathSubjectExecutor} from "./tree/MaterializedPathSubjectExecutor";
|
||||
|
||||
/**
|
||||
* Executes all database operations (inserts, updated, deletes) that must be executed
|
||||
@ -226,7 +229,14 @@ export class SubjectExecutor {
|
||||
});
|
||||
} else {
|
||||
subjects.forEach(subject => {
|
||||
if (subject.changeMaps.length === 0) {
|
||||
|
||||
// we do not insert in bulk in following cases:
|
||||
// - when there is no values in insert (only defaults are inserted), since we cannot use DEFAULT VALUES expression for multiple inserted rows
|
||||
// - when entity is a tree table, since tree tables require extra operation per each inserted row
|
||||
// - when oracle is used, since oracle's bulk insertion is very bad
|
||||
if (subject.changeMaps.length === 0 ||
|
||||
subject.metadata.treeType ||
|
||||
this.queryRunner.connection.driver instanceof OracleDriver) {
|
||||
singleInsertSubjects.push(subject);
|
||||
|
||||
} else {
|
||||
@ -273,23 +283,35 @@ export class SubjectExecutor {
|
||||
|
||||
// insert subjects which must be inserted in separate requests (all default values)
|
||||
if (singleInsertSubjects.length > 0) {
|
||||
await Promise.all(singleInsertSubjects.map(subject => {
|
||||
const updatedEntity = subject.createValueSetAndPopChangeMap(); // important to have because query builder sets inserted values into it
|
||||
return this.queryRunner
|
||||
await PromiseUtils.runInSequence(singleInsertSubjects, async subject => {
|
||||
subject.insertedValueSet = subject.createValueSetAndPopChangeMap(); // important to have because query builder sets inserted values into it
|
||||
|
||||
// for nested set we execute additional queries
|
||||
if (subject.metadata.treeType === "nested-set")
|
||||
await new NestedSetSubjectExecutor(this.queryRunner).insert(subject);
|
||||
|
||||
await this.queryRunner
|
||||
.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(subject.metadata.target)
|
||||
.values(updatedEntity)
|
||||
.values(subject.insertedValueSet)
|
||||
.updateEntity(this.options && this.options.reload === false ? false : true)
|
||||
.callListeners(false)
|
||||
.execute()
|
||||
.then(insertResult => {
|
||||
subject.identifier = insertResult.identifiers[0];
|
||||
subject.generatedMap = insertResult.generatedMaps[0];
|
||||
subject.insertedValueSet = updatedEntity;
|
||||
});
|
||||
}));
|
||||
|
||||
// for tree tables we execute additional queries
|
||||
if (subject.metadata.treeType === "closure-table") {
|
||||
await new ClosureSubjectExecutor(this.queryRunner).insert(subject);
|
||||
|
||||
} else if (subject.metadata.treeType === "materialized-path") {
|
||||
await new MaterializedPathSubjectExecutor(this.queryRunner).insert(subject);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -334,6 +356,18 @@ export class SubjectExecutor {
|
||||
|
||||
const updateResult = await updateQueryBuilder.execute();
|
||||
subject.generatedMap = updateResult.generatedMaps[0];
|
||||
|
||||
// experiments, remove probably, need to implement tree tables children removal
|
||||
// if (subject.updatedRelationMaps.length > 0) {
|
||||
// await Promise.all(subject.updatedRelationMaps.map(async updatedRelation => {
|
||||
// if (!updatedRelation.relation.isTreeParent) return;
|
||||
// if (!updatedRelation.value !== null) return;
|
||||
//
|
||||
// if (subject.metadata.treeType === "closure-table") {
|
||||
// await new ClosureSubjectExecutor(this.queryRunner).deleteChildrenOf(subject);
|
||||
// }
|
||||
// }));
|
||||
// }
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ export class SubjectTopoligicalSorter {
|
||||
|
||||
function visit(node: any, i: number, predecessors: any[]) {
|
||||
if (predecessors.indexOf(node) >= 0) {
|
||||
throw new Error("Cyclic dependency: " + JSON.stringify(node));
|
||||
throw new Error("Cyclic dependency: " + JSON.stringify(node)); // todo: better error
|
||||
}
|
||||
|
||||
if (!~nodes.indexOf(node)) {
|
||||
|
||||
@ -11,8 +11,7 @@ export class CascadesSubjectBuilder {
|
||||
// Constructor
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
constructor(protected subject: Subject,
|
||||
protected allSubjects: Subject[]) {
|
||||
constructor(protected allSubjects: Subject[]) {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@ -22,18 +21,7 @@ export class CascadesSubjectBuilder {
|
||||
/**
|
||||
* Builds a cascade subjects tree and pushes them in into the given array of subjects.
|
||||
*/
|
||||
build() {
|
||||
this.buildRecursively(this.subject);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Builds a cascade subjects recursively.
|
||||
*/
|
||||
protected buildRecursively(subject: Subject) {
|
||||
build(subject: Subject) {
|
||||
|
||||
subject.metadata
|
||||
.extractRelationValuesFromEntity(subject.entity!, subject.metadata.relations) // todo: we can create EntityMetadata.cascadeRelations
|
||||
@ -64,6 +52,7 @@ export class CascadesSubjectBuilder {
|
||||
// and add to the array of subjects to load only if there is no same entity there already
|
||||
const relationEntitySubject = new Subject({
|
||||
metadata: relationEntityMetadata,
|
||||
parentSubject: subject,
|
||||
entity: relationEntity,
|
||||
canBeInserted: relation.isCascadeInsert === true,
|
||||
canBeUpdated: relation.isCascadeUpdate === true
|
||||
@ -71,10 +60,14 @@ export class CascadesSubjectBuilder {
|
||||
this.allSubjects.push(relationEntitySubject);
|
||||
|
||||
// go recursively and find other entities we need to insert/update
|
||||
this.buildRecursively(relationEntitySubject);
|
||||
this.build(relationEntitySubject);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Finds subject where entity like given subject's entity.
|
||||
* Comparision made by entity id.
|
||||
|
||||
@ -72,6 +72,7 @@ export class ManyToManySubjectBuilder {
|
||||
relatedEntityRelationIdsInDatabase.forEach(relationId => {
|
||||
const junctionSubject = new Subject({
|
||||
metadata: relation.junctionEntityMetadata!,
|
||||
parentSubject: subject,
|
||||
mustBeRemoved: true,
|
||||
identifier: this.buildJunctionIdentifier(subject, relation, relationId)
|
||||
});
|
||||
@ -158,6 +159,7 @@ export class ManyToManySubjectBuilder {
|
||||
// create a new subject for insert operation of junction rows
|
||||
const junctionSubject = new Subject({
|
||||
metadata: relation.junctionEntityMetadata!,
|
||||
parentSubject: subject,
|
||||
canBeInserted: true,
|
||||
});
|
||||
this.subjects.push(junctionSubject);
|
||||
@ -195,6 +197,7 @@ export class ManyToManySubjectBuilder {
|
||||
removedJunctionEntityIds.forEach(removedEntityRelationId => {
|
||||
const junctionSubject = new Subject({
|
||||
metadata: relation.junctionEntityMetadata!,
|
||||
parentSubject: subject,
|
||||
mustBeRemoved: true,
|
||||
identifier: this.buildJunctionIdentifier(subject, relation, removedEntityRelationId)
|
||||
});
|
||||
|
||||
@ -129,6 +129,7 @@ export class OneToManySubjectBuilder {
|
||||
if (!relatedEntitySubject) {
|
||||
relatedEntitySubject = new Subject({
|
||||
metadata: relation.inverseEntityMetadata,
|
||||
parentSubject: subject,
|
||||
canBeUpdated: true,
|
||||
identifier: relationIdMap
|
||||
});
|
||||
@ -159,6 +160,7 @@ export class OneToManySubjectBuilder {
|
||||
// we create a new subject which operations will be executed in subject operation executor
|
||||
const removedRelatedEntitySubject = new Subject({
|
||||
metadata: relation.inverseEntityMetadata,
|
||||
parentSubject: subject,
|
||||
canBeUpdated: true,
|
||||
identifier: removedRelatedEntityRelationId,
|
||||
changeMaps: [{
|
||||
|
||||
@ -82,6 +82,7 @@ export class OneToOneInverseSideSubjectBuilder {
|
||||
|
||||
const removedRelatedEntitySubject = new Subject({
|
||||
metadata: relation.inverseEntityMetadata,
|
||||
parentSubject: subject,
|
||||
canBeUpdated: true,
|
||||
identifier: relatedEntityDatabaseRelationId,
|
||||
changeMaps: [{
|
||||
|
||||
96
src/persistence/tree/ClosureSubjectExecutor.ts
Normal file
96
src/persistence/tree/ClosureSubjectExecutor.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import {Subject} from "../Subject";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {ObjectLiteral} from "../../common/ObjectLiteral";
|
||||
import {CannotAttachTreeChildrenEntityError} from "../../error/CannotAttachTreeChildrenEntityError";
|
||||
|
||||
/**
|
||||
* Executes subject operations for closure entities.
|
||||
*/
|
||||
export class ClosureSubjectExecutor {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(protected queryRunner: QueryRunner) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Removes all children of the given subject's entity.
|
||||
|
||||
async deleteChildrenOf(subject: Subject) {
|
||||
// const relationValue = subject.metadata.treeParentRelation.getEntityValue(subject.databaseEntity);
|
||||
// console.log("relationValue: ", relationValue);
|
||||
// this.queryRunner.manager
|
||||
// .createQueryBuilder()
|
||||
// .from(subject.metadata.closureJunctionTable.target, "tree")
|
||||
// .where("tree.");
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Executes operations when subject is being inserted.
|
||||
*/
|
||||
async insert(subject: Subject): Promise<void> {
|
||||
|
||||
// create values to be inserted into the closure junction
|
||||
const closureJunctionInsertMap: ObjectLiteral = {};
|
||||
subject.metadata.closureJunctionTable.ancestorColumns.forEach(column => {
|
||||
closureJunctionInsertMap[column.databaseName] = subject.identifier;
|
||||
});
|
||||
subject.metadata.closureJunctionTable.descendantColumns.forEach(column => {
|
||||
closureJunctionInsertMap[column.databaseName] = subject.identifier;
|
||||
});
|
||||
|
||||
// insert values into the closure junction table
|
||||
await this.queryRunner
|
||||
.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(subject.metadata.closureJunctionTable.tablePath)
|
||||
.values(closureJunctionInsertMap)
|
||||
.updateEntity(false)
|
||||
.callListeners(false)
|
||||
.execute();
|
||||
|
||||
let parent = subject.metadata.treeParentRelation!.getEntityValue(subject.entity!); // if entity was attached via parent
|
||||
if (!parent && subject.parentSubject && subject.parentSubject.entity) // if entity was attached via children
|
||||
parent = subject.parentSubject.insertedValueSet ? subject.parentSubject.insertedValueSet : subject.parentSubject.entity;
|
||||
|
||||
if (parent) {
|
||||
const escape = (alias: string) => this.queryRunner.connection.driver.escape(alias);
|
||||
const tableName = escape(subject.metadata.closureJunctionTable.tablePath); // todo: make sure to properly escape table path, not just a table name
|
||||
const ancestorColumnNames = subject.metadata.closureJunctionTable.ancestorColumns.map(column => {
|
||||
return escape(column.databaseName);
|
||||
});
|
||||
const descendantColumnNames = subject.metadata.closureJunctionTable.descendantColumns.map(column => {
|
||||
return escape(column.databaseName);
|
||||
});
|
||||
const firstQueryParameters: any[] = [];
|
||||
const childEntityIdValues = subject.metadata.primaryColumns.map(column => column.getEntityValue(subject.insertedValueSet!));
|
||||
const childEntityIds1 = subject.metadata.primaryColumns.map((column, index) => {
|
||||
firstQueryParameters.push(childEntityIdValues[index]);
|
||||
return this.queryRunner.connection.driver.createParameter("child_entity_" + column.databaseName, firstQueryParameters.length - 1);
|
||||
});
|
||||
const whereCondition = subject.metadata.primaryColumns.map(column => {
|
||||
const columnName = escape(column.databaseName + "_descendant");
|
||||
const parentId = column.getEntityValue(parent);
|
||||
if (!parentId)
|
||||
throw new CannotAttachTreeChildrenEntityError(subject.metadata.name);
|
||||
|
||||
firstQueryParameters.push(parentId);
|
||||
const parameterName = this.queryRunner.connection.driver.createParameter("parent_entity_" + column.databaseName, firstQueryParameters.length - 1);
|
||||
return columnName + " = " + parameterName;
|
||||
}).join(", ");
|
||||
await this.queryRunner.query(
|
||||
`INSERT INTO ${tableName} (${[...ancestorColumnNames, ...descendantColumnNames].join(", ")}) ` +
|
||||
`SELECT ${ancestorColumnNames.join(", ")}, ${childEntityIds1.join(", ")} FROM ${tableName} WHERE ${whereCondition}`,
|
||||
firstQueryParameters
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
56
src/persistence/tree/MaterializedPathSubjectExecutor.ts
Normal file
56
src/persistence/tree/MaterializedPathSubjectExecutor.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import {Subject} from "../Subject";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
|
||||
/**
|
||||
* Executes subject operations for materialized-path tree entities.
|
||||
*/
|
||||
export class MaterializedPathSubjectExecutor {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(protected queryRunner: QueryRunner) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Executes operations when subject is being inserted.
|
||||
*/
|
||||
async insert(subject: Subject): Promise<void> {
|
||||
|
||||
let parent = subject.metadata.treeParentRelation!.getEntityValue(subject.entity!); // if entity was attached via parent
|
||||
if (!parent && subject.parentSubject && subject.parentSubject.entity) // if entity was attached via children
|
||||
parent = subject.parentSubject.insertedValueSet ? subject.parentSubject.insertedValueSet : subject.parentSubject.entity;
|
||||
|
||||
const parentId = subject.metadata.getEntityIdMap(parent);
|
||||
|
||||
let parentPath: string = "";
|
||||
if (parentId) {
|
||||
parentPath = await this.queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select(subject.metadata.targetName + "." + subject.metadata.materializedPathColumn!.propertyPath, "path")
|
||||
.from(subject.metadata.target, subject.metadata.targetName)
|
||||
.whereInIds(parentId)
|
||||
.getRawOne()
|
||||
.then(result => result ? result["path"] : undefined);
|
||||
}
|
||||
|
||||
const insertedEntityId = subject.metadata.treeParentRelation!.joinColumns.map(joinColumn => {
|
||||
return joinColumn.referencedColumn!.getEntityValue(subject.insertedValueSet!);
|
||||
}).join("_");
|
||||
|
||||
await this.queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.update(subject.metadata.target)
|
||||
.set({
|
||||
[subject.metadata.materializedPathColumn!.propertyPath]: parentPath + insertedEntityId + "."
|
||||
})
|
||||
.where(subject.identifier!)
|
||||
.execute();
|
||||
}
|
||||
|
||||
}
|
||||
66
src/persistence/tree/NestedSetSubjectExecutor.ts
Normal file
66
src/persistence/tree/NestedSetSubjectExecutor.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {Subject} from "../Subject";
|
||||
import {QueryRunner} from "../../query-runner/QueryRunner";
|
||||
import {OrmUtils} from "../../util/OrmUtils";
|
||||
|
||||
/**
|
||||
* Executes subject operations for nested set tree entities.
|
||||
*/
|
||||
export class NestedSetSubjectExecutor {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(protected queryRunner: QueryRunner) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Executes operations when subject is being inserted.
|
||||
*/
|
||||
async insert(subject: Subject): Promise<void> {
|
||||
const escape = (alias: string) => this.queryRunner.connection.driver.escape(alias);
|
||||
const tableName = escape(subject.metadata.tablePath);
|
||||
const leftColumnName = escape(subject.metadata.nestedSetLeftColumn!.databaseName);
|
||||
const rightColumnName = escape(subject.metadata.nestedSetRightColumn!.databaseName);
|
||||
|
||||
let parent = subject.metadata.treeParentRelation!.getEntityValue(subject.entity!); // if entity was attached via parent
|
||||
if (!parent && subject.parentSubject && subject.parentSubject.entity) // if entity was attached via children
|
||||
parent = subject.parentSubject.insertedValueSet ? subject.parentSubject.insertedValueSet : subject.parentSubject.entity;
|
||||
const parentId = subject.metadata.getEntityIdMap(parent);
|
||||
|
||||
let parentNsRight: number|undefined = undefined;
|
||||
if (parentId) {
|
||||
parentNsRight = await this.queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select(subject.metadata.targetName + "." + subject.metadata.nestedSetRightColumn!.propertyPath, "right")
|
||||
.from(subject.metadata.target, subject.metadata.targetName)
|
||||
.whereInIds(parentId)
|
||||
.getRawOne()
|
||||
.then(result => result ? result["right"] : undefined);
|
||||
}
|
||||
|
||||
if (parentNsRight !== undefined) {
|
||||
await this.queryRunner.query(`UPDATE ${tableName} SET ` +
|
||||
`${leftColumnName} = CASE WHEN ${leftColumnName} > ${parentNsRight} THEN ${leftColumnName} + 2 ELSE ${leftColumnName} END,` +
|
||||
`${rightColumnName} = ${rightColumnName} + 2 ` +
|
||||
`WHERE ${rightColumnName} >= ${parentNsRight}`);
|
||||
|
||||
OrmUtils.mergeDeep(
|
||||
subject.insertedValueSet,
|
||||
subject.metadata.nestedSetLeftColumn!.createValueMap(parentNsRight),
|
||||
subject.metadata.nestedSetRightColumn!.createValueMap(parentNsRight + 1),
|
||||
);
|
||||
} else {
|
||||
OrmUtils.mergeDeep(
|
||||
subject.insertedValueSet,
|
||||
subject.metadata.nestedSetLeftColumn!.createValueMap(1),
|
||||
subject.metadata.nestedSetRightColumn!.createValueMap(2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -354,6 +354,18 @@ export class InsertQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
if (column.isVersion) {
|
||||
expression += "1";
|
||||
|
||||
// } else if (column.isNestedSetLeft) {
|
||||
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
||||
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
||||
// const subQuery = `(SELECT c.max + 1 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
||||
// expression += subQuery;
|
||||
//
|
||||
// } else if (column.isNestedSetRight) {
|
||||
// const tableName = this.connection.driver.escape(column.entityMetadata.tablePath);
|
||||
// const rightColumnName = this.connection.driver.escape(column.entityMetadata.nestedSetRightColumn!.databaseName);
|
||||
// const subQuery = `(SELECT c.max + 2 FROM (SELECT MAX(${rightColumnName}) as max from ${tableName}) c)`;
|
||||
// expression += subQuery;
|
||||
|
||||
} else if (column.isDiscriminator) {
|
||||
this.expressionMap.nativeParameters["discriminator_value"] = this.expressionMap.mainAlias!.metadata.discriminatorValue;
|
||||
expression += this.connection.driver.createParameter("discriminator_value", parametersCount);
|
||||
|
||||
@ -749,8 +749,8 @@ export abstract class QueryBuilder<Entity> {
|
||||
parameterIndex++;
|
||||
return `${aliasPath} = ${this.connection.driver.createParameter(parameterName, parameterIndex - 1)}`;
|
||||
}
|
||||
}).join(" AND ");
|
||||
}).join(" AND ");
|
||||
}).filter(expression => !!expression).join(" AND ");
|
||||
}).filter(expression => !!expression).join(" AND ");
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
321
src/query-builder/RelationIdLoader.ts
Normal file
321
src/query-builder/RelationIdLoader.ts
Normal file
@ -0,0 +1,321 @@
|
||||
import {Connection, ObjectLiteral} from "../";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {ColumnMetadata} from "../metadata/ColumnMetadata";
|
||||
|
||||
/**
|
||||
* Loads relation ids for the given entities.
|
||||
*/
|
||||
export class RelationIdLoader {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constructor(private connection: Connection) {
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Loads relation ids of the given entity or entities.
|
||||
*/
|
||||
async load(target: Function|string, relation: string, entities: ObjectLiteral|ObjectLiteral[], relatedEntities?: ObjectLiteral|ObjectLiteral[]): Promise<any[]>;
|
||||
|
||||
/**
|
||||
* Loads relation ids of the given entity or entities.
|
||||
*/
|
||||
async load(relation: RelationMetadata, entities: ObjectLiteral|ObjectLiteral[], relatedEntities?: ObjectLiteral|ObjectLiteral[]): Promise<any[]>;
|
||||
|
||||
/**
|
||||
* Loads relation ids of the given entity or entities.
|
||||
*/
|
||||
async load(relationOrTarget: RelationMetadata|Function|string, relationNameOrEntities: string|ObjectLiteral|ObjectLiteral[], entitiesOrRelatedEntities?: ObjectLiteral|ObjectLiteral[], maybeRelatedEntities?: ObjectLiteral|ObjectLiteral[]): Promise<any[]> {
|
||||
|
||||
// normalize arguments
|
||||
let relation: RelationMetadata|undefined, entities: ObjectLiteral[], relatedEntities: ObjectLiteral[]|undefined;
|
||||
if (relationOrTarget instanceof RelationMetadata) {
|
||||
relation = relationOrTarget;
|
||||
entities = relationNameOrEntities instanceof Array ? relationNameOrEntities as ObjectLiteral[] : [relationNameOrEntities as ObjectLiteral];
|
||||
relatedEntities = entitiesOrRelatedEntities instanceof Array ? entitiesOrRelatedEntities as ObjectLiteral[] : (entitiesOrRelatedEntities ? [entitiesOrRelatedEntities as ObjectLiteral] : undefined);
|
||||
|
||||
} else {
|
||||
const entityMetadata = this.connection.getMetadata(relationOrTarget);
|
||||
relation = entityMetadata.findRelationWithPropertyPath(relationNameOrEntities as string);
|
||||
if (!relation)
|
||||
throw new Error(`Relation "${relation}" was not found in "${entityMetadata.name}".`);
|
||||
|
||||
entities = entitiesOrRelatedEntities instanceof Array ? entitiesOrRelatedEntities as ObjectLiteral[] : [entitiesOrRelatedEntities as ObjectLiteral];
|
||||
relatedEntities = maybeRelatedEntities instanceof Array ? maybeRelatedEntities as ObjectLiteral[] : (maybeRelatedEntities ? [maybeRelatedEntities as ObjectLiteral] : undefined);
|
||||
}
|
||||
|
||||
// load relation ids depend of relation type
|
||||
if (relation.isManyToMany) {
|
||||
return this.loadForManyToMany(relation, entities, relatedEntities);
|
||||
|
||||
} else if (relation.isManyToOne || relation.isOneToOneOwner) {
|
||||
return this.loadForManyToOneAndOneToOneOwner(relation, entities, relatedEntities);
|
||||
|
||||
} else { // if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
return this.loadForOneToManyAndOneToOneNotOwner(relation, entities, relatedEntities);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads relation ids of the given entities and groups them into the object with parent and children.
|
||||
*
|
||||
* todo: extract this method?
|
||||
*/
|
||||
async loadManyToManyRelationIdsAndGroup<E1, E2>(
|
||||
relation: RelationMetadata,
|
||||
entitiesOrEntities: E1|E1[],
|
||||
relatedEntityOrEntities?: E2|E2[]
|
||||
): Promise<{ entity: E1, related?: E2|E2[] }[]> {
|
||||
|
||||
// console.log("relation:", relation.propertyName);
|
||||
// console.log("entitiesOrEntities", entitiesOrEntities);
|
||||
|
||||
if (!relatedEntityOrEntities) {
|
||||
relatedEntityOrEntities = await this.connection.relationLoader.load(relation, entitiesOrEntities);
|
||||
// console.log("relatedEntityOrEntities", relatedEntityOrEntities);
|
||||
}
|
||||
// const relationIds = await this.load(relation, relatedEntityOrEntities!, entitiesOrEntities);
|
||||
const relationIds = await this.load(relation, entitiesOrEntities, relatedEntityOrEntities);
|
||||
// console.log("relationIds", relationIds);
|
||||
|
||||
const entities: E1[] = entitiesOrEntities instanceof Array ? entitiesOrEntities : [entitiesOrEntities];
|
||||
const relatedEntities: E2[] = relatedEntityOrEntities instanceof Array ? relatedEntityOrEntities : [relatedEntityOrEntities!];
|
||||
|
||||
const isMany = relation.isManyToMany || relation.isOneToMany;
|
||||
let columns: ColumnMetadata[], inverseColumns: ColumnMetadata[];
|
||||
if (relation.isManyToManyOwner) {
|
||||
columns = relation.junctionEntityMetadata!.inverseColumns.map(column => column.referencedColumn!);
|
||||
inverseColumns = relation.junctionEntityMetadata!.ownerColumns.map(column => column.referencedColumn!);
|
||||
|
||||
} else if (relation.isManyToManyNotOwner) {
|
||||
columns = relation.junctionEntityMetadata!.ownerColumns.map(column => column.referencedColumn!);
|
||||
inverseColumns = relation.junctionEntityMetadata!.inverseColumns.map(column => column.referencedColumn!);
|
||||
|
||||
} else if (relation.isManyToOne || relation.isOneToOneOwner) {
|
||||
columns = relation.joinColumns.map(column => column.referencedColumn!);
|
||||
inverseColumns = relation.entityMetadata.primaryColumns;
|
||||
|
||||
} else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
columns = relation.inverseRelation!.entityMetadata.primaryColumns;
|
||||
inverseColumns = relation.inverseRelation!.joinColumns.map(column => column.referencedColumn!);
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
return entities.map(entity => {
|
||||
const group: { entity: E1, related?: E2|E2[] } = { entity: entity, related: isMany ? [] : undefined };
|
||||
relationIds.forEach(relationId => {
|
||||
const entityMatched = inverseColumns.every(column => {
|
||||
return column.getEntityValue(entity) === relationId[column.entityMetadata.name + "_" + column.propertyPath.replace(".", "_")];
|
||||
});
|
||||
if (entityMatched) {
|
||||
relatedEntities.forEach(relatedEntity => {
|
||||
const relatedEntityMatched = columns.every(column => {
|
||||
return column.getEntityValue(relatedEntity) === relationId[column.entityMetadata.name + "_" + column.propertyPath.replace(".", "_")];
|
||||
});
|
||||
if (relatedEntityMatched) {
|
||||
if (isMany) {
|
||||
(group.related as E2[]).push(relatedEntity);
|
||||
} else {
|
||||
group.related = relatedEntity;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return group;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads relation ids of the given entities and maps them into the given entity property.
|
||||
|
||||
async loadManyToManyRelationIdsAndMap(
|
||||
relation: RelationMetadata,
|
||||
entityOrEntities: ObjectLiteral|ObjectLiteral[],
|
||||
mapToEntityOrEntities: ObjectLiteral|ObjectLiteral[],
|
||||
propertyName: string
|
||||
): Promise<void> {
|
||||
|
||||
const relationIds = await this.loadManyToManyRelationIds(relation, entityOrEntities, mapToEntityOrEntities);
|
||||
const mapToEntities = mapToEntityOrEntities instanceof Array ? mapToEntityOrEntities : [mapToEntityOrEntities];
|
||||
const junctionMetadata = relation.junctionEntityMetadata!;
|
||||
const mainAlias = junctionMetadata.name;
|
||||
const columns = relation.isOwning ? junctionMetadata.inverseColumns : junctionMetadata.ownerColumns;
|
||||
const inverseColumns = relation.isOwning ? junctionMetadata.ownerColumns : junctionMetadata.inverseColumns;
|
||||
|
||||
mapToEntities.forEach(mapToEntity => {
|
||||
mapToEntity[propertyName] = [];
|
||||
relationIds.forEach(relationId => {
|
||||
const match = inverseColumns.every(column => {
|
||||
return column.referencedColumn!.getEntityValue(mapToEntity) === relationId[mainAlias + "_" + column.propertyName];
|
||||
});
|
||||
if (match) {
|
||||
if (columns.length === 1) {
|
||||
mapToEntity[propertyName].push(relationId[mainAlias + "_" + columns[0].propertyName]);
|
||||
|
||||
} else {
|
||||
const value = {};
|
||||
columns.forEach(column => {
|
||||
column.referencedColumn!.setEntityValue(value, relationId[mainAlias + "_" + column.propertyName]);
|
||||
});
|
||||
mapToEntity[propertyName].push(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}*/
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Protected Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Loads relation ids for the many-to-many relation.
|
||||
*/
|
||||
protected loadForManyToMany(relation: RelationMetadata, entities: ObjectLiteral[], relatedEntities?: ObjectLiteral[]) {
|
||||
|
||||
const junctionMetadata = relation.junctionEntityMetadata!;
|
||||
const mainAlias = junctionMetadata.name;
|
||||
const columns = relation.isOwning ? junctionMetadata.ownerColumns : junctionMetadata.inverseColumns;
|
||||
const inverseColumns = relation.isOwning ? junctionMetadata.inverseColumns : junctionMetadata.ownerColumns;
|
||||
const qb = this.connection.createQueryBuilder();
|
||||
|
||||
// select all columns from junction table
|
||||
junctionMetadata.ownerColumns.forEach(column => {
|
||||
const columnName = column.referencedColumn!.entityMetadata.name + "_" + column.referencedColumn!.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + column.propertyPath, columnName);
|
||||
});
|
||||
junctionMetadata.inverseColumns.forEach(column => {
|
||||
const columnName = column.referencedColumn!.entityMetadata.name + "_" + column.referencedColumn!.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + column.propertyPath, columnName);
|
||||
});
|
||||
|
||||
// add conditions for the given entities
|
||||
let condition1 = "";
|
||||
if (columns.length === 1) {
|
||||
qb.setParameter("values1", entities.map(entity => columns[0].referencedColumn!.getEntityValue(entity)));
|
||||
condition1 = mainAlias + "." + columns[0].propertyPath + " IN (:values1)"; // todo: use ANY for postgres
|
||||
|
||||
} else {
|
||||
condition1 = "(" + entities.map((entity, entityIndex) => {
|
||||
return columns.map(column => {
|
||||
const paramName = "entity1_" + entityIndex + "_" + column.propertyName;
|
||||
qb.setParameter(paramName, column.referencedColumn!.getEntityValue(entity));
|
||||
return mainAlias + "." + column.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ") + ")";
|
||||
}
|
||||
|
||||
// add conditions for the given inverse entities
|
||||
let condition2 = "";
|
||||
if (relatedEntities) {
|
||||
if (inverseColumns.length === 1) {
|
||||
qb.setParameter("values2", relatedEntities.map(entity => inverseColumns[0].referencedColumn!.getEntityValue(entity)));
|
||||
condition2 = mainAlias + "." + inverseColumns[0].propertyPath + " IN (:values2)"; // todo: use ANY for postgres
|
||||
|
||||
} else {
|
||||
condition2 = "(" + relatedEntities.map((entity, entityIndex) => {
|
||||
return inverseColumns.map(column => {
|
||||
const paramName = "entity2_" + entityIndex + "_" + column.propertyName;
|
||||
qb.setParameter(paramName, column.referencedColumn!.getEntityValue(entity));
|
||||
return mainAlias + "." + column.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
// execute query
|
||||
return qb
|
||||
.from(junctionMetadata.target, mainAlias)
|
||||
.where(condition1 + (condition2 ? " AND " + condition2 : ""))
|
||||
.getRawMany();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads relation ids for the many-to-one and one-to-one owner relations.
|
||||
*/
|
||||
protected loadForManyToOneAndOneToOneOwner(relation: RelationMetadata, entities: ObjectLiteral[], relatedEntities?: ObjectLiteral[]) {
|
||||
const mainAlias = relation.entityMetadata.targetName;
|
||||
|
||||
// select all columns we need
|
||||
const qb = this.connection.createQueryBuilder();
|
||||
relation.entityMetadata.primaryColumns.forEach(primaryColumn => {
|
||||
const columnName = primaryColumn.entityMetadata.name + "_" + primaryColumn.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + primaryColumn.propertyPath, columnName);
|
||||
});
|
||||
relation.joinColumns.forEach(column => {
|
||||
const columnName = column.referencedColumn!.entityMetadata.name + "_" + column.referencedColumn!.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + column.propertyPath, columnName);
|
||||
});
|
||||
|
||||
// add condition for entities
|
||||
let condition: string = "";
|
||||
if (relation.entityMetadata.primaryColumns.length === 1) {
|
||||
qb.setParameter("values", entities.map(entity => relation.entityMetadata.primaryColumns[0].getEntityValue(entity)));
|
||||
condition = mainAlias + "." + relation.entityMetadata.primaryColumns[0].propertyPath + " IN (:values)";
|
||||
|
||||
} else {
|
||||
condition = entities.map((entity, entityIndex) => {
|
||||
return relation.entityMetadata.primaryColumns.map((column, columnIndex) => {
|
||||
const paramName = "entity" + entityIndex + "_" + columnIndex;
|
||||
qb.setParameter(paramName, column.getEntityValue(entity));
|
||||
return mainAlias + "." + column.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ");
|
||||
}
|
||||
|
||||
// execute query
|
||||
return qb.from(relation.entityMetadata.target, mainAlias)
|
||||
.where(condition)
|
||||
.getRawMany();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads relation ids for the one-to-many and one-to-one not owner relations.
|
||||
*/
|
||||
protected loadForOneToManyAndOneToOneNotOwner(relation: RelationMetadata, entities: ObjectLiteral[], relatedEntities?: ObjectLiteral[]) {
|
||||
relation = relation.inverseRelation!;
|
||||
const mainAlias = relation.entityMetadata.targetName;
|
||||
|
||||
// select all columns we need
|
||||
const qb = this.connection.createQueryBuilder();
|
||||
relation.entityMetadata.primaryColumns.forEach(primaryColumn => {
|
||||
const columnName = primaryColumn.entityMetadata.name + "_" + primaryColumn.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + primaryColumn.propertyPath, columnName);
|
||||
});
|
||||
relation.joinColumns.forEach(column => {
|
||||
const columnName = column.referencedColumn!.entityMetadata.name + "_" + column.referencedColumn!.propertyPath.replace(".", "_");
|
||||
qb.addSelect(mainAlias + "." + column.propertyPath, columnName);
|
||||
});
|
||||
|
||||
// add condition for entities
|
||||
let condition: string = "";
|
||||
if (relation.joinColumns.length === 1) {
|
||||
qb.setParameter("values", entities.map(entity => relation.joinColumns[0].referencedColumn!.getEntityValue(entity)));
|
||||
condition = mainAlias + "." + relation.joinColumns[0].propertyPath + " IN (:values)";
|
||||
|
||||
} else {
|
||||
condition = entities.map((entity, entityIndex) => {
|
||||
return relation.joinColumns.map((joinColumn, joinColumnIndex) => {
|
||||
const paramName = "entity" + entityIndex + "_" + joinColumnIndex;
|
||||
qb.setParameter(paramName, joinColumn.referencedColumn!.getEntityValue(entity));
|
||||
return mainAlias + "." + joinColumn.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ");
|
||||
}
|
||||
|
||||
// execute query
|
||||
return qb.from(relation.entityMetadata.target, mainAlias)
|
||||
.where(condition)
|
||||
.getRawMany();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
import {Connection, ObjectLiteral} from "../";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
|
||||
/**
|
||||
* Helper class used to load relations and wrap lazy loaded relations.
|
||||
* Wraps entities and creates getters/setters for their relations
|
||||
* to be able to lazily load relations when accessing these relations.
|
||||
*/
|
||||
export class RelationLoader {
|
||||
|
||||
@ -21,18 +21,18 @@ export class RelationLoader {
|
||||
/**
|
||||
* Loads relation data for the given entity and its relation.
|
||||
*/
|
||||
load(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
|
||||
load(relation: RelationMetadata, entityOrEntities: ObjectLiteral|ObjectLiteral[]): Promise<any[]> { // todo: check all places where it uses non array
|
||||
if (relation.isManyToOne || relation.isOneToOneOwner) {
|
||||
return this.loadManyToOneOrOneToOneOwner(relation, entity);
|
||||
return this.loadManyToOneOrOneToOneOwner(relation, entityOrEntities);
|
||||
|
||||
} else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
|
||||
return this.loadOneToManyOrOneToOneNotOwner(relation, entity);
|
||||
return this.loadOneToManyOrOneToOneNotOwner(relation, entityOrEntities);
|
||||
|
||||
} else if (relation.isManyToManyOwner) {
|
||||
return this.loadManyToManyOwner(relation, entity);
|
||||
return this.loadManyToManyOwner(relation, entityOrEntities);
|
||||
|
||||
} else { // many-to-many non owner
|
||||
return this.loadManyToManyNotOwner(relation, entity);
|
||||
return this.loadManyToManyNotOwner(relation, entityOrEntities);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,25 +44,38 @@ export class RelationLoader {
|
||||
* example: SELECT category.id AS category_id, category.name AS category_name FROM category category
|
||||
* INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
|
||||
*/
|
||||
loadManyToOneOrOneToOneOwner(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
|
||||
const primaryColumns = relation.entityMetadata.primaryColumns;
|
||||
loadManyToOneOrOneToOneOwner(relation: RelationMetadata, entityOrEntities: ObjectLiteral|ObjectLiteral[]): Promise<any> {
|
||||
const entities = entityOrEntities instanceof Array ? entityOrEntities : [entityOrEntities];
|
||||
const columns = relation.entityMetadata.primaryColumns;
|
||||
const joinColumns = relation.isOwning ? relation.joinColumns : relation.inverseRelation!.joinColumns;
|
||||
const conditions = joinColumns.map(joinColumn => {
|
||||
return `${relation.entityMetadata.name}.${relation.propertyName} = ${relation.propertyName}.${joinColumn.referencedColumn!.propertyName}`;
|
||||
}).join(" AND ");
|
||||
|
||||
const joinAliasName = relation.entityMetadata.name;
|
||||
const qb = this.connection
|
||||
.createQueryBuilder()
|
||||
.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name, conditions);
|
||||
.innerJoin(relation.entityMetadata.target as Function, joinAliasName, conditions);
|
||||
|
||||
primaryColumns.forEach(primaryColumn => {
|
||||
qb.andWhere(`${relation.entityMetadata.name}.${primaryColumn.propertyPath} = :${primaryColumn.propertyName}`)
|
||||
.setParameter(`${primaryColumn.propertyName}`, primaryColumn.getEntityValue(entity));
|
||||
});
|
||||
if (columns.length === 1) {
|
||||
qb.where(`${joinAliasName}.${columns[0].propertyPath} IN (:${joinAliasName + "_" + columns[0].propertyName})`);
|
||||
qb.setParameter(joinAliasName + "_" + columns[0].propertyName, entities.map(entity => columns[0].getEntityValue(entity)));
|
||||
|
||||
return qb.getOne();
|
||||
} else {
|
||||
const condition = entities.map((entity, entityIndex) => {
|
||||
return columns.map((column, columnIndex) => {
|
||||
const paramName = joinAliasName + "_entity_" + entityIndex + "_" + columnIndex;
|
||||
qb.setParameter(paramName, column.getEntityValue(entity));
|
||||
return joinAliasName + "." + column.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ");
|
||||
qb.where(condition);
|
||||
}
|
||||
|
||||
return qb.getMany();
|
||||
// return qb.getOne(); todo: fix all usages
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,17 +85,31 @@ export class RelationLoader {
|
||||
* FROM post post
|
||||
* WHERE post.[joinColumn.name] = entity[joinColumn.referencedColumn]
|
||||
*/
|
||||
loadOneToManyOrOneToOneNotOwner(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
|
||||
loadOneToManyOrOneToOneNotOwner(relation: RelationMetadata, entityOrEntities: ObjectLiteral|ObjectLiteral[]): Promise<any> {
|
||||
const entities = entityOrEntities instanceof Array ? entityOrEntities : [entityOrEntities];
|
||||
const aliasName = relation.propertyName;
|
||||
const columns = relation.inverseRelation!.joinColumns;
|
||||
const qb = this.connection
|
||||
.createQueryBuilder()
|
||||
.select(relation.propertyName)
|
||||
.from(relation.inverseRelation!.entityMetadata.target, relation.propertyName);
|
||||
.select(aliasName)
|
||||
.from(relation.inverseRelation!.entityMetadata.target, aliasName);
|
||||
|
||||
relation.inverseRelation!.joinColumns.forEach(joinColumn => {
|
||||
qb.andWhere(`${relation.propertyName}.${joinColumn.propertyName} = :${joinColumn.referencedColumn!.propertyName}`)
|
||||
.setParameter(`${joinColumn.referencedColumn!.propertyName}`, joinColumn.referencedColumn!.getEntityValue(entity));
|
||||
});
|
||||
return relation.isOneToMany ? qb.getMany() : qb.getOne();
|
||||
if (columns.length === 1) {
|
||||
qb.where(`${aliasName}.${columns[0].propertyPath} IN (:${aliasName + "_" + columns[0].propertyName})`);
|
||||
qb.setParameter(aliasName + "_" + columns[0].propertyName, entities.map(entity => columns[0].referencedColumn!.getEntityValue(entity)));
|
||||
|
||||
} else {
|
||||
const condition = entities.map((entity, entityIndex) => {
|
||||
return columns.map((column, columnIndex) => {
|
||||
const paramName = aliasName + "_entity_" + entityIndex + "_" + columnIndex;
|
||||
qb.setParameter(paramName, column.referencedColumn!.getEntityValue(entity));
|
||||
return aliasName + "." + column.propertyPath + " = :" + paramName;
|
||||
}).join(" AND ");
|
||||
}).map(condition => "(" + condition + ")").join(" OR ");
|
||||
qb.where(condition);
|
||||
}
|
||||
return qb.getMany();
|
||||
// return relation.isOneToMany ? qb.getMany() : qb.getOne(); todo: fix all usages
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,17 +121,18 @@ export class RelationLoader {
|
||||
* ON post_categories.postId = :postId
|
||||
* AND post_categories.categoryId = category.id
|
||||
*/
|
||||
loadManyToManyOwner(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
|
||||
loadManyToManyOwner(relation: RelationMetadata, entityOrEntities: ObjectLiteral|ObjectLiteral[]): Promise<any> {
|
||||
const entities = entityOrEntities instanceof Array ? entityOrEntities : [entityOrEntities];
|
||||
const mainAlias = relation.propertyName;
|
||||
const joinAlias = relation.junctionEntityMetadata!.tableName;
|
||||
const joinColumnConditions = relation.joinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.propertyName} = :${joinColumn.propertyName}`;
|
||||
return `${joinAlias}.${joinColumn.propertyName} IN (:${joinColumn.propertyName})`;
|
||||
});
|
||||
const inverseJoinColumnConditions = relation.inverseJoinColumns.map(inverseJoinColumn => {
|
||||
return `${joinAlias}.${inverseJoinColumn.propertyName}=${mainAlias}.${inverseJoinColumn.referencedColumn!.propertyName}`;
|
||||
});
|
||||
const parameters = relation.joinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.propertyName] = joinColumn.referencedColumn!.getEntityValue(entity);
|
||||
parameters[joinColumn.propertyName] = entities.map(entity => joinColumn.referencedColumn!.getEntityValue(entity));
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
|
||||
@ -126,17 +154,18 @@ export class RelationLoader {
|
||||
* ON post_categories.postId = post.id
|
||||
* AND post_categories.categoryId = post_categories.categoryId
|
||||
*/
|
||||
loadManyToManyNotOwner(relation: RelationMetadata, entity: ObjectLiteral): Promise<any> {
|
||||
loadManyToManyNotOwner(relation: RelationMetadata, entityOrEntities: ObjectLiteral|ObjectLiteral[]): Promise<any> {
|
||||
const entities = entityOrEntities instanceof Array ? entityOrEntities : [entityOrEntities];
|
||||
const mainAlias = relation.propertyName;
|
||||
const joinAlias = relation.junctionEntityMetadata!.tableName;
|
||||
const joinColumnConditions = relation.inverseRelation!.joinColumns.map(joinColumn => {
|
||||
return `${joinAlias}.${joinColumn.propertyName} = ${mainAlias}.${joinColumn.referencedColumn!.propertyName}`;
|
||||
});
|
||||
const inverseJoinColumnConditions = relation.inverseRelation!.inverseJoinColumns.map(inverseJoinColumn => {
|
||||
return `${joinAlias}.${inverseJoinColumn.propertyName} = :${inverseJoinColumn.propertyName}`;
|
||||
return `${joinAlias}.${inverseJoinColumn.propertyName} IN (:${inverseJoinColumn.propertyName})`;
|
||||
});
|
||||
const parameters = relation.inverseRelation!.inverseJoinColumns.reduce((parameters, joinColumn) => {
|
||||
parameters[joinColumn.propertyName] = joinColumn.referencedColumn!.getEntityValue(entity);
|
||||
parameters[joinColumn.propertyName] = entities.map(entity => joinColumn.referencedColumn!.getEntityValue(entity));
|
||||
return parameters;
|
||||
}, {} as ObjectLiteral);
|
||||
|
||||
@ -169,6 +198,7 @@ export class RelationLoader {
|
||||
|
||||
// nothing is loaded yet, load relation data and save it in the model once they are loaded
|
||||
this[promiseIndex] = relationLoader.load(relation, this).then(result => {
|
||||
if (relation.isOneToOne || relation.isManyToOne) result = result[0];
|
||||
this[dataIndex] = result;
|
||||
this[resolveIndex] = true;
|
||||
delete this[promiseIndex];
|
||||
|
||||
@ -144,6 +144,14 @@ export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
* You can also provide id of relational entity to filter by.
|
||||
*/
|
||||
async loadOne<T = any>(): Promise<T|undefined> {
|
||||
return this.loadMany<T>().then(results => results[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entities (relational) from the relation.
|
||||
* You can also provide ids of relational entities to filter by.
|
||||
*/
|
||||
async loadMany<T = any>(): Promise<T[]> {
|
||||
let of = this.expressionMap.of;
|
||||
if (!(of instanceof Object)) {
|
||||
const metadata = this.expressionMap.mainAlias!.metadata;
|
||||
@ -156,12 +164,4 @@ export class RelationQueryBuilder<Entity> extends QueryBuilder<Entity> {
|
||||
return this.connection.relationLoader.load(this.expressionMap.relationMetadata, of);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entities (relational) from the relation.
|
||||
* You can also provide ids of relational entities to filter by.
|
||||
*/
|
||||
async loadMany<T = any>(): Promise<T[]> {
|
||||
return this.loadOne();
|
||||
}
|
||||
|
||||
}
|
||||
@ -210,7 +210,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoin(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoin(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs (without selection) entity's property.
|
||||
@ -218,29 +218,29 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoin(property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoin(property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs (without selection) given entity's table.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoin(entity: Function|string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoin(entity: Function|string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs (without selection) given table.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoin(tableName: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoin(tableName: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs (without selection).
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoin(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.join("INNER", entityOrProperty, aliasName, condition, parameters);
|
||||
innerJoin(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.join("INNER", entityOrProperty, alias, condition, parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -249,7 +249,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoin(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoin(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs (without selection) entity's property.
|
||||
@ -257,29 +257,29 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoin(property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoin(property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs (without selection) entity's table.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoin(entity: Function|string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoin(entity: Function|string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs (without selection) given table.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoin(tableName: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoin(tableName: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs (without selection).
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoin(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.join("LEFT", entityOrProperty, aliasName, condition, parameters);
|
||||
leftJoin(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.join("LEFT", entityOrProperty, alias, condition, parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -288,7 +288,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndSelect(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndSelect(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity's property and adds all selection properties to SELECT.
|
||||
@ -296,30 +296,30 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndSelect(property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndSelect(property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndSelect(entity: Function|string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndSelect(entity: Function|string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs table and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndSelect(tableName: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndSelect(tableName: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndSelect(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.innerJoin(entityOrProperty, aliasName, condition, parameters);
|
||||
innerJoinAndSelect(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.innerJoin(entityOrProperty, alias, condition, parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -328,7 +328,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndSelect(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndSelect(subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity's property and adds all selection properties to SELECT.
|
||||
@ -336,30 +336,30 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndSelect(property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndSelect(property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndSelect(entity: Function|string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndSelect(entity: Function|string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs table and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndSelect(tableName: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndSelect(tableName: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs and adds all selection properties to SELECT.
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndSelect(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.leftJoin(entityOrProperty, aliasName, condition, parameters);
|
||||
leftJoinAndSelect(entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.leftJoin(entityOrProperty, alias, condition, parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -371,7 +371,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapMany(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -381,7 +381,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapMany(mapToProperty: string, property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -390,7 +390,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapMany(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, entity: Function|string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -399,7 +399,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapMany(mapToProperty: string, tableName: string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapMany(mapToProperty: string, tableName: string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -408,9 +408,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.join("INNER", entityOrProperty, aliasName, condition, parameters, mapToProperty, true);
|
||||
innerJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.join("INNER", entityOrProperty, alias, condition, parameters, mapToProperty, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -422,7 +422,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapOne(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -432,7 +432,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapOne(mapToProperty: string, property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -441,7 +441,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapOne(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, entity: Function|string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -450,7 +450,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapOne(mapToProperty: string, tableName: string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
innerJoinAndMapOne(mapToProperty: string, tableName: string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* INNER JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -459,9 +459,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
innerJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.join("INNER", entityOrProperty, aliasName, condition, parameters, mapToProperty, false);
|
||||
innerJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.join("INNER", entityOrProperty, alias, condition, parameters, mapToProperty, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -473,7 +473,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapMany(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -483,7 +483,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapMany(mapToProperty: string, property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -492,7 +492,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapMany(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, entity: Function|string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -501,7 +501,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapMany(mapToProperty: string, tableName: string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapMany(mapToProperty: string, tableName: string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -510,9 +510,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.join("LEFT", entityOrProperty, aliasName, condition, parameters, mapToProperty, true);
|
||||
leftJoinAndMapMany(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.join("LEFT", entityOrProperty, alias, condition, parameters, mapToProperty, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -524,7 +524,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapOne(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, subQueryFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity's property, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -534,7 +534,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapOne(mapToProperty: string, property: string, aliasName: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, property: string, alias: string, condition?: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs entity's table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -543,7 +543,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapOne(mapToProperty: string, entity: Function|string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, entity: Function|string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs table, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -552,7 +552,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapOne(mapToProperty: string, tableName: string, aliasName: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
leftJoinAndMapOne(mapToProperty: string, tableName: string, alias: string, condition: string, parameters?: ObjectLiteral): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs, SELECTs the data returned by a join and MAPs all that data to some entity's property.
|
||||
@ -561,9 +561,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* You also need to specify an alias of the joined data.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
leftJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), aliasName: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(aliasName);
|
||||
this.join("LEFT", entityOrProperty, aliasName, condition, parameters, mapToProperty, false);
|
||||
leftJoinAndMapOne(mapToProperty: string, entityOrProperty: Function|string|((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>), alias: string, condition: string = "", parameters?: ObjectLiteral): this {
|
||||
this.addSelect(alias);
|
||||
this.join("LEFT", entityOrProperty, alias, condition, parameters, mapToProperty, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -600,7 +600,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
* LEFT JOINs relation id and maps it into some entity's property.
|
||||
* Optionally, you can add condition and parameters used in condition.
|
||||
*/
|
||||
loadRelationIdAndMap(mapToProperty: string, relationName: string, aliasName: string, queryBuilderFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): this;
|
||||
loadRelationIdAndMap(mapToProperty: string, relationName: string, alias: string, queryBuilderFactory: (qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>): this;
|
||||
|
||||
/**
|
||||
* LEFT JOINs relation id and maps it into some entity's property.
|
||||
@ -1326,7 +1326,8 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
const hasMainAlias = this.expressionMap.selects.some(select => select.selection === join.alias.name);
|
||||
if (hasMainAlias) {
|
||||
allSelects.push({ selection: this.escape(join.alias.name!) + ".*" });
|
||||
excludedSelects.push({ selection: this.escape(join.alias.name!) });
|
||||
const excludedSelect = this.expressionMap.selects.find(select => select.selection === join.alias.name);
|
||||
excludedSelects.push(excludedSelect!);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1609,6 +1610,9 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
// if user used partial selection and did not select some primary columns which are required to be selected
|
||||
// we select those primary columns and mark them as "virtual". Later virtual column values will be removed from final entity
|
||||
// to make entity contain exactly what user selected
|
||||
if (columns.length === 0) // however not in the case when nothing (even partial) was selected from this target (for example joins without selection)
|
||||
return [];
|
||||
|
||||
const nonSelectedPrimaryColumns = this.expressionMap.queryEntity ? metadata.primaryColumns.filter(primaryColumn => columns.indexOf(primaryColumn) === -1) : [];
|
||||
const allColumns = [...columns, ...nonSelectedPrimaryColumns];
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ import {MysqlDriver} from "../driver/mysql/MysqlDriver";
|
||||
import {WebsqlDriver} from "../driver/websql/WebsqlDriver";
|
||||
import {BroadcasterResult} from "../subscriber/BroadcasterResult";
|
||||
import {AbstractSqliteDriver} from "../driver/sqlite-abstract/AbstractSqliteDriver";
|
||||
import {OrderByCondition} from "../find-options/OrderByCondition";
|
||||
import {LimitOnUpdateNotSupportedError} from "../error/LimitOnUpdateNotSupportedError";
|
||||
import {OracleDriver} from "../driver/oracle/OracleDriver";
|
||||
|
||||
/**
|
||||
@ -40,6 +42,8 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
*/
|
||||
getQuery(): string {
|
||||
let sql = this.createUpdateExpression();
|
||||
sql += this.createOrderByExpression();
|
||||
sql += this.createLimitExpression();
|
||||
return sql.trim();
|
||||
}
|
||||
|
||||
@ -240,6 +244,71 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets ORDER BY condition in the query builder.
|
||||
* If you had previously ORDER BY expression defined,
|
||||
* calling this function will override previously set ORDER BY conditions.
|
||||
*
|
||||
* Calling order by without order set will remove all previously set order bys.
|
||||
*/
|
||||
orderBy(): this;
|
||||
|
||||
/**
|
||||
* Sets ORDER BY condition in the query builder.
|
||||
* If you had previously ORDER BY expression defined,
|
||||
* calling this function will override previously set ORDER BY conditions.
|
||||
*/
|
||||
orderBy(sort: string, order?: "ASC"|"DESC", nulls?: "NULLS FIRST"|"NULLS LAST"): this;
|
||||
|
||||
/**
|
||||
* Sets ORDER BY condition in the query builder.
|
||||
* If you had previously ORDER BY expression defined,
|
||||
* calling this function will override previously set ORDER BY conditions.
|
||||
*/
|
||||
orderBy(order: OrderByCondition): this;
|
||||
|
||||
/**
|
||||
* Sets ORDER BY condition in the query builder.
|
||||
* If you had previously ORDER BY expression defined,
|
||||
* calling this function will override previously set ORDER BY conditions.
|
||||
*/
|
||||
orderBy(sort?: string|OrderByCondition, order: "ASC"|"DESC" = "ASC", nulls?: "NULLS FIRST"|"NULLS LAST"): this {
|
||||
if (sort) {
|
||||
if (sort instanceof Object) {
|
||||
this.expressionMap.orderBys = sort as OrderByCondition;
|
||||
} else {
|
||||
if (nulls) {
|
||||
this.expressionMap.orderBys = { [sort as string]: { order, nulls } };
|
||||
} else {
|
||||
this.expressionMap.orderBys = { [sort as string]: order };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.expressionMap.orderBys = {};
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds ORDER BY condition in the query builder.
|
||||
*/
|
||||
addOrderBy(sort: string, order: "ASC"|"DESC" = "ASC", nulls?: "NULLS FIRST"|"NULLS LAST"): this {
|
||||
if (nulls) {
|
||||
this.expressionMap.orderBys[sort] = { order, nulls };
|
||||
} else {
|
||||
this.expressionMap.orderBys[sort] = order;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets LIMIT - maximum number of rows to be selected.
|
||||
*/
|
||||
limit(limit?: number): this {
|
||||
this.expressionMap.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if entity must be updated after update operation.
|
||||
* This may produce extra query or use RETURNING / OUTPUT statement (depend on database).
|
||||
@ -392,6 +461,42 @@ export class UpdateQueryBuilder<Entity> extends QueryBuilder<Entity> implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates "ORDER BY" part of SQL query.
|
||||
*/
|
||||
protected createOrderByExpression() {
|
||||
const orderBys = this.expressionMap.allOrderBys;
|
||||
if (Object.keys(orderBys).length > 0)
|
||||
return " ORDER BY " + Object.keys(orderBys)
|
||||
.map(columnName => {
|
||||
if (typeof orderBys[columnName] === "string") {
|
||||
return this.replacePropertyNames(columnName) + " " + orderBys[columnName];
|
||||
} else {
|
||||
return this.replacePropertyNames(columnName) + " " + (orderBys[columnName] as any).order + " " + (orderBys[columnName] as any).nulls;
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates "LIMIT" parts of SQL query.
|
||||
*/
|
||||
protected createLimitExpression(): string {
|
||||
let limit: number|undefined = this.expressionMap.limit;
|
||||
|
||||
if (limit) {
|
||||
if (this.connection.driver instanceof MysqlDriver) {
|
||||
return " LIMIT " + limit;
|
||||
} else {
|
||||
throw new LimitOnUpdateNotSupportedError();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets array of values need to be inserted into the target table.
|
||||
*/
|
||||
|
||||
@ -20,7 +20,7 @@ export class RepositoryFactory {
|
||||
*/
|
||||
create(manager: EntityManager, metadata: EntityMetadata, queryRunner?: QueryRunner): Repository<any> {
|
||||
|
||||
if (metadata.isClosure) {
|
||||
if (metadata.treeType) {
|
||||
// NOTE: dynamic access to protected properties. We need this to prevent unwanted properties in those classes to be exposed,
|
||||
// however we need these properties for internal work of the class
|
||||
const repository = new TreeRepository<any>();
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import {Repository} from "./Repository";
|
||||
import {SelectQueryBuilder} from "../query-builder/SelectQueryBuilder";
|
||||
import {ObjectLiteral} from "../common/ObjectLiteral";
|
||||
import {AbstractSqliteDriver} from "../driver/sqlite-abstract/AbstractSqliteDriver";
|
||||
|
||||
/**
|
||||
* Repository with additional functions to work with trees.
|
||||
@ -20,10 +22,7 @@ export class TreeRepository<Entity> extends Repository<Entity> {
|
||||
*/
|
||||
async findTrees(): Promise<Entity[]> {
|
||||
const roots = await this.findRoots();
|
||||
await Promise.all(roots.map(async root => {
|
||||
await this.findDescendantsTree(root);
|
||||
}));
|
||||
|
||||
await Promise.all(roots.map(root => this.findDescendantsTree(root)));
|
||||
return roots;
|
||||
}
|
||||
|
||||
@ -83,11 +82,59 @@ export class TreeRepository<Entity> extends Repository<Entity> {
|
||||
// create shortcuts for better readability
|
||||
const escape = (alias: string) => this.manager.connection.driver.escape(alias);
|
||||
|
||||
const joinCondition = `${escape(alias)}.${escape(this.metadata.primaryColumns[0].databaseName)}=${escape(closureTableAlias)}.${escape("descendant")}`;
|
||||
return this.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
|
||||
.where(`${escape(closureTableAlias)}.${escape("ancestor")}=${this.metadata.getEntityIdMap(entity)![this.metadata.primaryColumns[0].propertyName]}`);
|
||||
if (this.metadata.treeType === "closure-table") {
|
||||
|
||||
const joinCondition = this.metadata.closureJunctionTable.descendantColumns.map(column => {
|
||||
return escape(closureTableAlias) + "." + escape(column.propertyPath) + " = " + escape(alias) + "." + escape(column.referencedColumn!.propertyPath);
|
||||
}).join(" AND ");
|
||||
|
||||
const parameters: ObjectLiteral = {};
|
||||
const whereCondition = this.metadata.closureJunctionTable.ancestorColumns.map(column => {
|
||||
parameters[column.referencedColumn!.propertyName] = column.referencedColumn!.getEntityValue(entity);
|
||||
return escape(closureTableAlias) + "." + escape(column.propertyPath) + " = :" + column.referencedColumn!.propertyName;
|
||||
}).join(" AND ");
|
||||
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
|
||||
.where(whereCondition)
|
||||
.setParameters(parameters);
|
||||
|
||||
} else if (this.metadata.treeType === "nested-set") {
|
||||
|
||||
const whereCondition = alias + "." + this.metadata.nestedSetLeftColumn!.propertyPath + " BETWEEN " +
|
||||
"joined." + this.metadata.nestedSetLeftColumn!.propertyPath + " AND joined." + this.metadata.nestedSetRightColumn!.propertyPath;
|
||||
const parameters: ObjectLiteral = {};
|
||||
const joinCondition = this.metadata.treeParentRelation!.joinColumns.map(joinColumn => {
|
||||
const parameterName = joinColumn.referencedColumn!.propertyPath.replace(".", "_");
|
||||
parameters[parameterName] = joinColumn.referencedColumn!.getEntityValue(entity);
|
||||
return "joined." + joinColumn.referencedColumn!.propertyPath + " = :" + parameterName;
|
||||
}).join(" AND ");
|
||||
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.targetName, "joined", whereCondition)
|
||||
.where(joinCondition, parameters);
|
||||
|
||||
} else if (this.metadata.treeType === "materialized-path") {
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.where(qb => {
|
||||
const subQuery = qb.subQuery()
|
||||
.select(this.metadata.materializedPathColumn!.propertyPath, "path")
|
||||
.from(this.metadata.target, this.metadata.targetName)
|
||||
.whereInIds(this.metadata.getEntityIdMap(entity));
|
||||
|
||||
qb.setNativeParameters(subQuery.expressionMap.nativeParameters);
|
||||
if (this.manager.connection.driver instanceof AbstractSqliteDriver) {
|
||||
return this.metadata.materializedPathColumn!.propertyPath + " LIKE " + subQuery.getQuery() + " || '%'";
|
||||
} else {
|
||||
return this.metadata.materializedPathColumn!.propertyPath + " LIKE CONCAT(" + subQuery.getQuery() + ", '%')";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Supported only in tree entities`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -129,13 +176,63 @@ export class TreeRepository<Entity> extends Repository<Entity> {
|
||||
createAncestorsQueryBuilder(alias: string, closureTableAlias: string, entity: Entity): SelectQueryBuilder<Entity> {
|
||||
|
||||
// create shortcuts for better readability
|
||||
const escapeAlias = (alias: string) => this.manager.connection.driver.escape(alias);
|
||||
const escapeColumn = (column: string) => this.manager.connection.driver.escape(column);
|
||||
const escape = (alias: string) => this.manager.connection.driver.escape(alias);
|
||||
|
||||
const joinCondition = `${escapeAlias(alias)}.${escapeColumn(this.metadata.primaryColumns[0].databaseName)}=${escapeAlias(closureTableAlias)}.${escapeColumn("ancestor")}`;
|
||||
return this.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
|
||||
.where(`${escapeAlias(closureTableAlias)}.${escapeColumn("descendant")}=${this.metadata.getEntityIdMap(entity)![this.metadata.primaryColumns[0].propertyName]}`);
|
||||
if (this.metadata.treeType === "closure-table") {
|
||||
const joinCondition = this.metadata.closureJunctionTable.ancestorColumns.map(column => {
|
||||
return escape(closureTableAlias) + "." + escape(column.propertyPath) + " = " + escape(alias) + "." + escape(column.referencedColumn!.propertyPath);
|
||||
}).join(" AND ");
|
||||
|
||||
const parameters: ObjectLiteral = {};
|
||||
const whereCondition = this.metadata.closureJunctionTable.descendantColumns.map(column => {
|
||||
parameters[column.referencedColumn!.propertyName] = column.referencedColumn!.getEntityValue(entity);
|
||||
return escape(closureTableAlias) + "." + escape(column.propertyPath) + " = :" + column.referencedColumn!.propertyName;
|
||||
}).join(" AND ");
|
||||
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.closureJunctionTable.tableName, closureTableAlias, joinCondition)
|
||||
.where(whereCondition)
|
||||
.setParameters(parameters);
|
||||
|
||||
} else if (this.metadata.treeType === "nested-set") {
|
||||
|
||||
const joinCondition = "joined." + this.metadata.nestedSetLeftColumn!.propertyPath + " BETWEEN " +
|
||||
alias + "." + this.metadata.nestedSetLeftColumn!.propertyPath + " AND " + alias + "." + this.metadata.nestedSetRightColumn!.propertyPath;
|
||||
const parameters: ObjectLiteral = {};
|
||||
const whereCondition = this.metadata.treeParentRelation!.joinColumns.map(joinColumn => {
|
||||
const parameterName = joinColumn.referencedColumn!.propertyPath.replace(".", "_");
|
||||
parameters[parameterName] = joinColumn.referencedColumn!.getEntityValue(entity);
|
||||
return "joined." + joinColumn.referencedColumn!.propertyPath + " = :" + parameterName;
|
||||
}).join(" AND ");
|
||||
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.innerJoin(this.metadata.targetName, "joined", joinCondition)
|
||||
.where(whereCondition, parameters);
|
||||
|
||||
|
||||
} else if (this.metadata.treeType === "materialized-path") {
|
||||
// example: SELECT * FROM category WHERE (SELECT mpath FROM `category` WHERE id = 2) LIKE CONCAT(mpath, '%');
|
||||
return this
|
||||
.createQueryBuilder(alias)
|
||||
.where(qb => {
|
||||
const subQuery = qb.subQuery()
|
||||
.select(this.metadata.materializedPathColumn!.propertyPath, "path")
|
||||
.from(this.metadata.target, this.metadata.targetName)
|
||||
.whereInIds(this.metadata.getEntityIdMap(entity));
|
||||
|
||||
qb.setNativeParameters(subQuery.expressionMap.nativeParameters);
|
||||
if (this.manager.connection.driver instanceof AbstractSqliteDriver) {
|
||||
return subQuery.getQuery() + " LIKE " + this.metadata.materializedPathColumn!.propertyPath + " || '%'";
|
||||
|
||||
} else {
|
||||
return subQuery.getQuery() + " LIKE CONCAT(" + this.metadata.materializedPathColumn!.propertyPath + ", '%')";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Supported only in tree entities`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -314,7 +314,8 @@ export class Broadcaster {
|
||||
return !subscriber.listenTo ||
|
||||
!subscriber.listenTo() ||
|
||||
subscriber.listenTo() === Object ||
|
||||
subscriber.listenTo() === target;
|
||||
subscriber.listenTo() === target ||
|
||||
subscriber.listenTo().isPrototypeOf(target);
|
||||
}
|
||||
|
||||
}
|
||||
@ -174,6 +174,14 @@ export class DateUtils {
|
||||
return value;
|
||||
}
|
||||
|
||||
static simpleJsonToString(value: any): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
static stringToSimpleJson(value: string) {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Static Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import {ClosureEntity} from "../../../../src/decorator/entity/ClosureEntity";
|
||||
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../src/decorator/columns/Column";
|
||||
import {TreeParent} from "../../../../src/decorator/tree/TreeParent";
|
||||
import {TreeChildren} from "../../../../src/decorator/tree/TreeChildren";
|
||||
import {TreeLevelColumn} from "../../../../src/decorator/tree/TreeLevelColumn";
|
||||
import {Entity} from "../../../../src/decorator/entity/Entity";
|
||||
import {Tree} from "../../../../src/decorator/tree/Tree";
|
||||
|
||||
@ClosureEntity("CaTeGoRy")
|
||||
@Entity("CaTeGoRy")
|
||||
@Tree("closure-table")
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
|
||||
@ -62,6 +62,7 @@ describe("database schema > column types > mssql", () => {
|
||||
post.time = "15:30:00";
|
||||
post.datetimeoffset = new Date();
|
||||
post.simpleArray = ["A", "B", "C"];
|
||||
post.simpleJson = { param: "VALUE" };
|
||||
await postRepository.save(post);
|
||||
|
||||
const loadedPost = (await postRepository.findOne(1))!;
|
||||
@ -102,6 +103,7 @@ describe("database schema > column types > mssql", () => {
|
||||
loadedPost.simpleArray[0].should.be.equal(post.simpleArray[0]);
|
||||
loadedPost.simpleArray[1].should.be.equal(post.simpleArray[1]);
|
||||
loadedPost.simpleArray[2].should.be.equal(post.simpleArray[2]);
|
||||
loadedPost.simpleJson.param.should.be.equal(post.simpleJson.param);
|
||||
|
||||
table!.findColumnByName("id")!.type.should.be.equal("int");
|
||||
table!.findColumnByName("name")!.type.should.be.equal("nvarchar");
|
||||
@ -143,6 +145,7 @@ describe("database schema > column types > mssql", () => {
|
||||
table!.findColumnByName("timeObj")!.type.should.be.equal("time");
|
||||
table!.findColumnByName("datetimeoffset")!.type.should.be.equal("datetimeoffset");
|
||||
table!.findColumnByName("simpleArray")!.type.should.be.equal("ntext");
|
||||
table!.findColumnByName("simpleJson")!.type.should.be.equal("ntext");
|
||||
|
||||
})));
|
||||
|
||||
|
||||
@ -114,10 +114,12 @@ export class Post {
|
||||
datetimeoffset: Date;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// TypeOrm Specific Type
|
||||
// TypeOrm Specific Types
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Column("simple-array")
|
||||
simpleArray: string[];
|
||||
|
||||
@Column("simple-json")
|
||||
simpleJson: { param: string };
|
||||
}
|
||||
@ -57,6 +57,7 @@ describe("database schema > column types > mysql", () => {
|
||||
post.classEnum1 = FruitEnum.Apple;
|
||||
post.json = { id: 1, name: "Post" };
|
||||
post.simpleArray = ["A", "B", "C"];
|
||||
post.simpleJson = { param: "VALUE" };
|
||||
await postRepository.save(post);
|
||||
|
||||
const loadedPost = (await postRepository.findOne(1))!;
|
||||
@ -91,6 +92,7 @@ describe("database schema > column types > mysql", () => {
|
||||
loadedPost.simpleArray[0].should.be.equal(post.simpleArray[0]);
|
||||
loadedPost.simpleArray[1].should.be.equal(post.simpleArray[1]);
|
||||
loadedPost.simpleArray[2].should.be.equal(post.simpleArray[2]);
|
||||
loadedPost.simpleJson.param.should.be.equal(post.simpleJson.param);
|
||||
|
||||
table!.findColumnByName("id")!.type.should.be.equal("int");
|
||||
table!.findColumnByName("id")!.length!.should.be.equal("11");
|
||||
@ -139,6 +141,7 @@ describe("database schema > column types > mysql", () => {
|
||||
table!.findColumnByName("classEnum1")!.enum![2].should.be.equal("banana");
|
||||
table!.findColumnByName("json")!.type.should.be.equal("json");
|
||||
table!.findColumnByName("simpleArray")!.type.should.be.equal("text");
|
||||
table!.findColumnByName("simpleJson")!.type.should.be.equal("text");
|
||||
|
||||
})));
|
||||
|
||||
|
||||
@ -93,4 +93,6 @@ export class Post {
|
||||
@Column("simple-array")
|
||||
simpleArray: string[];
|
||||
|
||||
@Column("simple-json")
|
||||
simpleJson: { param: string };
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user