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.
Files changed (66) hide show
  1. package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
  2. package/dist/extensions/order.extension.d.ts +2 -2
  3. package/dist/extensions/pagination.extension.d.ts +1 -1
  4. package/dist/extensions/period-filter.extension.d.ts +4 -0
  5. package/dist/extensions/period-filter.extension.js +2 -0
  6. package/dist/extensions/typed/index.d.ts +1 -0
  7. package/dist/extensions/typed/index.js +1 -0
  8. package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +2 -2
  9. package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +2 -2
  10. package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +4 -4
  11. package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.d.ts +19 -0
  12. package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.js +22 -0
  13. package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +4 -4
  14. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +2 -2
  15. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +2 -2
  16. package/dist/extensions/typed/typed-root.extension.d.ts +26 -4
  17. package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
  18. package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +5 -1
  19. package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +2 -0
  20. package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +13 -9
  21. package/dist/extensions/typed/typed-where/typed-where-array.extension.js +2 -0
  22. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +10 -6
  23. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +2 -0
  24. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +10 -6
  25. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +2 -0
  26. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +10 -6
  27. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +2 -0
  28. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +10 -6
  29. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +2 -0
  30. package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +10 -6
  31. package/dist/extensions/typed/typed-where/typed-where-json.extension.js +2 -0
  32. package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +10 -6
  33. package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +2 -0
  34. package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +8 -4
  35. package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +2 -0
  36. package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +7 -3
  37. package/dist/extensions/typed/typed-where/typed-where.extension.js +2 -0
  38. package/dist/extensions/where-array.extension.d.ts +4 -0
  39. package/dist/extensions/where-array.extension.js +2 -0
  40. package/dist/extensions/where-is-any.extension.d.ts +41 -6
  41. package/dist/extensions/where-is-any.extension.js +2 -0
  42. package/dist/extensions/where-is-distinct-from.extension.d.ts +4 -0
  43. package/dist/extensions/where-is-distinct-from.extension.js +2 -0
  44. package/dist/extensions/where-is-in.extension.d.ts +38 -6
  45. package/dist/extensions/where-is-in.extension.js +2 -0
  46. package/dist/extensions/where-json.extension.d.ts +4 -0
  47. package/dist/extensions/where-json.extension.js +2 -0
  48. package/dist/extensions/where-relation-exists.extension.d.ts +6 -6
  49. package/dist/helpers.d.ts +4 -0
  50. package/dist/helpers.js +11 -3
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +9 -1
  53. package/dist/types/extensions/index.d.ts +1 -0
  54. package/dist/types/extensions/index.js +1 -0
  55. package/dist/types/extensions/typed-join-and-map.types.d.ts +6 -0
  56. package/dist/types/extensions/typed-join-and-map.types.js +2 -0
  57. package/dist/types/modules/typed-root.types.d.ts +11 -9
  58. package/dist/utils/query-builder.utils.d.ts +4 -6
  59. package/dist/utils/query-builder.utils.js +82 -21
  60. package/dist/utils/reserved-qualifier-guard.utils.d.ts +36 -0
  61. package/dist/utils/reserved-qualifier-guard.utils.js +98 -0
  62. package/dist/utils/reserved-qualifier.utils.d.ts +33 -0
  63. package/dist/utils/reserved-qualifier.utils.js +252 -0
  64. package/dist/utils/table.utils.d.ts +27 -11
  65. package/dist/utils/table.utils.js +49 -12
  66. 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; importing the package root
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; } });
@@ -10,4 +10,5 @@ export * from './search.types';
10
10
  export * from './typed-having.types';
11
11
  export * from './union.types';
12
12
  export * from './join-lateral.types';
13
+ export * from './typed-join-and-map.types';
13
14
  export * from './query-returning.types';
@@ -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>);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,17 +1,19 @@
1
1
  import type { ObjectLiteral } from 'typeorm';
2
- declare const typedRoot: unique symbol;
3
2
  declare class TypedRootMark<Root> {
4
- [typedRoot]?(): Root;
3
+ __typedRoot?(): Root;
5
4
  }
6
5
  /**
7
- * The entity type of a builder whose typed callbacks take another root, set with `typed<Root>()`: the entity
8
- * with an optional symbol method that only types carry. Entities loaded with it are plain entities.
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<Entity, Root extends ObjectLiteral> = Entity & TypedRootMark<Root>;
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>()`, or else its entity. The key check
13
- * comes first: without `strictNullChecks` an entity with an index signature (`ObjectLiteral`) matches the
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> = typeof typedRoot extends keyof Entity ? Entity extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : Entity : Entity : 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) is quoted unless `useQuotes` is `false`:
61
- * TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
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-\uE003]+)([\uE001\uE002])([^\uE000-\uE003]+)\uE003/g;
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. TypeORM maps
60
- * property names when it builds the SQL, and so does `resolveDeferredKeys` with these, once the alias is known.
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
- * Replaces placeholders of `deferColumnKey` in SQL built by `builder`: the database column through the entity of
67
- * the alias, quoted, `"usr"."receipts_email"`. An alias still unknown or without an entity keeps the name.
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
- return statement.replace(DEFERRED_KEY, (_match, aliasName, separator, name) => {
75
- const metadata = getAliasMetadata(queryBuilder, aliasName);
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 prototype = typeorm_1.QueryBuilder.prototype;
86
- if (prototype[DEFERRED_KEYS_INSTALLED]) {
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
- prototype[DEFERRED_KEYS_INSTALLED] = true;
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 = prototype[method];
133
+ const original = builderPrototype[method];
92
134
  if (typeof original === 'function') {
93
- prototype[method] = function (statement) {
94
- return resolveDeferredKeys(this, original.call(this, statement));
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 may still get metadata (a join added later), so it stays unquoted
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) is quoted unless `useQuotes` is `false`:
356
- * TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
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[];