typeorm-extensions 2.2.0-rc.4 → 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-\
|
|
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; 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
|
-
|
|
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
|
-
});
|
|
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
|
|
86
|
-
|
|
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
|
-
|
|
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 =
|
|
132
|
+
const original = builderPrototype[method];
|
|
92
133
|
if (typeof original === 'function') {
|
|
93
|
-
|
|
94
|
-
return
|
|
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
|
|
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}`;
|
|
@@ -13,8 +13,11 @@ function getValuePlaceholder(value, paramName) {
|
|
|
13
13
|
return `:${paramName}`;
|
|
14
14
|
}
|
|
15
15
|
exports.getValuePlaceholder = getValuePlaceholder;
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A condition that ends with an operator, `=`, `>=`, `LIKE`, `IN`, `@>`: it needs a value after it. A word after
|
|
18
|
+
* `:` or `.` is a parameter or a column (`ILIKE :like`, `= t.in`), not an operator.
|
|
19
|
+
*/
|
|
20
|
+
const OPERATOR_AT_END = /(?:[=<>!~@&|?]|(?<![\w$:.])(?:LIKE|ILIKE|IN|SIMILAR TO|DISTINCT FROM|BETWEEN|ANY|ALL))\s*$/i;
|
|
18
21
|
const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
19
22
|
/**
|
|
20
23
|
* Objects that are parameter values themselves, not `{ name: value }` params objects:
|