typeorm-extensions 2.2.0-rc.5 → 2.2.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
- package/dist/extensions/order.extension.d.ts +2 -2
- package/dist/extensions/pagination.extension.d.ts +1 -1
- package/dist/extensions/period-filter.extension.d.ts +4 -0
- package/dist/extensions/period-filter.extension.js +2 -0
- package/dist/extensions/typed/index.d.ts +1 -0
- package/dist/extensions/typed/index.js +1 -0
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.d.ts +19 -0
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.js +22 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-root.extension.d.ts +26 -4
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +5 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +13 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +8 -4
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +2 -0
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +7 -3
- package/dist/extensions/typed/typed-where/typed-where.extension.js +2 -0
- package/dist/extensions/where-array.extension.d.ts +4 -0
- package/dist/extensions/where-array.extension.js +2 -0
- package/dist/extensions/where-is-any.extension.d.ts +41 -6
- package/dist/extensions/where-is-any.extension.js +2 -0
- package/dist/extensions/where-is-distinct-from.extension.d.ts +4 -0
- package/dist/extensions/where-is-distinct-from.extension.js +2 -0
- package/dist/extensions/where-is-in.extension.d.ts +38 -6
- package/dist/extensions/where-is-in.extension.js +2 -0
- package/dist/extensions/where-json.extension.d.ts +4 -0
- package/dist/extensions/where-json.extension.js +2 -0
- package/dist/extensions/where-relation-exists.extension.d.ts +6 -6
- package/dist/helpers.d.ts +4 -0
- package/dist/helpers.js +11 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/types/extensions/index.d.ts +1 -0
- package/dist/types/extensions/index.js +1 -0
- package/dist/types/extensions/typed-join-and-map.types.d.ts +6 -0
- package/dist/types/extensions/typed-join-and-map.types.js +2 -0
- package/dist/types/modules/typed-root.types.d.ts +11 -9
- package/dist/utils/query-builder.utils.d.ts +4 -6
- package/dist/utils/query-builder.utils.js +82 -21
- package/dist/utils/reserved-qualifier-guard.utils.d.ts +36 -0
- package/dist/utils/reserved-qualifier-guard.utils.js +98 -0
- package/dist/utils/reserved-qualifier.utils.d.ts +33 -0
- package/dist/utils/reserved-qualifier.utils.js +252 -0
- package/dist/utils/table.utils.d.ts +27 -11
- package/dist/utils/table.utils.js +49 -12
- package/package.json +2 -2
|
@@ -19,17 +19,17 @@ type RelatedCondition<Related extends ObjectLiteral> = (subQuery: SelectQueryBui
|
|
|
19
19
|
*/
|
|
20
20
|
export interface WhereRelationExistsMethods<Entity extends ObjectLiteral> {
|
|
21
21
|
/** Sets `WHERE EXISTS (…)` over the relation, replacing the WHERE conditions set before. */
|
|
22
|
-
whereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
22
|
+
whereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
23
23
|
/** Adds `AND EXISTS (…)` over the relation. */
|
|
24
|
-
andWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
24
|
+
andWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
25
25
|
/** Adds `OR EXISTS (…)` over the relation. */
|
|
26
|
-
orWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
26
|
+
orWhereRelationExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
27
27
|
/** Sets `WHERE NOT EXISTS (…)` over the relation, replacing the WHERE conditions set before. */
|
|
28
|
-
whereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
28
|
+
whereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
29
29
|
/** Adds `AND NOT EXISTS (…)` over the relation. */
|
|
30
|
-
andWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
30
|
+
andWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
31
31
|
/** Adds `OR NOT EXISTS (…)` over the relation. */
|
|
32
|
-
orWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
32
|
+
orWhereRelationNotExists<Related extends ObjectLiteral, Type extends ObjectLiteral = TypedRoot<Entity, this>>(relation: RelationSelection<Type, Related>, alias: string, where?: RelatedCondition<Related>): this;
|
|
33
33
|
}
|
|
34
34
|
declare module 'typeorm/query-builder/SelectQueryBuilder' {
|
|
35
35
|
interface SelectQueryBuilder<Entity extends ObjectLiteral> extends WhereRelationExistsMethods<Entity> {
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export * from './utils/table.utils';
|
|
2
2
|
export { escapeLikePattern } from './utils/search.utils';
|
|
3
|
+
export { COCKROACHDB_RESERVED_KEY_WORDS, findBareReservedQualifiers, POSTGRES_RESERVED_KEY_WORDS, } from './utils/reserved-qualifier.utils';
|
|
4
|
+
export type { ReservedQualifier } from './utils/reserved-qualifier.utils';
|
|
5
|
+
export { disableReservedQualifierGuard, enableReservedQualifierGuard, ReservedQualifierError, } from './utils/reserved-qualifier-guard.utils';
|
|
6
|
+
export type { ReservedQualifierGuardOptions } from './utils/reserved-qualifier-guard.utils';
|
package/dist/helpers.js
CHANGED
|
@@ -14,10 +14,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.escapeLikePattern = void 0;
|
|
17
|
+
exports.ReservedQualifierError = exports.enableReservedQualifierGuard = exports.disableReservedQualifierGuard = exports.POSTGRES_RESERVED_KEY_WORDS = exports.findBareReservedQualifiers = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.escapeLikePattern = void 0;
|
|
18
18
|
// Side-effect-free entry point: `typeorm-extensions/helpers`.
|
|
19
|
-
// Exports helpers (tableHelper, escapeLikePattern) only and patches nothing
|
|
20
|
-
// (`typeorm-extensions`) installs every QueryBuilder extension.
|
|
19
|
+
// Exports helpers (tableHelper, escapeLikePattern, the reserved qualifier guard) only and patches nothing on import;
|
|
20
|
+
// importing the package root (`typeorm-extensions`) installs every QueryBuilder extension.
|
|
21
21
|
__exportStar(require("./utils/table.utils"), exports);
|
|
22
22
|
var search_utils_1 = require("./utils/search.utils");
|
|
23
23
|
Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
|
|
24
|
+
var reserved_qualifier_utils_1 = require("./utils/reserved-qualifier.utils");
|
|
25
|
+
Object.defineProperty(exports, "COCKROACHDB_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS; } });
|
|
26
|
+
Object.defineProperty(exports, "findBareReservedQualifiers", { enumerable: true, get: function () { return reserved_qualifier_utils_1.findBareReservedQualifiers; } });
|
|
27
|
+
Object.defineProperty(exports, "POSTGRES_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS; } });
|
|
28
|
+
var reserved_qualifier_guard_utils_1 = require("./utils/reserved-qualifier-guard.utils");
|
|
29
|
+
Object.defineProperty(exports, "disableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.disableReservedQualifierGuard; } });
|
|
30
|
+
Object.defineProperty(exports, "enableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.enableReservedQualifierGuard; } });
|
|
31
|
+
Object.defineProperty(exports, "ReservedQualifierError", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.ReservedQualifierError; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -6,3 +6,7 @@ export type { Json } from './types/modules/proxy-callback.types';
|
|
|
6
6
|
export type { TypedRoot, WithTypedRoot } from './types/modules/typed-root.types';
|
|
7
7
|
export * from './utils/table.utils';
|
|
8
8
|
export { escapeLikePattern } from './utils/search.utils';
|
|
9
|
+
export { COCKROACHDB_RESERVED_KEY_WORDS, findBareReservedQualifiers, POSTGRES_RESERVED_KEY_WORDS, } from './utils/reserved-qualifier.utils';
|
|
10
|
+
export type { ReservedQualifier } from './utils/reserved-qualifier.utils';
|
|
11
|
+
export { disableReservedQualifierGuard, enableReservedQualifierGuard, ReservedQualifierError, } from './utils/reserved-qualifier-guard.utils';
|
|
12
|
+
export type { ReservedQualifierGuardOptions } from './utils/reserved-qualifier-guard.utils';
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.escapeLikePattern = void 0;
|
|
17
|
+
exports.ReservedQualifierError = exports.enableReservedQualifierGuard = exports.disableReservedQualifierGuard = exports.POSTGRES_RESERVED_KEY_WORDS = exports.findBareReservedQualifiers = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.escapeLikePattern = void 0;
|
|
18
18
|
__exportStar(require("./extensions"), exports);
|
|
19
19
|
__exportStar(require("./decorators"), exports);
|
|
20
20
|
__exportStar(require("./types/interfaces"), exports);
|
|
@@ -22,3 +22,11 @@ __exportStar(require("./types/extensions"), exports);
|
|
|
22
22
|
__exportStar(require("./utils/table.utils"), exports);
|
|
23
23
|
var search_utils_1 = require("./utils/search.utils");
|
|
24
24
|
Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
|
|
25
|
+
var reserved_qualifier_utils_1 = require("./utils/reserved-qualifier.utils");
|
|
26
|
+
Object.defineProperty(exports, "COCKROACHDB_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS; } });
|
|
27
|
+
Object.defineProperty(exports, "findBareReservedQualifiers", { enumerable: true, get: function () { return reserved_qualifier_utils_1.findBareReservedQualifiers; } });
|
|
28
|
+
Object.defineProperty(exports, "POSTGRES_RESERVED_KEY_WORDS", { enumerable: true, get: function () { return reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS; } });
|
|
29
|
+
var reserved_qualifier_guard_utils_1 = require("./utils/reserved-qualifier-guard.utils");
|
|
30
|
+
Object.defineProperty(exports, "disableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.disableReservedQualifierGuard; } });
|
|
31
|
+
Object.defineProperty(exports, "enableReservedQualifierGuard", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.enableReservedQualifierGuard; } });
|
|
32
|
+
Object.defineProperty(exports, "ReservedQualifierError", { enumerable: true, get: function () { return reserved_qualifier_guard_utils_1.ReservedQualifierError; } });
|
|
@@ -26,4 +26,5 @@ __exportStar(require("./search.types"), exports);
|
|
|
26
26
|
__exportStar(require("./typed-having.types"), exports);
|
|
27
27
|
__exportStar(require("./union.types"), exports);
|
|
28
28
|
__exportStar(require("./join-lateral.types"), exports);
|
|
29
|
+
__exportStar(require("./typed-join-and-map.types"), exports);
|
|
29
30
|
__exportStar(require("./query-returning.types"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SelectQueryBuilder } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* What a map join joins, as TypeORM's `leftJoinAndMapOne` and the others take it: an entity class or name, a
|
|
4
|
+
* relation (`'user.photos'`), a table name, or a factory that builds a subquery on the query builder it is given.
|
|
5
|
+
*/
|
|
6
|
+
export type JoinAndMapTarget = Function | string | ((qb: SelectQueryBuilder<any>) => SelectQueryBuilder<any>);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { ObjectLiteral } from 'typeorm';
|
|
2
|
-
declare const typedRoot: unique symbol;
|
|
3
2
|
declare class TypedRootMark<Root> {
|
|
4
|
-
|
|
3
|
+
__typedRoot?(): Root;
|
|
5
4
|
}
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
* with an optional
|
|
6
|
+
* A query builder whose typed callbacks take another root, as `typed<Root>()` returns it:
|
|
7
|
+
* `WithTypedRoot<SelectQueryBuilder<Entity>, Root>`, the builder with an optional `__typedRoot` method that only
|
|
8
|
+
* types carry. A builder of a marked entity, `SelectQueryBuilder<WithTypedRoot<Entity, Root>>`, takes the root too.
|
|
9
9
|
*/
|
|
10
|
-
export type WithTypedRoot<
|
|
10
|
+
export type WithTypedRoot<Marked, Root extends ObjectLiteral> = Marked & TypedRootMark<Root>;
|
|
11
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
12
|
+
type BuilderRoot<Builder> = '__typedRoot' extends keyof Builder ? Builder extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : never : never : never;
|
|
13
|
+
type EntityRoot<Entity> = '__typedRoot' extends keyof Entity ? IsAny<Entity['__typedRoot' & keyof Entity]> extends true ? Entity : Entity extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : Entity : Entity : Entity;
|
|
11
14
|
/**
|
|
12
|
-
* The root of a builder's typed callbacks: the one set with `typed<Root>()
|
|
13
|
-
*
|
|
14
|
-
* optional mark too, and its root would be inferred as `unknown`.
|
|
15
|
+
* The root of a builder's typed callbacks: the one set with `typed<Root>()` on the builder (`Builder`, which the
|
|
16
|
+
* typed methods pass as `this`) or on its entity, or else the entity.
|
|
15
17
|
*/
|
|
16
|
-
export type TypedRoot<Entity
|
|
18
|
+
export type TypedRoot<Entity, Builder = unknown> = [BuilderRoot<Builder>] extends [never] ? EntityRoot<Entity> : BuilderRoot<Builder>;
|
|
17
19
|
export {};
|
|
@@ -2,10 +2,7 @@ import type { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
|
|
|
2
2
|
import type { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
|
|
3
3
|
import type { KeyProxyCallback } from '../types/modules/proxy-callback.types';
|
|
4
4
|
import { JsonPathSqlOptions } from './json-path.utils';
|
|
5
|
-
/**
|
|
6
|
-
* Replaces placeholders of `deferColumnKey` in SQL built by `builder`: the database column through the entity of
|
|
7
|
-
* the alias, quoted, `"usr"."receipts_email"`. An alias still unknown or without an entity keeps the name.
|
|
8
|
-
*/
|
|
5
|
+
/** Replaces placeholders of `deferColumnKey` in SQL built by `builder`, through the aliases it has by then. */
|
|
9
6
|
export declare function resolveDeferredKeys(builder: WhereExpressionBuilder, statement: string): string;
|
|
10
7
|
/**
|
|
11
8
|
* Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
|
|
@@ -57,8 +54,9 @@ export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(buil
|
|
|
57
54
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
58
55
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
59
56
|
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
60
|
-
* metadata (a subquery or a CTE in FROM or a join)
|
|
61
|
-
* TypeORM leaves its keys as written, and
|
|
57
|
+
* metadata (a subquery or a CTE in FROM or a join), or one the query still doesn't have when the SQL
|
|
58
|
+
* is built, is quoted unless `useQuotes` is `false`: TypeORM leaves its keys as written, and
|
|
59
|
+
* PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
62
60
|
* Strings are kept as is.
|
|
63
61
|
*/
|
|
64
62
|
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean, columnsFor?: string): string | string[];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = exports.resolveDeferredKeys = void 0;
|
|
4
4
|
const typeorm_1 = require("typeorm");
|
|
5
|
+
const QueryExpressionMap_1 = require("typeorm/query-builder/QueryExpressionMap");
|
|
5
6
|
const proxy_key_utils_1 = require("./proxy-key.utils");
|
|
6
7
|
const json_path_utils_1 = require("./json-path.utils");
|
|
7
8
|
const sql_utils_1 = require("./sql.utils");
|
|
@@ -52,49 +53,100 @@ function findQueryAlias(queryBuilder, aliasName) {
|
|
|
52
53
|
const DEFERRED_KEY_START = '\uE000';
|
|
53
54
|
const DEFERRED_PROPERTY = '\uE001';
|
|
54
55
|
const DEFERRED_FIELD = '\uE002';
|
|
56
|
+
const DEFERRED_UNQUOTED = '\uE004';
|
|
55
57
|
const DEFERRED_KEY_END = '\uE003';
|
|
56
|
-
const DEFERRED_KEY = /\uE000([^\uE000-\
|
|
58
|
+
const DEFERRED_KEY = /\uE000([^\uE000-\uE004]+)([\uE001\uE002\uE004])([^\uE000-\uE004]+)\uE003/g;
|
|
57
59
|
const TWO_PART_FIELD = /^([A-Za-z_][\w$]*)\.([A-Za-z_][\w$]*)$/;
|
|
58
60
|
/**
|
|
59
|
-
* Placeholder for a column of an alias the query doesn't have yet, e.g. a join added after the call
|
|
60
|
-
*
|
|
61
|
+
* Placeholder for a column of an alias the query doesn't have yet, e.g. a join added after the call: resolved when
|
|
62
|
+
* the alias is added (`resolveAliasKeys`) or, at the latest, when the SQL is built (`resolveDeferredKeys`).
|
|
61
63
|
*/
|
|
62
64
|
function deferColumnKey(aliasName, separator, name) {
|
|
63
65
|
return `${DEFERRED_KEY_START}${aliasName}${separator}${name}${DEFERRED_KEY_END}`;
|
|
64
66
|
}
|
|
65
67
|
/**
|
|
66
|
-
*
|
|
67
|
-
* the alias, quoted, `"usr"."receipts_email"
|
|
68
|
+
* SQL of a deferred key for `alias`, undefined while the query doesn't have it:
|
|
69
|
+
* - a quoted key: the database column through the alias' entity, quoted, `"usr"."receipts_email"`; the name
|
|
70
|
+
* quoted without an entity;
|
|
71
|
+
* - an unquoted one (select, order, group by, distinct without `useQuotes`): `alias.property` on an alias with an
|
|
72
|
+
* entity, which TypeORM maps and reads as a column of the entity; quoted on an alias without one (a subquery, a
|
|
73
|
+
* CTE), as on an alias known at the call, and on an alias the query never has, which only raw SQL adds (a LATERAL
|
|
74
|
+
* subquery in the condition of a raw join): TypeORM can't map it, and PostgreSQL would fold it to lower case.
|
|
68
75
|
*/
|
|
76
|
+
function toDeferredKeySql(alias, aliasName, separator, name, escape) {
|
|
77
|
+
const metadata = (alias === null || alias === void 0 ? void 0 : alias.hasMetadata) ? alias.metadata : undefined;
|
|
78
|
+
if (separator === DEFERRED_UNQUOTED) {
|
|
79
|
+
return metadata ? `${aliasName}.${name}` : `${escape(aliasName)}.${escape(name)}`;
|
|
80
|
+
}
|
|
81
|
+
const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
|
|
82
|
+
const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
|
|
83
|
+
return `${escape(aliasName)}.${escape(columnName)}`;
|
|
84
|
+
}
|
|
85
|
+
/** Replaces placeholders of `deferColumnKey` in SQL built by `builder`, through the aliases it has by then. */
|
|
69
86
|
function resolveDeferredKeys(builder, statement) {
|
|
70
87
|
if (typeof statement !== 'string' || !statement.includes(DEFERRED_KEY_START)) {
|
|
71
88
|
return statement;
|
|
72
89
|
}
|
|
73
90
|
const queryBuilder = builder;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
|
|
77
|
-
const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
|
|
78
|
-
return `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
|
|
79
|
-
});
|
|
91
|
+
const escape = (identifier) => queryBuilder.escape(identifier);
|
|
92
|
+
return statement.replace(DEFERRED_KEY, (_match, aliasName, separator, name) => toDeferredKeySql(findQueryAlias(queryBuilder, aliasName), aliasName, separator, name, escape));
|
|
80
93
|
}
|
|
81
94
|
exports.resolveDeferredKeys = resolveDeferredKeys;
|
|
95
|
+
/**
|
|
96
|
+
* Resolves the placeholders of a new alias in select, order, group by and distinct on, which TypeORM reads before it
|
|
97
|
+
* builds the SQL: a select of `alias.property` is how it knows to fill the property of a joined entity.
|
|
98
|
+
*/
|
|
99
|
+
function resolveAliasKeys(expressionMap, alias) {
|
|
100
|
+
var _a;
|
|
101
|
+
// `connection` (TypeORM 0.3) or `dataSource` (1.x), protected in its types; read only when a key is found
|
|
102
|
+
const { connection, dataSource } = expressionMap;
|
|
103
|
+
const escape = (identifier) => (connection !== null && connection !== void 0 ? connection : dataSource).driver.escape(identifier);
|
|
104
|
+
const hasKey = (statement) => typeof statement === 'string' && statement.includes(DEFERRED_KEY_START);
|
|
105
|
+
const resolve = (statement) => hasKey(statement)
|
|
106
|
+
? statement.replace(DEFERRED_KEY, (match, aliasName, separator, name) => aliasName === alias.name ? toDeferredKeySql(alias, aliasName, separator, name, escape) : match)
|
|
107
|
+
: statement;
|
|
108
|
+
// new objects and arrays: a clone shares them with the query it was cloned from
|
|
109
|
+
if (expressionMap.selects.some((select) => hasKey(select.selection))) {
|
|
110
|
+
expressionMap.selects = expressionMap.selects.map((select) => hasKey(select.selection) ? { ...select, selection: resolve(select.selection) } : select);
|
|
111
|
+
}
|
|
112
|
+
if (expressionMap.groupBys.some(hasKey)) {
|
|
113
|
+
expressionMap.groupBys = expressionMap.groupBys.map(resolve);
|
|
114
|
+
}
|
|
115
|
+
if ((_a = expressionMap.selectDistinctOn) === null || _a === void 0 ? void 0 : _a.some(hasKey)) {
|
|
116
|
+
expressionMap.selectDistinctOn = expressionMap.selectDistinctOn.map(resolve);
|
|
117
|
+
}
|
|
118
|
+
if (Object.keys(expressionMap.orderBys).some(hasKey)) {
|
|
119
|
+
expressionMap.orderBys = Object.fromEntries(Object.entries(expressionMap.orderBys).map(([key, order]) => [resolve(key), order]));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
82
122
|
const DEFERRED_KEYS_INSTALLED = Symbol.for('typeorm-extensions.deferredKeys');
|
|
83
|
-
// TypeORM 0.3.12 maps property names in each part of the query, later versions once in the whole query
|
|
84
123
|
function installDeferredKeyResolution() {
|
|
85
|
-
const
|
|
86
|
-
|
|
124
|
+
const builderPrototype = typeorm_1.QueryBuilder.prototype;
|
|
125
|
+
const mapPrototype = QueryExpressionMap_1.QueryExpressionMap.prototype;
|
|
126
|
+
if (builderPrototype[DEFERRED_KEYS_INSTALLED]) {
|
|
87
127
|
return;
|
|
88
128
|
}
|
|
89
|
-
|
|
129
|
+
builderPrototype[DEFERRED_KEYS_INSTALLED] = true;
|
|
130
|
+
// TypeORM 0.3.12 maps property names in each part of the query, later versions once in the whole query; keys are
|
|
131
|
+
// resolved first, so an unquoted `alias.property` is mapped too
|
|
90
132
|
for (const method of ['replacePropertyNames', 'replacePropertyNamesForTheWholeQuery']) {
|
|
91
|
-
const original =
|
|
133
|
+
const original = builderPrototype[method];
|
|
92
134
|
if (typeof original === 'function') {
|
|
93
|
-
|
|
94
|
-
return
|
|
135
|
+
builderPrototype[method] = function (statement) {
|
|
136
|
+
return original.call(this, resolveDeferredKeys(this, statement));
|
|
95
137
|
};
|
|
96
138
|
}
|
|
97
139
|
}
|
|
140
|
+
const createAlias = mapPrototype.createAlias;
|
|
141
|
+
if (typeof createAlias === 'function') {
|
|
142
|
+
mapPrototype.createAlias = function (...args) {
|
|
143
|
+
const alias = createAlias.apply(this, args);
|
|
144
|
+
if (alias === null || alias === void 0 ? void 0 : alias.name) {
|
|
145
|
+
resolveAliasKeys(this, alias);
|
|
146
|
+
}
|
|
147
|
+
return alias;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
98
150
|
}
|
|
99
151
|
installDeferredKeyResolution();
|
|
100
152
|
/** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
|
|
@@ -341,7 +393,7 @@ function assertTableColumnKey({ metadata, propertyPath }, keys, methodName) {
|
|
|
341
393
|
`"${keys.join('.')}.someColumn"`);
|
|
342
394
|
}
|
|
343
395
|
}
|
|
344
|
-
// an alias the query doesn't know yet
|
|
396
|
+
// an alias the query doesn't know yet is resolved when it is added (`resolveAliasKeys`)
|
|
345
397
|
function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
346
398
|
const alias = aliasName === undefined ? undefined : findQueryAlias(queryBuilder, aliasName);
|
|
347
399
|
return alias !== undefined && !alias.hasMetadata;
|
|
@@ -352,8 +404,9 @@ function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
|
352
404
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
353
405
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
354
406
|
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
355
|
-
* metadata (a subquery or a CTE in FROM or a join)
|
|
356
|
-
* TypeORM leaves its keys as written, and
|
|
407
|
+
* metadata (a subquery or a CTE in FROM or a join), or one the query still doesn't have when the SQL
|
|
408
|
+
* is built, is quoted unless `useQuotes` is `false`: TypeORM leaves its keys as written, and
|
|
409
|
+
* PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
357
410
|
* Strings are kept as is.
|
|
358
411
|
*/
|
|
359
412
|
function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
|
|
@@ -373,6 +426,14 @@ function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
|
|
|
373
426
|
if (useQuotes !== null && useQuotes !== void 0 ? useQuotes : isAliasWithoutMetadata(queryBuilder, resolved.aliasName)) {
|
|
374
427
|
return toQuotedColumnKey(queryBuilder, resolved);
|
|
375
428
|
}
|
|
429
|
+
// an alias added later decides then: TypeORM maps an entity's keys, a subquery's are quoted
|
|
430
|
+
if (useQuotes === undefined &&
|
|
431
|
+
columnsFor !== undefined &&
|
|
432
|
+
resolved.aliasName !== undefined &&
|
|
433
|
+
!resolved.metadata &&
|
|
434
|
+
!findQueryAlias(queryBuilder, resolved.aliasName)) {
|
|
435
|
+
return deferColumnKey(resolved.aliasName, DEFERRED_UNQUOTED, resolved.propertyPath);
|
|
436
|
+
}
|
|
376
437
|
return resolved.aliasName === undefined
|
|
377
438
|
? resolved.propertyPath
|
|
378
439
|
: `${resolved.aliasName}.${resolved.propertyPath}`;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ReservedQualifier } from './reserved-qualifier.utils';
|
|
2
|
+
/** The SQL of a query names a reserved word as a qualifier without quotes; see `enableReservedQualifierGuard`. */
|
|
3
|
+
export declare class ReservedQualifierError extends Error {
|
|
4
|
+
/** The SQL of the query, as the driver gets it. */
|
|
5
|
+
readonly sql: string;
|
|
6
|
+
/** Each reserved word used as a qualifier, in the order of the SQL. */
|
|
7
|
+
readonly qualifiers: ReservedQualifier[];
|
|
8
|
+
constructor(sql: string, qualifiers: ReservedQualifier[]);
|
|
9
|
+
}
|
|
10
|
+
type GuardWordsOption = {
|
|
11
|
+
/**
|
|
12
|
+
* The words to report, instead of the default list of the database (`POSTGRES_RESERVED_KEY_WORDS`,
|
|
13
|
+
* `COCKROACHDB_RESERVED_KEY_WORDS` on CockroachDB). Spread a default list to extend it.
|
|
14
|
+
*/
|
|
15
|
+
words?: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
export type ReservedQualifierGuardOptions = GuardWordsOption & ({
|
|
18
|
+
/** `'throw'` (the default): the query rejects with a `ReservedQualifierError` before it runs. */
|
|
19
|
+
mode?: 'throw';
|
|
20
|
+
} | {
|
|
21
|
+
/** `'warn'`: the query runs, and the error is passed to `onViolation`. */
|
|
22
|
+
mode: 'warn';
|
|
23
|
+
/** Reports the error the query would throw in `'throw'` mode; `console.warn` by default. */
|
|
24
|
+
onViolation?: (violation: ReservedQualifierError) => void;
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Checks the SQL of every query builder on PostgreSQL and CockroachDB for a reserved word used as a qualifier
|
|
28
|
+
* without quotes, such as `order."userId"` or `user.id` (`findBareReservedQualifiers`), before the query runs.
|
|
29
|
+
* TypeORM quotes the aliases it writes, so such a word comes from SQL written by hand, which PostgreSQL rejects.
|
|
30
|
+
*
|
|
31
|
+
* Off by default. A second call replaces the options. Other databases aren't checked.
|
|
32
|
+
*/
|
|
33
|
+
export declare function enableReservedQualifierGuard(options?: ReservedQualifierGuardOptions): void;
|
|
34
|
+
/** Turns the check of `enableReservedQualifierGuard` off. */
|
|
35
|
+
export declare function disableReservedQualifierGuard(): void;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disableReservedQualifierGuard = exports.enableReservedQualifierGuard = exports.ReservedQualifierError = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
const reserved_qualifier_utils_1 = require("./reserved-qualifier.utils");
|
|
6
|
+
const sql_utils_1 = require("./sql.utils");
|
|
7
|
+
const NAME_AFTER_DOT = /^\s*\.\s*("(?:[^"]|"")*"|[A-Za-z_][\w$]*|\*)/;
|
|
8
|
+
/** `"order"."userId"` for `order."userId"` / `order.userId`, `"order".*` for `order.*`. */
|
|
9
|
+
function toQuotedReference(sql, { word, index }) {
|
|
10
|
+
var _a, _b;
|
|
11
|
+
const name = (_b = (_a = NAME_AFTER_DOT.exec(sql.slice(index + word.length))) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 'column';
|
|
12
|
+
const quotedName = name.startsWith('"') || name === '*' ? name : `"${name}"`;
|
|
13
|
+
return `"${word}".${quotedName}`;
|
|
14
|
+
}
|
|
15
|
+
/** The SQL of a query names a reserved word as a qualifier without quotes; see `enableReservedQualifierGuard`. */
|
|
16
|
+
class ReservedQualifierError extends Error {
|
|
17
|
+
constructor(sql, qualifiers) {
|
|
18
|
+
const [first] = qualifiers;
|
|
19
|
+
const found = qualifiers.map(({ word, snippet }) => `"${word}" in ${snippet}`).join('; ');
|
|
20
|
+
super(`Reserved word used as a qualifier without quotes: ${found}. ` +
|
|
21
|
+
`PostgreSQL reads it as the key word, not as an alias, and rejects the query. ` +
|
|
22
|
+
(first
|
|
23
|
+
? `Quote both halves, ${toQuotedReference(sql, first)}, or build the reference with tableHelper or the typed methods`
|
|
24
|
+
: ''));
|
|
25
|
+
this.name = 'ReservedQualifierError';
|
|
26
|
+
this.sql = sql;
|
|
27
|
+
this.qualifiers = qualifiers;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.ReservedQualifierError = ReservedQualifierError;
|
|
31
|
+
let guard;
|
|
32
|
+
let installed = false;
|
|
33
|
+
let lastScan;
|
|
34
|
+
const reportViolation = (violation) => console.warn(violation);
|
|
35
|
+
function checkQuery(queryBuilder, sql, state) {
|
|
36
|
+
var _a;
|
|
37
|
+
const driver = (0, sql_utils_1.getDriverType)(queryBuilder);
|
|
38
|
+
if (typeof sql !== 'string' || !(0, sql_utils_1.isPostgresLikeDriver)(driver)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const words = (_a = state.words) !== null && _a !== void 0 ? _a : (0, reserved_qualifier_utils_1.toReservedWordSet)(driver === 'cockroachdb' ? reserved_qualifier_utils_1.COCKROACHDB_RESERVED_KEY_WORDS : reserved_qualifier_utils_1.POSTGRES_RESERVED_KEY_WORDS);
|
|
42
|
+
// the same SQL again, as a query run in a loop: one scan
|
|
43
|
+
const qualifiers = lastScan && lastScan.sql === sql && lastScan.words === words
|
|
44
|
+
? lastScan.qualifiers
|
|
45
|
+
: (0, reserved_qualifier_utils_1.scanReservedQualifiers)(sql, words);
|
|
46
|
+
lastScan = { sql, words, qualifiers };
|
|
47
|
+
if (qualifiers.length === 0) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const violation = new ReservedQualifierError(sql, qualifiers);
|
|
51
|
+
if (state.mode === 'throw') {
|
|
52
|
+
throw violation;
|
|
53
|
+
}
|
|
54
|
+
state.onViolation(violation);
|
|
55
|
+
}
|
|
56
|
+
// every query of every builder gets its SQL here before it runs: getMany, getRawMany, getOne, getCount, stream,
|
|
57
|
+
// execute; getSql() too. getQuery() isn't checked: a subquery's SQL is checked as part of the query it is in
|
|
58
|
+
function installGuard() {
|
|
59
|
+
if (installed) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
installed = true;
|
|
63
|
+
const getQueryAndParameters = typeorm_1.QueryBuilder.prototype.getQueryAndParameters;
|
|
64
|
+
typeorm_1.QueryBuilder.prototype.getQueryAndParameters = function () {
|
|
65
|
+
const queryAndParameters = getQueryAndParameters.call(this);
|
|
66
|
+
if (guard) {
|
|
67
|
+
checkQuery(this, queryAndParameters[0], guard);
|
|
68
|
+
}
|
|
69
|
+
return queryAndParameters;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Checks the SQL of every query builder on PostgreSQL and CockroachDB for a reserved word used as a qualifier
|
|
74
|
+
* without quotes, such as `order."userId"` or `user.id` (`findBareReservedQualifiers`), before the query runs.
|
|
75
|
+
* TypeORM quotes the aliases it writes, so such a word comes from SQL written by hand, which PostgreSQL rejects.
|
|
76
|
+
*
|
|
77
|
+
* Off by default. A second call replaces the options. Other databases aren't checked.
|
|
78
|
+
*/
|
|
79
|
+
function enableReservedQualifierGuard(options = {}) {
|
|
80
|
+
var _a;
|
|
81
|
+
const mode = (_a = options.mode) !== null && _a !== void 0 ? _a : 'throw';
|
|
82
|
+
if (mode !== 'throw' && mode !== 'warn') {
|
|
83
|
+
throw new TypeError(`Invalid mode ${JSON.stringify(mode)} of the reserved qualifier guard: 'throw' or 'warn'`);
|
|
84
|
+
}
|
|
85
|
+
guard = {
|
|
86
|
+
mode,
|
|
87
|
+
words: options.words === undefined ? undefined : (0, reserved_qualifier_utils_1.toReservedWordSet)(options.words),
|
|
88
|
+
onViolation: (options.mode === 'warn' && options.onViolation) || reportViolation,
|
|
89
|
+
};
|
|
90
|
+
installGuard();
|
|
91
|
+
}
|
|
92
|
+
exports.enableReservedQualifierGuard = enableReservedQualifierGuard;
|
|
93
|
+
/** Turns the check of `enableReservedQualifierGuard` off. */
|
|
94
|
+
function disableReservedQualifierGuard() {
|
|
95
|
+
guard = undefined;
|
|
96
|
+
lastScan = undefined;
|
|
97
|
+
}
|
|
98
|
+
exports.disableReservedQualifierGuard = disableReservedQualifierGuard;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** A reserved word that qualifies a name without quotes, as in `order."userId"`. */
|
|
2
|
+
export interface ReservedQualifier {
|
|
3
|
+
/** The word as the SQL has it: `order`, `ORDER`. */
|
|
4
|
+
word: string;
|
|
5
|
+
/** Offset of the word in the SQL. */
|
|
6
|
+
index: number;
|
|
7
|
+
/** The SQL around the word, on one line. */
|
|
8
|
+
snippet: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The reserved key words of PostgreSQL, "reserved" in Appendix C of its documentation (`SYSTEM_USER` since
|
|
12
|
+
* PostgreSQL 16). None can be a table, an alias or a schema without quotes, so none is followed by a `.` in valid
|
|
13
|
+
* SQL. The words "reserved (can be function or type)" (`LEFT`, `JOIN`, `BINARY`, …) aren't on the list: one may
|
|
14
|
+
* name the schema of a type, as in `CAST(x AS binary.my_type)`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const POSTGRES_RESERVED_KEY_WORDS: readonly string[];
|
|
17
|
+
/** The reserved key words of CockroachDB: PostgreSQL's without `SYSTEM_USER`, with `CONCURRENTLY` and `CURRENT_SCHEMA`. */
|
|
18
|
+
export declare const COCKROACHDB_RESERVED_KEY_WORDS: readonly string[];
|
|
19
|
+
/** The words in lower case; cached for a frozen list, such as the defaults, which can't change afterwards. */
|
|
20
|
+
export declare function toReservedWordSet(words: readonly string[]): ReadonlySet<string>;
|
|
21
|
+
/** `findBareReservedQualifiers` with the words as a set of lower-case words (`toReservedWordSet`). */
|
|
22
|
+
export declare function scanReservedQualifiers(sql: string, words: ReadonlySet<string>): ReservedQualifier[];
|
|
23
|
+
/**
|
|
24
|
+
* Reserved words that qualify a name without quotes, as in `order."userId"` or `user.id`: PostgreSQL reads such a
|
|
25
|
+
* word as the key word, not as an alias, and rejects the statement (`syntax error at or near "order"`).
|
|
26
|
+
*
|
|
27
|
+
* Words in string literals (`E'…'` and dollar-quoted ones too), quoted identifiers, comments and parameters are
|
|
28
|
+
* skipped, and so are words after a dot (`public.order`) and a number after one (`THEN .5`). Case-insensitive;
|
|
29
|
+
* whitespace may come before the dot (`group . name`).
|
|
30
|
+
*
|
|
31
|
+
* @param words the words to look for, `POSTGRES_RESERVED_KEY_WORDS` by default
|
|
32
|
+
*/
|
|
33
|
+
export declare function findBareReservedQualifiers(sql: string, words?: readonly string[]): ReservedQualifier[];
|