typeorm-extensions 2.2.0-rc.5 → 2.2.0-rc.6

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.
@@ -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
@@ -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,99 @@ 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; as written on an unknown alias.
68
74
  */
75
+ function toDeferredKeySql(alias, aliasName, separator, name, escape) {
76
+ const metadata = (alias === null || alias === void 0 ? void 0 : alias.hasMetadata) ? alias.metadata : undefined;
77
+ if (separator === DEFERRED_UNQUOTED) {
78
+ return metadata || !alias ? `${aliasName}.${name}` : `${escape(aliasName)}.${escape(name)}`;
79
+ }
80
+ const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
81
+ const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
82
+ return `${escape(aliasName)}.${escape(columnName)}`;
83
+ }
84
+ /** Replaces placeholders of `deferColumnKey` in SQL built by `builder`, through the aliases it has by then. */
69
85
  function resolveDeferredKeys(builder, statement) {
70
86
  if (typeof statement !== 'string' || !statement.includes(DEFERRED_KEY_START)) {
71
87
  return statement;
72
88
  }
73
89
  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
- });
90
+ const escape = (identifier) => queryBuilder.escape(identifier);
91
+ return statement.replace(DEFERRED_KEY, (_match, aliasName, separator, name) => toDeferredKeySql(findQueryAlias(queryBuilder, aliasName), aliasName, separator, name, escape));
80
92
  }
81
93
  exports.resolveDeferredKeys = resolveDeferredKeys;
94
+ /**
95
+ * Resolves the placeholders of a new alias in select, order, group by and distinct on, which TypeORM reads before it
96
+ * builds the SQL: a select of `alias.property` is how it knows to fill the property of a joined entity.
97
+ */
98
+ function resolveAliasKeys(expressionMap, alias) {
99
+ var _a;
100
+ // `connection` (TypeORM 0.3) or `dataSource` (1.x), protected in its types; read only when a key is found
101
+ const { connection, dataSource } = expressionMap;
102
+ const escape = (identifier) => (connection !== null && connection !== void 0 ? connection : dataSource).driver.escape(identifier);
103
+ const hasKey = (statement) => typeof statement === 'string' && statement.includes(DEFERRED_KEY_START);
104
+ const resolve = (statement) => hasKey(statement)
105
+ ? statement.replace(DEFERRED_KEY, (match, aliasName, separator, name) => aliasName === alias.name ? toDeferredKeySql(alias, aliasName, separator, name, escape) : match)
106
+ : statement;
107
+ // new objects and arrays: a clone shares them with the query it was cloned from
108
+ if (expressionMap.selects.some((select) => hasKey(select.selection))) {
109
+ expressionMap.selects = expressionMap.selects.map((select) => hasKey(select.selection) ? { ...select, selection: resolve(select.selection) } : select);
110
+ }
111
+ if (expressionMap.groupBys.some(hasKey)) {
112
+ expressionMap.groupBys = expressionMap.groupBys.map(resolve);
113
+ }
114
+ if ((_a = expressionMap.selectDistinctOn) === null || _a === void 0 ? void 0 : _a.some(hasKey)) {
115
+ expressionMap.selectDistinctOn = expressionMap.selectDistinctOn.map(resolve);
116
+ }
117
+ if (Object.keys(expressionMap.orderBys).some(hasKey)) {
118
+ expressionMap.orderBys = Object.fromEntries(Object.entries(expressionMap.orderBys).map(([key, order]) => [resolve(key), order]));
119
+ }
120
+ }
82
121
  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
122
  function installDeferredKeyResolution() {
85
- const prototype = typeorm_1.QueryBuilder.prototype;
86
- if (prototype[DEFERRED_KEYS_INSTALLED]) {
123
+ const builderPrototype = typeorm_1.QueryBuilder.prototype;
124
+ const mapPrototype = QueryExpressionMap_1.QueryExpressionMap.prototype;
125
+ if (builderPrototype[DEFERRED_KEYS_INSTALLED]) {
87
126
  return;
88
127
  }
89
- prototype[DEFERRED_KEYS_INSTALLED] = true;
128
+ builderPrototype[DEFERRED_KEYS_INSTALLED] = true;
129
+ // TypeORM 0.3.12 maps property names in each part of the query, later versions once in the whole query; keys are
130
+ // resolved first, so an unquoted `alias.property` is mapped too
90
131
  for (const method of ['replacePropertyNames', 'replacePropertyNamesForTheWholeQuery']) {
91
- const original = prototype[method];
132
+ const original = builderPrototype[method];
92
133
  if (typeof original === 'function') {
93
- prototype[method] = function (statement) {
94
- return resolveDeferredKeys(this, original.call(this, statement));
134
+ builderPrototype[method] = function (statement) {
135
+ return original.call(this, resolveDeferredKeys(this, statement));
95
136
  };
96
137
  }
97
138
  }
139
+ const createAlias = mapPrototype.createAlias;
140
+ if (typeof createAlias === 'function') {
141
+ mapPrototype.createAlias = function (...args) {
142
+ const alias = createAlias.apply(this, args);
143
+ if (alias === null || alias === void 0 ? void 0 : alias.name) {
144
+ resolveAliasKeys(this, alias);
145
+ }
146
+ return alias;
147
+ };
148
+ }
98
149
  }
99
150
  installDeferredKeyResolution();
100
151
  /** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
@@ -341,7 +392,7 @@ function assertTableColumnKey({ metadata, propertyPath }, keys, methodName) {
341
392
  `"${keys.join('.')}.someColumn"`);
342
393
  }
343
394
  }
344
- // an alias the query doesn't know yet may still get metadata (a join added later), so it stays unquoted
395
+ // an alias the query doesn't know yet is resolved when it is added (`resolveAliasKeys`)
345
396
  function isAliasWithoutMetadata(queryBuilder, aliasName) {
346
397
  const alias = aliasName === undefined ? undefined : findQueryAlias(queryBuilder, aliasName);
347
398
  return alias !== undefined && !alias.hasMetadata;
@@ -373,6 +424,14 @@ function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
373
424
  if (useQuotes !== null && useQuotes !== void 0 ? useQuotes : isAliasWithoutMetadata(queryBuilder, resolved.aliasName)) {
374
425
  return toQuotedColumnKey(queryBuilder, resolved);
375
426
  }
427
+ // an alias added later decides then: TypeORM maps an entity's keys, a subquery's are quoted
428
+ if (useQuotes === undefined &&
429
+ columnsFor !== undefined &&
430
+ resolved.aliasName !== undefined &&
431
+ !resolved.metadata &&
432
+ !findQueryAlias(queryBuilder, resolved.aliasName)) {
433
+ return deferColumnKey(resolved.aliasName, DEFERRED_UNQUOTED, resolved.propertyPath);
434
+ }
376
435
  return resolved.aliasName === undefined
377
436
  ? resolved.propertyPath
378
437
  : `${resolved.aliasName}.${resolved.propertyPath}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typeorm-extensions",
3
- "version": "2.2.0-rc.5",
3
+ "version": "2.2.0-rc.6",
4
4
  "description": "typeorm commonly used extensions",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",