typeorm-extensions 1.3.5 → 2.1.0
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/README.md +12 -10
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +2 -2
- package/dist/extensions/cursor-pagination.extension.d.ts +18 -0
- package/dist/extensions/cursor-pagination.extension.js +108 -0
- package/dist/extensions/index.d.ts +7 -0
- package/dist/extensions/index.js +7 -0
- package/dist/extensions/order-by-array-position.extension.d.ts +24 -0
- package/dist/extensions/order-by-array-position.extension.js +18 -0
- package/dist/extensions/order.extension.d.ts +3 -2
- package/dist/extensions/order.extension.js +16 -13
- package/dist/extensions/period-filter.extension.d.ts +29 -0
- package/dist/extensions/period-filter.extension.js +23 -0
- package/dist/extensions/typed/index.d.ts +10 -0
- package/dist/extensions/typed/index.js +10 -0
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +2 -2
- package/dist/extensions/typed/typed-joins/typed-joins.extension.js +2 -1
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +20 -0
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +21 -0
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +4 -3
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +7 -6
- package/dist/extensions/typed/typed-returning/typed-returning.extension.d.ts +27 -0
- package/dist/extensions/typed/typed-returning/typed-returning.extension.js +38 -0
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
- package/dist/extensions/typed/typed-select/typed-select.extension.js +11 -8
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +31 -0
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +25 -0
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +68 -0
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +41 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +62 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +84 -0
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +53 -0
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +38 -0
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +53 -0
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +38 -0
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +36 -119
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +14 -29
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +53 -0
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +39 -0
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +59 -0
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +86 -0
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +54 -0
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +73 -0
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +30 -74
- package/dist/extensions/typed/typed-where/typed-where.extension.js +13 -21
- package/dist/extensions/where-array.extension.d.ts +70 -0
- package/dist/extensions/where-array.extension.js +47 -0
- package/dist/extensions/where-is-any.extension.d.ts +142 -0
- package/dist/extensions/where-is-any.extension.js +44 -0
- package/dist/extensions/where-is-distinct-from.extension.d.ts +56 -0
- package/dist/extensions/where-is-distinct-from.extension.js +55 -0
- package/dist/extensions/where-json.extension.d.ts +55 -0
- package/dist/extensions/where-json.extension.js +64 -0
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +23 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/types/extensions/cursor-pagination.types.d.ts +16 -0
- package/dist/types/extensions/cursor-pagination.types.js +2 -0
- package/dist/types/extensions/get-many-with-totals.types.d.ts +3 -1
- package/dist/types/extensions/index.d.ts +4 -0
- package/dist/types/extensions/index.js +4 -0
- package/dist/types/extensions/order.types.d.ts +1 -1
- package/dist/types/extensions/period.types.d.ts +15 -0
- package/dist/types/extensions/period.types.js +2 -0
- package/dist/types/extensions/search.types.d.ts +68 -0
- package/dist/types/extensions/search.types.js +2 -0
- package/dist/types/extensions/typed-order-by.types.d.ts +3 -0
- package/dist/types/extensions/typed-order-by.types.js +2 -0
- package/dist/types/extensions/typed-where.types.d.ts +4 -4
- package/dist/types/extensions/where.types.d.ts +1 -0
- package/dist/types/interfaces/config.interface.d.ts +17 -0
- package/dist/types/interfaces/config.interface.js +2 -0
- package/dist/types/interfaces/cursor-pagination.interface.d.ts +16 -0
- package/dist/types/interfaces/cursor-pagination.interface.js +2 -0
- package/dist/types/interfaces/index.d.ts +3 -0
- package/dist/types/interfaces/index.js +3 -0
- package/dist/types/interfaces/period.interface.d.ts +12 -0
- package/dist/types/interfaces/period.interface.js +2 -0
- package/dist/types/modules/proxy-callback.types.d.ts +50 -3
- package/dist/types/modules/typed-params.types.d.ts +45 -0
- package/dist/types/modules/typed-params.types.js +2 -0
- package/dist/types/util-types/index.d.ts +3 -1
- package/dist/utils/cursor.utils.d.ts +31 -0
- package/dist/utils/cursor.utils.js +130 -0
- package/dist/utils/json-path.utils.d.ts +22 -0
- package/dist/utils/json-path.utils.js +57 -0
- package/dist/utils/order.utils.d.ts +15 -0
- package/dist/utils/order.utils.js +45 -0
- package/dist/utils/period.utils.d.ts +9 -0
- package/dist/utils/period.utils.js +82 -0
- package/dist/utils/proxy-key.utils.d.ts +19 -3
- package/dist/utils/proxy-key.utils.js +40 -15
- package/dist/utils/query-builder.utils.d.ts +48 -0
- package/dist/utils/query-builder.utils.js +257 -0
- package/dist/utils/search.utils.d.ts +27 -0
- package/dist/utils/search.utils.js +83 -0
- package/dist/utils/sql.utils.d.ts +29 -1
- package/dist/utils/sql.utils.js +91 -9
- package/dist/utils/table.utils.d.ts +86 -0
- package/dist/utils/table.utils.js +124 -0
- package/dist/utils/where.utils.d.ts +18 -1
- package/dist/utils/where.utils.js +62 -9
- package/helpers/package.json +7 -0
- package/package.json +5 -4
- package/dist/utils/identifier.utils.d.ts +0 -15
- package/dist/utils/identifier.utils.js +0 -45
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.getTypedColumnKey = exports.toFieldSql = exports.getColumnDatabaseName = void 0;
|
|
4
|
+
const proxy_key_utils_1 = require("./proxy-key.utils");
|
|
5
|
+
const json_path_utils_1 = require("./json-path.utils");
|
|
6
|
+
const sql_utils_1 = require("./sql.utils");
|
|
7
|
+
/**
|
|
8
|
+
* Whether single-level typed keys are qualified with the main alias (`user.firstName`).
|
|
9
|
+
*
|
|
10
|
+
* UPDATE / DELETE builders turn alias prefixing off (`aliasNamePrefixingEnabled === false`)
|
|
11
|
+
* and render `UPDATE "users" … WHERE …` without an alias, where `"UserEntity"."firstName"`
|
|
12
|
+
* is invalid: their keys are just the column, `"firstName"`.
|
|
13
|
+
*/
|
|
14
|
+
function isAliasPrefixingEnabled(queryBuilder) {
|
|
15
|
+
var _a;
|
|
16
|
+
return ((_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.aliasNamePrefixingEnabled) !== false;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Main alias of the query for the single-level key `key`. A query created without an entity
|
|
20
|
+
* (`dataSource.createQueryBuilder()`, `qb.subQuery()`) has none until `from()`: throw a
|
|
21
|
+
* `TypeError` that says how to fix the call, instead of TypeORM's "Main alias is not set".
|
|
22
|
+
*/
|
|
23
|
+
function requireMainAliasName(queryBuilder, key) {
|
|
24
|
+
var _a;
|
|
25
|
+
const mainAlias = (_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.mainAlias;
|
|
26
|
+
if (!mainAlias) {
|
|
27
|
+
throw new TypeError(`Typed key "${key}" has no alias and the query has no main alias yet. ` +
|
|
28
|
+
`Call from() before this method, or address the column by its alias with an alias-keyed type: ` +
|
|
29
|
+
`type Q = { someAlias: SomeEntity }; (q) => q.someAlias.${key}`);
|
|
30
|
+
}
|
|
31
|
+
return mainAlias.name;
|
|
32
|
+
}
|
|
33
|
+
/** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
|
|
34
|
+
function getAliasMetadata(queryBuilder, aliasName) {
|
|
35
|
+
const expressionMap = queryBuilder.expressionMap;
|
|
36
|
+
const alias = aliasName === undefined
|
|
37
|
+
? expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.mainAlias
|
|
38
|
+
: expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.aliases.find((queryAlias) => queryAlias.name === aliasName);
|
|
39
|
+
return (alias === null || alias === void 0 ? void 0 : alias.hasMetadata) ? alias.metadata : undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
|
|
43
|
+
* a query alias (the main alias when `aliasName` is undefined). `undefined` when the alias has no
|
|
44
|
+
* entity or the property isn't a column.
|
|
45
|
+
*/
|
|
46
|
+
function getColumnDatabaseName(builder, aliasName, propertyPath) {
|
|
47
|
+
var _a;
|
|
48
|
+
const metadata = getAliasMetadata(builder, aliasName);
|
|
49
|
+
return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.findColumnWithPropertyPath(propertyPath)) === null || _a === void 0 ? void 0 : _a.databaseName;
|
|
50
|
+
}
|
|
51
|
+
exports.getColumnDatabaseName = getColumnDatabaseName;
|
|
52
|
+
/** A column of the entity by its database name (`first-name`) or its property path (`firstName`, `address.city`). */
|
|
53
|
+
function findColumn(metadata, name) {
|
|
54
|
+
var _a;
|
|
55
|
+
return ((_a = metadata.columns.find((column) => column.databaseName === name)) !== null && _a !== void 0 ? _a : metadata.findColumnWithPropertyPath(name));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* SQL for a field passed as a string to the untyped methods (`whereIsIn('user.first-name', …)`).
|
|
59
|
+
*
|
|
60
|
+
* A string can't tell a column from an expression: `user.first-name` may be the column `first-name`
|
|
61
|
+
* or `user.first` minus `name`. The query builder knows its aliases and their entities, though:
|
|
62
|
+
* - `alias.column`, where `alias` is an alias of the query and `column` one of its entity's columns
|
|
63
|
+
* (by database name or property path): the column, quoted by the driver, `"user"."first-name"`.
|
|
64
|
+
* Property paths are mapped to their columns (`user.createdAt` → `"user"."created_at"`).
|
|
65
|
+
* UPDATE / DELETE give the bare column, as they have no alias.
|
|
66
|
+
* - `column`, a database column name of the main entity: the quoted column, `"first-name"`. Property
|
|
67
|
+
* names aren't mapped without an alias, as `status` may also be a column of a joined table.
|
|
68
|
+
* - anything else (unknown aliases, subqueries, expressions such as `LOWER(u.email)` or
|
|
69
|
+
* `u.price - u.discount`): quoted only when it is a path of plain identifiers, as
|
|
70
|
+
* `stringToSQLIdentifier` does, in the driver's quotes.
|
|
71
|
+
*/
|
|
72
|
+
function toFieldSql(builder, field) {
|
|
73
|
+
var _a;
|
|
74
|
+
const queryBuilder = builder;
|
|
75
|
+
const dot = field.indexOf('.');
|
|
76
|
+
if (dot > 0) {
|
|
77
|
+
const aliasName = field.slice(0, dot);
|
|
78
|
+
const metadata = ((_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.aliases.some((alias) => alias.name === aliasName))
|
|
79
|
+
? getAliasMetadata(queryBuilder, aliasName)
|
|
80
|
+
: undefined;
|
|
81
|
+
const column = metadata && findColumn(metadata, field.slice(dot + 1));
|
|
82
|
+
if (column) {
|
|
83
|
+
const columnSql = queryBuilder.escape(column.databaseName);
|
|
84
|
+
return isAliasPrefixingEnabled(queryBuilder) ? `${queryBuilder.escape(aliasName)}.${columnSql}` : columnSql;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// only by database name: an unqualified property name may mean another table's column
|
|
89
|
+
const metadata = getAliasMetadata(queryBuilder, undefined);
|
|
90
|
+
const column = metadata === null || metadata === void 0 ? void 0 : metadata.columns.find((candidate) => candidate.databaseName === field);
|
|
91
|
+
if (column) {
|
|
92
|
+
return queryBuilder.escape(column.databaseName);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return (0, sql_utils_1.stringToSQLIdentifier)(field, (0, sql_utils_1.getDriverType)(builder));
|
|
96
|
+
}
|
|
97
|
+
exports.toFieldSql = toFieldSql;
|
|
98
|
+
/**
|
|
99
|
+
* The column that `keys` name in an entity: the longest prefix of the keys that is a column's property path
|
|
100
|
+
* (`address.city` for an embedded entity). The keys after it are a path inside the column, allowed only in a
|
|
101
|
+
* JSON column (`details` + `['meta', 'endTs']`). `undefined` when no prefix is a column, or a column that isn't
|
|
102
|
+
* JSON is followed by more keys (a relation's join column, `user` in `user.email`).
|
|
103
|
+
*/
|
|
104
|
+
function findColumnPath(metadata, keys) {
|
|
105
|
+
for (let length = keys.length; metadata && length >= 1; length--) {
|
|
106
|
+
const propertyPath = keys.slice(0, length).join('.');
|
|
107
|
+
const column = metadata.findColumnWithPropertyPath(propertyPath);
|
|
108
|
+
if (!column) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (length === keys.length) {
|
|
112
|
+
return { propertyPath, column };
|
|
113
|
+
}
|
|
114
|
+
return (0, json_path_utils_1.isJsonColumn)(column) ? { propertyPath, column, jsonPath: keys.slice(length) } : undefined;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Splits typed keys into the alias and the property path they refer to:
|
|
120
|
+
* - `t => t.status` → the query's main alias + `status` (no alias on UPDATE / DELETE);
|
|
121
|
+
* - `j => j.t.status`, `u => u.images.url` → the first key, when it is an alias of the query;
|
|
122
|
+
* - `p => p.address.city` → when the first key isn't an alias but the main entity has a column
|
|
123
|
+
* with that path (an embedded entity), the main alias + `address.city`;
|
|
124
|
+
* - `e => e.details.meta.endTs`, `j => j.e.details.outcome` → a JSON column of the alias' entity
|
|
125
|
+
* followed by the keys inside it (`jsonPath`), at any depth;
|
|
126
|
+
* - anything else → the first key as the alias (an alias the query builder doesn't know yet,
|
|
127
|
+
* e.g. `q => q.base.chatRoomId` before `.from(…, 'base')`).
|
|
128
|
+
*
|
|
129
|
+
* Only single-level keys need the main alias, so `alias.column` keys work before `from()`.
|
|
130
|
+
* Their column names can be mapped only through aliases already known to the query, though:
|
|
131
|
+
* before `from()` the property name is used as the column name.
|
|
132
|
+
*/
|
|
133
|
+
function resolveTypedKey(queryBuilder, keys) {
|
|
134
|
+
const resolved = resolveTypedKeyPath(queryBuilder, keys);
|
|
135
|
+
// deeper than `alias.column` only inside a JSON column: a nested relation can't be mapped to an alias
|
|
136
|
+
if (!resolved.jsonPath && keys.length > proxy_key_utils_1.MAX_KEY_DEPTH) {
|
|
137
|
+
throw (0, proxy_key_utils_1.createKeyTooDeepError)(keys);
|
|
138
|
+
}
|
|
139
|
+
return resolved;
|
|
140
|
+
}
|
|
141
|
+
function resolveTypedKeyPath(queryBuilder, keys) {
|
|
142
|
+
const prefixing = isAliasPrefixingEnabled(queryBuilder);
|
|
143
|
+
if (keys.length === 1) {
|
|
144
|
+
const aliasName = prefixing ? requireMainAliasName(queryBuilder, keys[0]) : undefined;
|
|
145
|
+
return {
|
|
146
|
+
aliasName,
|
|
147
|
+
propertyPath: keys[0],
|
|
148
|
+
metadata: getAliasMetadata(queryBuilder, aliasName),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const [firstKey, ...restKeys] = keys;
|
|
152
|
+
const expressionMap = queryBuilder.expressionMap;
|
|
153
|
+
const mainAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.mainAlias;
|
|
154
|
+
// UPDATE / DELETE have no alias to qualify with: `u => u.user.firstName` is `"firstName"` there
|
|
155
|
+
if (!prefixing && (mainAlias === null || mainAlias === void 0 ? void 0 : mainAlias.name) === firstKey) {
|
|
156
|
+
const metadata = getAliasMetadata(queryBuilder, undefined);
|
|
157
|
+
return {
|
|
158
|
+
aliasName: undefined,
|
|
159
|
+
propertyPath: restKeys.join('.'),
|
|
160
|
+
metadata,
|
|
161
|
+
...findColumnPath(metadata, restKeys),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const queryAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.aliases.find((alias) => alias.name === firstKey);
|
|
165
|
+
if (!queryAlias && mainAlias) {
|
|
166
|
+
const mainMetadata = getAliasMetadata(queryBuilder, undefined);
|
|
167
|
+
const columnPath = findColumnPath(mainMetadata, keys);
|
|
168
|
+
if (columnPath) {
|
|
169
|
+
return {
|
|
170
|
+
aliasName: prefixing ? mainAlias.name : undefined,
|
|
171
|
+
metadata: mainMetadata,
|
|
172
|
+
...columnPath,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const metadata = (queryAlias === null || queryAlias === void 0 ? void 0 : queryAlias.hasMetadata) ? queryAlias.metadata : undefined;
|
|
177
|
+
return {
|
|
178
|
+
aliasName: firstKey,
|
|
179
|
+
propertyPath: restKeys.join('.'),
|
|
180
|
+
metadata,
|
|
181
|
+
...findColumnPath(metadata, restKeys),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* `"alias"."column"` (or `"column"` without an alias) with the database column name, followed by the path
|
|
186
|
+
* inside a JSON column: `"exec"."details" -> 'meta' ->> 'endTs'` (see `toJsonPathSql`).
|
|
187
|
+
*/
|
|
188
|
+
function toQuotedColumnKey(queryBuilder, { aliasName, propertyPath, metadata, column, jsonPath }, jsonOptions) {
|
|
189
|
+
var _a, _b;
|
|
190
|
+
const columnName = (_b = (_a = (column !== null && column !== void 0 ? column : metadata === null || metadata === void 0 ? void 0 : metadata.findColumnWithPropertyPath(propertyPath))) === null || _a === void 0 ? void 0 : _a.databaseName) !== null && _b !== void 0 ? _b : propertyPath;
|
|
191
|
+
const columnSql = aliasName === undefined
|
|
192
|
+
? queryBuilder.escape(columnName)
|
|
193
|
+
: `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
|
|
194
|
+
if (!column || !jsonPath) {
|
|
195
|
+
return columnSql;
|
|
196
|
+
}
|
|
197
|
+
const driver = (0, sql_utils_1.getDriverType)(queryBuilder);
|
|
198
|
+
return (0, json_path_utils_1.toJsonPathSql)(driver, columnSql, column, jsonPath, jsonOptions);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* SQL key for a typed callback on `builder`: `"alias"."column"`, or `"column"` on UPDATE / DELETE.
|
|
202
|
+
*
|
|
203
|
+
* The property is mapped to its database column name (`@Column({ name })`, embedded columns)
|
|
204
|
+
* through the metadata of the alias it belongs to (see `resolveTypedKey`). Aliases without
|
|
205
|
+
* metadata (subqueries) and unknown properties keep the property name. A path into a JSON column
|
|
206
|
+
* gives its JSON accessor, cast on PostgreSQL to the type of `jsonOptions.value`.
|
|
207
|
+
*/
|
|
208
|
+
function getTypedColumnKey(builder, callback, jsonOptions) {
|
|
209
|
+
const queryBuilder = builder;
|
|
210
|
+
const toKey = (keyHolder) => toQuotedColumnKey(queryBuilder, resolveTypedKey(queryBuilder, keyHolder.getKeys()), jsonOptions);
|
|
211
|
+
// the depth is checked against the query's metadata, where a JSON column may be deeper
|
|
212
|
+
const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(callback, true);
|
|
213
|
+
return Array.isArray(keyHolder) ? keyHolder.map(toKey) : toKey(keyHolder);
|
|
214
|
+
}
|
|
215
|
+
exports.getTypedColumnKey = getTypedColumnKey;
|
|
216
|
+
/**
|
|
217
|
+
* Quoted column of a typed callback that must select exactly one column
|
|
218
|
+
* (`whereIsAnyTyped`, `whereArrayContainsTyped`, …). Throws a `TypeError` for several columns:
|
|
219
|
+
* one condition per column has no single obvious meaning (AND? OR?), so don't guess.
|
|
220
|
+
*/
|
|
221
|
+
function getTypedSingleColumnKey(builder, callback, methodName, jsonOptions) {
|
|
222
|
+
const key = getTypedColumnKey(builder, callback, jsonOptions);
|
|
223
|
+
if (Array.isArray(key)) {
|
|
224
|
+
throw new TypeError(`${methodName} expects the callback to select a single column, got ${key.length}: ${key.join(', ')}`);
|
|
225
|
+
}
|
|
226
|
+
return key;
|
|
227
|
+
}
|
|
228
|
+
exports.getTypedSingleColumnKey = getTypedSingleColumnKey;
|
|
229
|
+
/**
|
|
230
|
+
* Key for typed select / order / distinct on a SelectQueryBuilder.
|
|
231
|
+
*
|
|
232
|
+
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
233
|
+
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
234
|
+
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. Strings are kept as is.
|
|
235
|
+
*/
|
|
236
|
+
function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
237
|
+
if (typeof selection === 'string') {
|
|
238
|
+
return selection;
|
|
239
|
+
}
|
|
240
|
+
const queryBuilder = builder;
|
|
241
|
+
const toKey = (keyHolder) => {
|
|
242
|
+
const resolved = resolveTypedKey(queryBuilder, keyHolder.getKeys());
|
|
243
|
+
if (resolved.jsonPath) {
|
|
244
|
+
throw new TypeError(`Typed key "${keyHolder.getKeys().join('.')}" points into the JSON column "${resolved.propertyPath}": ` +
|
|
245
|
+
`select, order and distinct take columns only. For an expression use tableHelper().jsonColumn()`);
|
|
246
|
+
}
|
|
247
|
+
if (useQuotes) {
|
|
248
|
+
return toQuotedColumnKey(queryBuilder, resolved);
|
|
249
|
+
}
|
|
250
|
+
return resolved.aliasName === undefined
|
|
251
|
+
? resolved.propertyPath
|
|
252
|
+
: `${resolved.aliasName}.${resolved.propertyPath}`;
|
|
253
|
+
};
|
|
254
|
+
const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(selection);
|
|
255
|
+
return Array.isArray(keyHolder) ? keyHolder.map(toKey) : toKey(keyHolder);
|
|
256
|
+
}
|
|
257
|
+
exports.getTypedSelectionKey = getTypedSelectionKey;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DatabaseType } from 'typeorm';
|
|
2
|
+
import type { LikeMatch } from '../types/extensions/search.types';
|
|
3
|
+
/**
|
|
4
|
+
* Escapes the LIKE wildcards `%` and `_`, and the escape character `\` itself, with a backslash:
|
|
5
|
+
* `50%_off` → `50\%\_off`. The default escape character of PostgreSQL and MySQL;
|
|
6
|
+
* SQLite needs `ESCAPE '\'`, which the search methods add.
|
|
7
|
+
*/
|
|
8
|
+
export declare function escapeLikePattern(value: string): string;
|
|
9
|
+
/** LIKE pattern for `value`: `%value%`, `value%`, `%value` or `value`. */
|
|
10
|
+
export declare function toLikePattern(value: string, match?: LikeMatch, escape?: boolean): string;
|
|
11
|
+
/**
|
|
12
|
+
* Trimmed search term, or `undefined` when there is nothing to search for (`null`, `undefined`,
|
|
13
|
+
* an empty or blank string): search methods add no condition then.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toSearchTerm(value: unknown, methodName: string): string | undefined;
|
|
16
|
+
/** Function to wrap both sides with for `unaccent`, or `undefined` for none. */
|
|
17
|
+
export declare function toUnaccentFunction(unaccent: boolean | string | undefined): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Case-insensitive LIKE of `column` against `parameter` in the syntax of the database:
|
|
20
|
+
* - PostgreSQL (and any other driver): `column ILIKE :p`, which `gin_trgm_ops` indexes support;
|
|
21
|
+
* - MySQL / MariaDB: `column LIKE :p`, case-insensitive with the default `_ci` collations;
|
|
22
|
+
* - SQLite: `column LIKE :p ESCAPE '\'`, case-insensitive for ASCII; SQLite has no default
|
|
23
|
+
* escape character.
|
|
24
|
+
*
|
|
25
|
+
* With `unaccentFunction`, both sides are wrapped with it: `unaccent(column) ILIKE unaccent(:p)`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildILikeCondition(driver: DatabaseType | undefined, column: string, parameter: string, unaccentFunction?: string): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildILikeCondition = exports.toUnaccentFunction = exports.toSearchTerm = exports.toLikePattern = exports.escapeLikePattern = void 0;
|
|
4
|
+
const sql_utils_1 = require("./sql.utils");
|
|
5
|
+
/**
|
|
6
|
+
* Escapes the LIKE wildcards `%` and `_`, and the escape character `\` itself, with a backslash:
|
|
7
|
+
* `50%_off` → `50\%\_off`. The default escape character of PostgreSQL and MySQL;
|
|
8
|
+
* SQLite needs `ESCAPE '\'`, which the search methods add.
|
|
9
|
+
*/
|
|
10
|
+
function escapeLikePattern(value) {
|
|
11
|
+
return value.replace(/[\\%_]/g, '\\$&');
|
|
12
|
+
}
|
|
13
|
+
exports.escapeLikePattern = escapeLikePattern;
|
|
14
|
+
/** LIKE pattern for `value`: `%value%`, `value%`, `%value` or `value`. */
|
|
15
|
+
function toLikePattern(value, match = 'contains', escape = true) {
|
|
16
|
+
const text = escape ? escapeLikePattern(value) : value;
|
|
17
|
+
switch (match) {
|
|
18
|
+
case 'contains':
|
|
19
|
+
return `%${text}%`;
|
|
20
|
+
case 'prefix':
|
|
21
|
+
return `${text}%`;
|
|
22
|
+
case 'suffix':
|
|
23
|
+
return `%${text}`;
|
|
24
|
+
case 'exact':
|
|
25
|
+
return text;
|
|
26
|
+
default:
|
|
27
|
+
throw new TypeError(`Invalid match "${match}": expected "contains", "prefix", "suffix" or "exact"`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.toLikePattern = toLikePattern;
|
|
31
|
+
/**
|
|
32
|
+
* Trimmed search term, or `undefined` when there is nothing to search for (`null`, `undefined`,
|
|
33
|
+
* an empty or blank string): search methods add no condition then.
|
|
34
|
+
*/
|
|
35
|
+
function toSearchTerm(value, methodName) {
|
|
36
|
+
if (value === null || value === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value !== 'string') {
|
|
40
|
+
throw new TypeError(`${methodName} expects a string to search for, got ${typeof value}`);
|
|
41
|
+
}
|
|
42
|
+
const term = value.trim();
|
|
43
|
+
return term.length > 0 ? term : undefined;
|
|
44
|
+
}
|
|
45
|
+
exports.toSearchTerm = toSearchTerm;
|
|
46
|
+
// `unaccent`, `public.f_unaccent`: a function name, optionally schema-qualified
|
|
47
|
+
const FUNCTION_NAME = /^[A-Za-z_][\w$]*(?:\.[A-Za-z_][\w$]*)?$/;
|
|
48
|
+
/** Function to wrap both sides with for `unaccent`, or `undefined` for none. */
|
|
49
|
+
function toUnaccentFunction(unaccent) {
|
|
50
|
+
if (unaccent === undefined || unaccent === false) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
if (unaccent === true) {
|
|
54
|
+
return 'unaccent';
|
|
55
|
+
}
|
|
56
|
+
if (typeof unaccent !== 'string' || !FUNCTION_NAME.test(unaccent)) {
|
|
57
|
+
// the name is inserted into the SQL as is
|
|
58
|
+
throw new TypeError(`Invalid unaccent function "${unaccent}": expected a function name like "unaccent"`);
|
|
59
|
+
}
|
|
60
|
+
return unaccent;
|
|
61
|
+
}
|
|
62
|
+
exports.toUnaccentFunction = toUnaccentFunction;
|
|
63
|
+
/**
|
|
64
|
+
* Case-insensitive LIKE of `column` against `parameter` in the syntax of the database:
|
|
65
|
+
* - PostgreSQL (and any other driver): `column ILIKE :p`, which `gin_trgm_ops` indexes support;
|
|
66
|
+
* - MySQL / MariaDB: `column LIKE :p`, case-insensitive with the default `_ci` collations;
|
|
67
|
+
* - SQLite: `column LIKE :p ESCAPE '\'`, case-insensitive for ASCII; SQLite has no default
|
|
68
|
+
* escape character.
|
|
69
|
+
*
|
|
70
|
+
* With `unaccentFunction`, both sides are wrapped with it: `unaccent(column) ILIKE unaccent(:p)`.
|
|
71
|
+
*/
|
|
72
|
+
function buildILikeCondition(driver, column, parameter, unaccentFunction) {
|
|
73
|
+
const left = unaccentFunction ? `${unaccentFunction}(${column})` : column;
|
|
74
|
+
const right = unaccentFunction ? `${unaccentFunction}(${parameter})` : parameter;
|
|
75
|
+
if ((0, sql_utils_1.isMysqlLikeDriver)(driver)) {
|
|
76
|
+
return `${left} LIKE ${right}`;
|
|
77
|
+
}
|
|
78
|
+
if ((0, sql_utils_1.isSqliteLikeDriver)(driver)) {
|
|
79
|
+
return `${left} LIKE ${right} ESCAPE '\\'`;
|
|
80
|
+
}
|
|
81
|
+
return `${left} ILIKE ${right}`;
|
|
82
|
+
}
|
|
83
|
+
exports.buildILikeCondition = buildILikeCondition;
|
|
@@ -1,6 +1,34 @@
|
|
|
1
|
-
import { DatabaseType } from 'typeorm';
|
|
1
|
+
import type { DatabaseType, WhereExpressionBuilder } from 'typeorm';
|
|
2
2
|
export declare const wrapWithDoubleQoutes: (str: string) => string;
|
|
3
3
|
export declare const wrapWithBackQoutes: (str: string) => string;
|
|
4
|
+
/** MySQL, MariaDB and Aurora MySQL: backtick identifiers, `<=>` null-safe comparison. */
|
|
5
|
+
export declare const isMysqlLikeDriver: (driver?: DatabaseType) => boolean;
|
|
6
|
+
/** PostgreSQL and the databases speaking its dialect. */
|
|
7
|
+
export declare const isPostgresLikeDriver: (driver?: DatabaseType) => boolean;
|
|
8
|
+
/** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
|
|
9
|
+
export declare const isSqliteLikeDriver: (driver?: DatabaseType) => boolean;
|
|
10
|
+
/** Database type of the query builder's connection, e.g. `'postgres'`; undefined without one. */
|
|
11
|
+
export declare function getDriverType(builder: WhereExpressionBuilder): DatabaseType | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Quotes one identifier, whatever characters it has: `first-name` → `"first-name"`, a `"` inside is
|
|
14
|
+
* doubled. An already quoted identifier is kept. For names known to be identifiers (columns, aliases).
|
|
15
|
+
*/
|
|
16
|
+
export declare const quoteIdentifier: (name: string, driver?: DatabaseType) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Quotes an alias or a table: as a path when it is one (`schema.table` → `"schema"."table"`),
|
|
19
|
+
* otherwise as one identifier (`user-1` → `"user-1"`).
|
|
20
|
+
*/
|
|
21
|
+
export declare const quoteIdentifierPath: (path: string, driver?: DatabaseType) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Quotes an identifier path: `user.id` → `"user"."id"`, `user.*` → `"user".*`.
|
|
24
|
+
* Already quoted segments are kept. Anything that isn't a path of identifiers
|
|
25
|
+
* (`COALESCE(a.x, b.x)`, `LOWER(u.email)`, `u.id::text`, `*`) is returned as is.
|
|
26
|
+
*/
|
|
4
27
|
export declare const stringToSQLIdentifier: (str: string, driver?: DatabaseType) => string;
|
|
5
28
|
export declare const stringToSQLValue: (str: string) => string;
|
|
6
29
|
export declare const createUniqueParameterName: (prefix?: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Returns `arrayType` if it is a type name (`uuid[]`, `public.order_status[]`, `varchar(36)[]`),
|
|
32
|
+
* throws a `TypeError` otherwise: the type is inserted into the SQL as is.
|
|
33
|
+
*/
|
|
34
|
+
export declare function assertArrayType(arrayType: string): string;
|
package/dist/utils/sql.utils.js
CHANGED
|
@@ -1,19 +1,85 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createUniqueParameterName = exports.stringToSQLValue = exports.stringToSQLIdentifier = exports.wrapWithBackQoutes = exports.wrapWithDoubleQoutes = void 0;
|
|
3
|
+
exports.assertArrayType = exports.createUniqueParameterName = exports.stringToSQLValue = exports.stringToSQLIdentifier = exports.quoteIdentifierPath = exports.quoteIdentifier = exports.getDriverType = exports.isSqliteLikeDriver = exports.isPostgresLikeDriver = exports.isMysqlLikeDriver = exports.wrapWithBackQoutes = exports.wrapWithDoubleQoutes = void 0;
|
|
4
4
|
const common_utils_1 = require("./common.utils");
|
|
5
5
|
const wrapWithDoubleQoutes = (str) => str.includes('"') ? str : `"${str}"`;
|
|
6
6
|
exports.wrapWithDoubleQoutes = wrapWithDoubleQoutes;
|
|
7
7
|
const wrapWithBackQoutes = (str) => str.includes('`') ? str : '`' + str + '`';
|
|
8
8
|
exports.wrapWithBackQoutes = wrapWithBackQoutes;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const BACKTICK = '`';
|
|
10
|
+
// one identifier: unquoted (user, firstName) or already quoted ("user", `user`, "a""b")
|
|
11
|
+
const UNQUOTED_IDENTIFIER = '[\\p{L}_][\\p{L}\\p{N}_$]*';
|
|
12
|
+
const DOUBLE_QUOTED_IDENTIFIER = '"(?:[^"]|"")+"';
|
|
13
|
+
const BACK_QUOTED_IDENTIFIER = `${BACKTICK}(?:[^${BACKTICK}]|${BACKTICK}${BACKTICK})+${BACKTICK}`;
|
|
14
|
+
const IDENTIFIER = `(?:${UNQUOTED_IDENTIFIER}|${DOUBLE_QUOTED_IDENTIFIER}|${BACK_QUOTED_IDENTIFIER})`;
|
|
15
|
+
// `id`, `user.id`, `"user"."id"`, `schema.table.column`, optionally ending with `.*`
|
|
16
|
+
const IDENTIFIER_PATH = new RegExp(`^${IDENTIFIER}(?:\\.${IDENTIFIER})*(?:\\.\\*)?$`, 'u');
|
|
17
|
+
const IDENTIFIER_PATH_SEGMENT = new RegExp(`${IDENTIFIER}|\\*`, 'gu');
|
|
18
|
+
/** MySQL, MariaDB and Aurora MySQL: backtick identifiers, `<=>` null-safe comparison. */
|
|
19
|
+
const isMysqlLikeDriver = (driver) => driver === 'mysql' || driver === 'mariadb' || driver === 'aurora-mysql';
|
|
20
|
+
exports.isMysqlLikeDriver = isMysqlLikeDriver;
|
|
21
|
+
// `string[]`: TypeORM 1.0 removed the `sqlite` driver from `DatabaseType`, 0.3.x still has it
|
|
22
|
+
const SQLITE_LIKE_DRIVERS = [
|
|
23
|
+
'sqlite',
|
|
24
|
+
'better-sqlite3',
|
|
25
|
+
'sqljs',
|
|
26
|
+
'capacitor',
|
|
27
|
+
'cordova',
|
|
28
|
+
'expo',
|
|
29
|
+
'nativescript',
|
|
30
|
+
'react-native',
|
|
31
|
+
];
|
|
32
|
+
const POSTGRES_LIKE_DRIVERS = ['postgres', 'aurora-postgres', 'cockroachdb'];
|
|
33
|
+
/** PostgreSQL and the databases speaking its dialect. */
|
|
34
|
+
const isPostgresLikeDriver = (driver) => driver !== undefined && POSTGRES_LIKE_DRIVERS.includes(driver);
|
|
35
|
+
exports.isPostgresLikeDriver = isPostgresLikeDriver;
|
|
36
|
+
/** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
|
|
37
|
+
const isSqliteLikeDriver = (driver) => driver !== undefined && SQLITE_LIKE_DRIVERS.includes(driver);
|
|
38
|
+
exports.isSqliteLikeDriver = isSqliteLikeDriver;
|
|
39
|
+
/** Database type of the query builder's connection, e.g. `'postgres'`; undefined without one. */
|
|
40
|
+
function getDriverType(builder) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
return (_c = (_b = (_a = builder.connection) === null || _a === void 0 ? void 0 : _a.driver) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.type;
|
|
43
|
+
}
|
|
44
|
+
exports.getDriverType = getDriverType;
|
|
45
|
+
/**
|
|
46
|
+
* Quotes one identifier, whatever characters it has: `first-name` → `"first-name"`, a `"` inside is
|
|
47
|
+
* doubled. An already quoted identifier is kept. For names known to be identifiers (columns, aliases).
|
|
48
|
+
*/
|
|
49
|
+
const quoteIdentifier = (name, driver) => {
|
|
50
|
+
const mysql = (0, exports.isMysqlLikeDriver)(driver);
|
|
51
|
+
const quote = mysql ? BACKTICK : '"';
|
|
52
|
+
if (name.length >= 2 && name.startsWith(quote) && name.endsWith(quote)) {
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
return `${quote}${name.split(quote).join(quote + quote)}${quote}`;
|
|
56
|
+
};
|
|
57
|
+
exports.quoteIdentifier = quoteIdentifier;
|
|
58
|
+
/**
|
|
59
|
+
* Quotes an alias or a table: as a path when it is one (`schema.table` → `"schema"."table"`),
|
|
60
|
+
* otherwise as one identifier (`user-1` → `"user-1"`).
|
|
61
|
+
*/
|
|
62
|
+
const quoteIdentifierPath = (path, driver) => IDENTIFIER_PATH.test(path) ? (0, exports.stringToSQLIdentifier)(path, driver) : (0, exports.quoteIdentifier)(path, driver);
|
|
63
|
+
exports.quoteIdentifierPath = quoteIdentifierPath;
|
|
64
|
+
/**
|
|
65
|
+
* Quotes an identifier path: `user.id` → `"user"."id"`, `user.*` → `"user".*`.
|
|
66
|
+
* Already quoted segments are kept. Anything that isn't a path of identifiers
|
|
67
|
+
* (`COALESCE(a.x, b.x)`, `LOWER(u.email)`, `u.id::text`, `*`) is returned as is.
|
|
68
|
+
*/
|
|
69
|
+
const stringToSQLIdentifier = (str, driver) => {
|
|
70
|
+
var _a;
|
|
71
|
+
if (!IDENTIFIER_PATH.test(str)) {
|
|
72
|
+
return str;
|
|
73
|
+
}
|
|
74
|
+
const quote = (0, exports.isMysqlLikeDriver)(driver)
|
|
75
|
+
? exports.wrapWithBackQoutes
|
|
76
|
+
: exports.wrapWithDoubleQoutes;
|
|
77
|
+
return ((_a = str.match(IDENTIFIER_PATH_SEGMENT)) !== null && _a !== void 0 ? _a : [])
|
|
78
|
+
.map((segment) => segment === '*' || segment.startsWith('"') || segment.startsWith(BACKTICK)
|
|
79
|
+
? segment
|
|
80
|
+
: quote(segment))
|
|
81
|
+
.join('.');
|
|
82
|
+
};
|
|
17
83
|
exports.stringToSQLIdentifier = stringToSQLIdentifier;
|
|
18
84
|
const stringToSQLValue = (str) => `'${str}'`;
|
|
19
85
|
exports.stringToSQLValue = stringToSQLValue;
|
|
@@ -22,3 +88,19 @@ const createUniqueParameterName = (prefix) => {
|
|
|
22
88
|
return `${prefix || 'param'}_${uniqueCode}`;
|
|
23
89
|
};
|
|
24
90
|
exports.createUniqueParameterName = createUniqueParameterName;
|
|
91
|
+
// a type name, optionally schema-qualified, multi-word (`character varying`), with a
|
|
92
|
+
// modifier (`varchar(255)`) and array brackets: `uuid[]`, `public.order_status[]`, `"MyEnum"[]`
|
|
93
|
+
const TYPE_NAME_PART = '(?:[A-Za-z_][\\w$]*|"(?:[^"]|"")+")';
|
|
94
|
+
const ARRAY_TYPE_PATTERN = new RegExp(`^${TYPE_NAME_PART}(?:\\.${TYPE_NAME_PART})*(?: ${TYPE_NAME_PART})*` +
|
|
95
|
+
`(?:\\(\\d+(?:, ?\\d+)?\\))?(?:\\[\\d*\\])*$`);
|
|
96
|
+
/**
|
|
97
|
+
* Returns `arrayType` if it is a type name (`uuid[]`, `public.order_status[]`, `varchar(36)[]`),
|
|
98
|
+
* throws a `TypeError` otherwise: the type is inserted into the SQL as is.
|
|
99
|
+
*/
|
|
100
|
+
function assertArrayType(arrayType) {
|
|
101
|
+
if (typeof arrayType !== 'string' || !ARRAY_TYPE_PATTERN.test(arrayType)) {
|
|
102
|
+
throw new TypeError(`Invalid array type "${arrayType}": expected a type name like "uuid[]"`);
|
|
103
|
+
}
|
|
104
|
+
return arrayType;
|
|
105
|
+
}
|
|
106
|
+
exports.assertArrayType = assertArrayType;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ObjectLiteral } from "typeorm";
|
|
2
|
+
export type TableHelperOptions = {
|
|
3
|
+
useQuotes?: boolean;
|
|
4
|
+
};
|
|
5
|
+
type JsonFieldTuple<T, Depth extends 0[] = []> = Depth['length'] extends 4 ? [string, ...string[]] : {
|
|
6
|
+
[K in keyof T & string]: [NonNullable<T[K]>] extends [Record<string, unknown>] ? [K, ...JsonFieldTuple<NonNullable<T[K]>, [...Depth, 0]>] | [K] : [K];
|
|
7
|
+
}[keyof T & string];
|
|
8
|
+
export type TableHelperCountOptions = {
|
|
9
|
+
/** `COUNT(DISTINCT column)`: count the different values of the column. Needs a column. */
|
|
10
|
+
distinct?: boolean;
|
|
11
|
+
/** Count only the rows that match this condition, e.g. `t.where('status', "= 'active'")`. */
|
|
12
|
+
filter?: string;
|
|
13
|
+
/**
|
|
14
|
+
* How `filter` is written:
|
|
15
|
+
* - `'case'` (default): `COUNT(CASE WHEN filter THEN column END)`, in every database;
|
|
16
|
+
* - `'filter'`: `COUNT(column) FILTER (WHERE filter)`, PostgreSQL and SQLite 3.30+ only, a few percent faster on PostgreSQL.
|
|
17
|
+
*/
|
|
18
|
+
syntax?: 'case' | 'filter';
|
|
19
|
+
};
|
|
20
|
+
export type JsonFieldRest<V> = [NonNullable<V>] extends [Record<string, unknown>] ? JsonFieldTuple<NonNullable<V>> : string[];
|
|
21
|
+
/**
|
|
22
|
+
* Creates a type-safe SQL helper for building column references, WHERE expressions,
|
|
23
|
+
* JOIN clauses, and JSONB accessors for a given entity type.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const t = tableHelper<UserEntity>('user');
|
|
27
|
+
* t.column('email') // → "user"."email"
|
|
28
|
+
* t.where('id', '= :id') // → "user"."id" = :id
|
|
29
|
+
* t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
|
|
30
|
+
*/
|
|
31
|
+
export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, options?: TableHelperOptions) => {
|
|
32
|
+
alias: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Returns a quoted SQL column identifier, e.g. `"alias"."column"`. The column is always quoted as one
|
|
35
|
+
* identifier, so names with other characters work too: `"user"."first-name"`.
|
|
36
|
+
*/
|
|
37
|
+
column(columnName: keyof T, alias?: string): string;
|
|
38
|
+
/** Returns a column identifier optionally followed by an expression, e.g. `"user"."id" = :id`. */
|
|
39
|
+
where(columnName: keyof T, expression?: string): string;
|
|
40
|
+
/** Returns an unquoted join path, e.g. `user.images`. Needs the helper's alias, or `alias`. */
|
|
41
|
+
join(relation: keyof T, alias?: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Returns a column with a JOIN ON expression, e.g. `"user"."id" = "images"."userId"`.
|
|
44
|
+
* The expression is inserted as is: build its columns with another `tableHelper`
|
|
45
|
+
* (`t.joinOn('id', '= ' + images.column('userId'))`), since unquoted camelCase names
|
|
46
|
+
* like `images.userId` are folded to lowercase by PostgreSQL unless TypeORM rewrites them.
|
|
47
|
+
*/
|
|
48
|
+
joinOn(thisColumn: keyof T, expression: string): string;
|
|
49
|
+
/** Returns `"alias"."column" IS NULL`. */
|
|
50
|
+
isNull(columnName: keyof T): string;
|
|
51
|
+
/** Returns `"alias"."column" IS NOT NULL`. */
|
|
52
|
+
isNotNull(columnName: keyof T): string;
|
|
53
|
+
/**
|
|
54
|
+
* Returns `CAST(COUNT("alias"."column") AS integer)`: the rows where the column isn't `NULL`.
|
|
55
|
+
* Without a column it is `COUNT(*)`, all the rows of the result, in every database. To count the rows
|
|
56
|
+
* of a table joined with LEFT JOIN, count one of its non-null columns: `images.count('id')`.
|
|
57
|
+
* @example
|
|
58
|
+
* t.count('email', { distinct: true }) // CAST(COUNT(DISTINCT "user"."email") AS integer)
|
|
59
|
+
* t.count(undefined, { filter: t.where('status', "= 'active'") })
|
|
60
|
+
* // CAST(COUNT(CASE WHEN "user"."status" = 'active' THEN 1 END) AS integer)
|
|
61
|
+
* t.count(undefined, { filter: t.where('status', "= 'active'"), syntax: 'filter' })
|
|
62
|
+
* // CAST(COUNT(*) FILTER (WHERE "user"."status" = 'active') AS integer)
|
|
63
|
+
*/
|
|
64
|
+
count(columnName?: keyof T | undefined, countOptions?: TableHelperCountOptions): string;
|
|
65
|
+
/** Returns `CAST(expression AS type)`. */
|
|
66
|
+
cast(expression: string, type: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Returns a full JSONB column access expression.
|
|
69
|
+
* @example
|
|
70
|
+
* t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
|
|
71
|
+
* t.jsonColumn('metadata', '->>', 'settings', 'theme') // → "user"."metadata" -> 'settings' ->> 'theme'
|
|
72
|
+
*/
|
|
73
|
+
jsonColumn<K extends keyof T>(columnName: K, jsonOperator: '->>' | '->', ...fieldNames: JsonFieldRest<T[K]>): string;
|
|
74
|
+
/**
|
|
75
|
+
* Returns only the JSONB operator fragment (without the column).
|
|
76
|
+
* Useful inside `whereTyped` conditions strings.
|
|
77
|
+
* @example
|
|
78
|
+
* t.jsonPath('metadata', '->>', 'role') // → ->> 'role'
|
|
79
|
+
* t.jsonPath('metadata', '->>', 'settings', 'theme') // → -> 'settings' ->> 'theme'
|
|
80
|
+
*
|
|
81
|
+
* // Combined with whereTyped:
|
|
82
|
+
* qb.whereTyped(u => u.metadata, `${t.jsonPath('metadata', '->>', 'role')} =`, 'admin')
|
|
83
|
+
*/
|
|
84
|
+
jsonPath<K_1 extends keyof T>(_columnName: K_1, jsonOperator: '->>' | '->', ...fieldNames: JsonFieldRest<T[K_1]>): string;
|
|
85
|
+
};
|
|
86
|
+
export {};
|