Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for NOWAIT & SKIP LOCKED in Postgres #5927

Merged
merged 3 commits into from May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 33 additions & 31 deletions docs/find-options.md
Expand Up @@ -23,7 +23,7 @@ userRepository.find({ relations: ["profile", "photos", "videos", "videos.video_a
* `join` - joins needs to be performed for the entity. Extended version of "relations".

```typescript
userRepository.find({
userRepository.find({
join: {
alias: "user",
leftJoinAndSelect: {
Expand All @@ -40,7 +40,7 @@ userRepository.find({
```typescript
userRepository.find({ where: { firstName: "Timber", lastName: "Saw" } });
```
Querying a column from an embedded entity should be done with respect to the hierarchy in which it was defined. Example:
Querying a column from an embedded entity should be done with respect to the hierarchy in which it was defined. Example:

```typescript
userRepository.find({ where: { name: { first: "Timber", last: "Saw" } } });
Expand All @@ -57,7 +57,7 @@ userRepository.find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "user" WHERE ("firstName" = 'Timber' AND "lastName" = 'Saw') OR ("firstName" = 'Stan' AND "lastName" = 'Lee')
Expand All @@ -66,7 +66,7 @@ SELECT * FROM "user" WHERE ("firstName" = 'Timber' AND "lastName" = 'Saw') OR ("
* `order` - selection order.

```typescript
userRepository.find({
userRepository.find({
order: {
name: "ASC",
id: "DESC"
Expand All @@ -79,28 +79,28 @@ userRepository.find({
* `skip` - offset (paginated) from where entities should be taken.

```typescript
userRepository.find({
userRepository.find({
skip: 5
});
```

* `take` - limit (paginated) - max number of entities that should be taken.

```typescript
userRepository.find({
userRepository.find({
take: 10
});
```

** If you are using typeorm with MSSQL, and want to use `take` or `limit`, you need to use order as well or you will receive the following error: `'Invalid usage of the option NEXT in the FETCH statement.'`

```typescript
userRepository.find({
order: {
columnName: 'ASC'
},
skip: 0,
take: 10
userRepository.find({
order: {
columnName: 'ASC'
},
skip: 0,
take: 10
})
```

Expand All @@ -120,7 +120,7 @@ userRepository.find({
```
or
```ts
{ mode: "pessimistic_read"|"pessimistic_write"|"dirty_read" }
{ mode: "pessimistic_read"|"pessimistic_write"|"dirty_read"|"pessimistic_partial_write"|"pessimistic_write_or_fail" }
```

for example:
Expand All @@ -131,15 +131,17 @@ userRepository.findOne(1, {
})
```

`pessimistic_partial_write` and `pessimistic_write_or_fail` are supported only on Postgres and are equivalents of `SELECT .. FOR UPDATE SKIP LOCKED` and `SELECT .. FOR UPDATE NOWAIT`, accordingly.

Complete example of find options:

```typescript
userRepository.find({
userRepository.find({
select: ["firstName", "lastName"],
relations: ["profile", "photos", "videos"],
where: {
firstName: "Timber",
lastName: "Saw"
where: {
firstName: "Timber",
lastName: "Saw"
},
order: {
name: "ASC",
Expand All @@ -166,7 +168,7 @@ const loadedPosts = await connection.getRepository(Post).find({
})
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "title" != 'About #1'
Expand All @@ -182,7 +184,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" < 10
Expand All @@ -198,7 +200,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" <= 10
Expand All @@ -214,7 +216,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" > 10
Expand All @@ -230,7 +232,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" >= 10
Expand All @@ -246,7 +248,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "title" = 'About #2'
Expand All @@ -262,7 +264,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "title" LIKE '%out #%'
Expand All @@ -278,7 +280,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" BETWEEN 1 AND 10
Expand All @@ -294,7 +296,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "title" IN ('About #2','About #3')
Expand All @@ -310,7 +312,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query (Postgres notation):
will execute following query (Postgres notation):

```sql
SELECT * FROM "post" WHERE "title" = ANY(['About #2','About #3'])
Expand All @@ -326,7 +328,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "title" IS NULL
Expand All @@ -342,7 +344,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "likes" = "dislikes" - 4
Expand All @@ -359,7 +361,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE "currentDate" > NOW()
Expand All @@ -379,7 +381,7 @@ const loadedPosts = await connection.getRepository(Post).find({
});
```

will execute following query:
will execute following query:

```sql
SELECT * FROM "post" WHERE NOT("likes" > 10) AND NOT("title" = 'About #2')
Expand Down
4 changes: 2 additions & 2 deletions src/find-options/FindOneOptions.ts
Expand Up @@ -40,11 +40,11 @@ export interface FindOneOptions<Entity = any> {
/**
* Enables or disables query result caching.
*/
lock?: { mode: "optimistic", version: number|Date } | { mode: "pessimistic_read"|"pessimistic_write"|"dirty_read" };
lock?: { mode: "optimistic", version: number|Date } | { mode: "pessimistic_read"|"pessimistic_write"|"dirty_read"|"pessimistic_partial_write"|"pessimistic_write_or_fail" };

/**
* Indicates if soft-deleted rows should be included in entity result.
*/
*/
withDeleted?: boolean;

/**
Expand Down
4 changes: 2 additions & 2 deletions src/find-options/FindOptionsUtils.ts
Expand Up @@ -176,11 +176,11 @@ export class FindOptionsUtils {
if (options.lock) {
if (options.lock.mode === "optimistic") {
qb.setLock(options.lock.mode, options.lock.version as any);
} else if (options.lock.mode === "pessimistic_read" || options.lock.mode === "pessimistic_write" || options.lock.mode === "dirty_read") {
} else if (options.lock.mode === "pessimistic_read" || options.lock.mode === "pessimistic_write" || options.lock.mode === "dirty_read" || options.lock.mode === "pessimistic_partial_write" || options.lock.mode === "pessimistic_write_or_fail") {
qb.setLock(options.lock.mode);
}
}

if (options.withDeleted) {
qb.withDeleted();
}
Expand Down
2 changes: 1 addition & 1 deletion src/query-builder/QueryExpressionMap.ts
Expand Up @@ -150,7 +150,7 @@ export class QueryExpressionMap {
/**
* Locking mode.
*/
lockMode?: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update";
lockMode?: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"pessimistic_partial_write"|"pessimistic_write_or_fail"|"for_no_key_update";

/**
* Current version of the entity, used for locking.
Expand Down
20 changes: 17 additions & 3 deletions src/query-builder/SelectQueryBuilder.ts
Expand Up @@ -967,12 +967,12 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
/**
* Sets locking mode.
*/
setLock(lockMode: "pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update"): this;
setLock(lockMode: "pessimistic_read"|"pessimistic_write"|"dirty_read"|"pessimistic_partial_write"|"pessimistic_write_or_fail"|"for_no_key_update"): this;

/**
* Sets locking mode.
*/
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"for_no_key_update", lockVersion?: number|Date): this {
setLock(lockMode: "optimistic"|"pessimistic_read"|"pessimistic_write"|"dirty_read"|"pessimistic_partial_write"|"pessimistic_write_or_fail"|"for_no_key_update", lockVersion?: number|Date): this {
this.expressionMap.lockMode = lockMode;
this.expressionMap.lockVersion = lockVersion;
return this;
Expand Down Expand Up @@ -1672,6 +1672,20 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
} else {
throw new LockNotSupportedOnGivenDriverError();
}
case "pessimistic_partial_write":
if (driver instanceof PostgresDriver) {
return " FOR UPDATE SKIP LOCKED";

} else {
throw new LockNotSupportedOnGivenDriverError();
}
case "pessimistic_write_or_fail":
if (driver instanceof PostgresDriver) {
return " FOR UPDATE NOWAIT";
} else {
throw new LockNotSupportedOnGivenDriverError();
}

case "for_no_key_update":
if (driver instanceof PostgresDriver) {
return " FOR NO KEY UPDATE";
Expand Down Expand Up @@ -1812,7 +1826,7 @@ export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> implements
if (!this.expressionMap.mainAlias)
throw new Error(`Alias is not set. Use "from" method to set an alias.`);

if ((this.expressionMap.lockMode === "pessimistic_read" || this.expressionMap.lockMode === "pessimistic_write" || this.expressionMap.lockMode === "for_no_key_update") && !queryRunner.isTransactionActive)
if ((this.expressionMap.lockMode === "pessimistic_read" || this.expressionMap.lockMode === "pessimistic_write" || this.expressionMap.lockMode === "pessimistic_partial_write" || this.expressionMap.lockMode === "pessimistic_write_or_fail" || this.expressionMap.lockMode === "for_no_key_update") && !queryRunner.isTransactionActive)
throw new PessimisticLockTransactionRequiredError();

if (this.expressionMap.lockMode === "optimistic") {
Expand Down