typeorm-extensions 2.0.0 → 2.2.0-rc.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/LICENSE +21 -0
- package/README.md +9 -8
- package/dist/decorators/calculated-column.decorator.d.ts +30 -0
- package/dist/decorators/calculated-column.decorator.js +82 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +17 -0
- package/dist/extensions/calculated-column.extension.d.ts +33 -0
- package/dist/extensions/calculated-column.extension.js +33 -0
- package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
- package/dist/extensions/cursor-pagination.extension.js +4 -3
- package/dist/extensions/get-many-with-totals.extension.d.ts +9 -1
- package/dist/extensions/get-many-with-totals.extension.js +158 -10
- package/dist/extensions/index.d.ts +7 -0
- package/dist/extensions/index.js +7 -0
- package/dist/extensions/join-lateral.extension.d.ts +19 -0
- package/dist/extensions/join-lateral.extension.js +50 -0
- package/dist/extensions/join-unnest.extension.d.ts +19 -0
- package/dist/extensions/join-unnest.extension.js +40 -0
- package/dist/extensions/order.extension.js +18 -7
- package/dist/extensions/query-returning.extension.d.ts +29 -0
- package/dist/extensions/query-returning.extension.js +50 -0
- package/dist/extensions/typed/index.d.ts +3 -0
- package/dist/extensions/typed/index.js +3 -2
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +24 -0
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +31 -0
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +36 -0
- package/dist/extensions/typed/typed-having/typed-having.extension.js +42 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +10 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +3 -2
- package/dist/extensions/typed/typed-root.extension.d.ts +25 -0
- package/dist/extensions/typed/typed-root.extension.js +13 -0
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-select/typed-select.extension.js +3 -2
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +2 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +10 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +32 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +39 -129
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +2 -1
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +36 -117
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +11 -19
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +30 -73
- package/dist/extensions/typed/typed-where/typed-where.extension.js +10 -10
- package/dist/extensions/union.extension.d.ts +22 -0
- package/dist/extensions/union.extension.js +52 -0
- package/dist/extensions/where-exists.extension.d.ts +50 -0
- package/dist/extensions/where-exists.extension.js +64 -0
- package/dist/extensions/where-relation-exists.extension.d.ts +38 -0
- package/dist/extensions/where-relation-exists.extension.js +95 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/types/extensions/cursor-pagination.types.d.ts +5 -0
- package/dist/types/extensions/get-many-with-totals.types.d.ts +19 -0
- package/dist/types/extensions/index.d.ts +5 -0
- package/dist/types/extensions/index.js +5 -0
- package/dist/types/extensions/join-lateral.types.d.ts +6 -0
- package/dist/types/extensions/join-lateral.types.js +2 -0
- package/dist/types/extensions/period.types.d.ts +8 -0
- package/dist/types/extensions/query-returning.types.d.ts +11 -0
- package/dist/types/extensions/query-returning.types.js +2 -0
- package/dist/types/extensions/typed-group-by.types.d.ts +8 -0
- package/dist/types/extensions/typed-group-by.types.js +2 -0
- package/dist/types/extensions/typed-having.types.d.ts +2 -0
- package/dist/types/extensions/typed-having.types.js +2 -0
- package/dist/types/extensions/union.types.d.ts +12 -0
- package/dist/types/extensions/union.types.js +2 -0
- 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/index.d.ts +1 -0
- package/dist/types/interfaces/index.js +1 -0
- package/dist/types/modules/proxy-callback.types.d.ts +28 -4
- package/dist/types/modules/typed-params.types.d.ts +45 -0
- package/dist/types/modules/typed-params.types.js +2 -0
- package/dist/types/modules/typed-root.types.d.ts +13 -0
- package/dist/types/modules/typed-root.types.js +2 -0
- package/dist/utils/calculated-column.utils.d.ts +21 -0
- package/dist/utils/calculated-column.utils.js +63 -0
- package/dist/utils/json-path.utils.d.ts +22 -0
- package/dist/utils/json-path.utils.js +57 -0
- package/dist/utils/parameter.utils.d.ts +7 -0
- package/dist/utils/parameter.utils.js +21 -0
- package/dist/utils/period.utils.js +7 -2
- package/dist/utils/prototype.utils.d.ts +5 -1
- package/dist/utils/prototype.utils.js +4 -1
- package/dist/utils/proxy-key.utils.d.ts +9 -4
- package/dist/utils/proxy-key.utils.js +21 -14
- package/dist/utils/query-builder.utils.d.ts +13 -3
- package/dist/utils/query-builder.utils.js +95 -18
- package/dist/utils/sql.utils.d.ts +2 -0
- package/dist/utils/sql.utils.js +5 -1
- package/dist/utils/table.utils.d.ts +112 -2
- package/dist/utils/table.utils.js +206 -22
- package/dist/utils/time-zone.utils.d.ts +8 -0
- package/dist/utils/time-zone.utils.js +102 -0
- package/dist/utils/where.utils.d.ts +2 -2
- package/dist/utils/where.utils.js +23 -2
- package/package.json +4 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withUniqueParameterNames = void 0;
|
|
4
|
+
const sql_utils_1 = require("./sql.utils");
|
|
5
|
+
// TypeORM's placeholder syntax, `:name` and `:...name`, as its drivers match it
|
|
6
|
+
const PARAMETER_PLACEHOLDER = /:(\.\.\.)?([A-Za-z0-9_.]+)/g;
|
|
7
|
+
/**
|
|
8
|
+
* Renames the parameters of a subquery to unique names, in its SQL and in its parameters. A query
|
|
9
|
+
* has one parameter namespace, so two subqueries with a `:status` each would otherwise both read
|
|
10
|
+
* whichever value was set last. Placeholders that aren't keys of `parameters` stay as they are.
|
|
11
|
+
*/
|
|
12
|
+
function withUniqueParameterNames(sql, parameters) {
|
|
13
|
+
const names = new Map(Object.keys(parameters).map((name) => [name, (0, sql_utils_1.createUniqueParameterName)(name)]));
|
|
14
|
+
const renamedSql = sql.replace(PARAMETER_PLACEHOLDER, (placeholder, spread, name) => {
|
|
15
|
+
const unique = names.get(name);
|
|
16
|
+
return unique ? `:${spread !== null && spread !== void 0 ? spread : ''}${unique}` : placeholder;
|
|
17
|
+
});
|
|
18
|
+
const renamedParameters = Object.fromEntries(Object.entries(parameters).map(([name, value]) => [names.get(name), value]));
|
|
19
|
+
return [renamedSql, renamedParameters];
|
|
20
|
+
}
|
|
21
|
+
exports.withUniqueParameterNames = withUniqueParameterNames;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.attachPeriodFilter = void 0;
|
|
4
4
|
const sql_utils_1 = require("./sql.utils");
|
|
5
5
|
const where_utils_1 = require("./where.utils");
|
|
6
|
+
const time_zone_utils_1 = require("./time-zone.utils");
|
|
6
7
|
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
7
8
|
/** `'2024-01-31'` as a UTC midnight Date, or a `TypeError` if it is not a calendar date (`'2024-02-30'`). */
|
|
8
9
|
function parseDateOnly(value, name) {
|
|
@@ -63,7 +64,10 @@ function getBoundCondition(boundary, name, include) {
|
|
|
63
64
|
* `(a OR b) AND column >= …`. `column` is already quoted.
|
|
64
65
|
*/
|
|
65
66
|
function attachPeriodFilter(builder, column, periodFilter, options) {
|
|
66
|
-
const { includeFrom = true, includeTo = true } = options !== null && options !== void 0 ? options : {};
|
|
67
|
+
const { includeFrom = true, includeTo = true, timeZone } = options !== null && options !== void 0 ? options : {};
|
|
68
|
+
if (timeZone) {
|
|
69
|
+
(0, time_zone_utils_1.assertTimeZone)(timeZone);
|
|
70
|
+
}
|
|
67
71
|
const bounds = [
|
|
68
72
|
{ name: 'periodFrom', include: includeFrom },
|
|
69
73
|
{ name: 'periodTo', include: includeTo },
|
|
@@ -75,7 +79,8 @@ function attachPeriodFilter(builder, column, periodFilter, options) {
|
|
|
75
79
|
}
|
|
76
80
|
const { operator, value } = getBoundCondition(boundary, name, include);
|
|
77
81
|
const paramName = (0, sql_utils_1.createUniqueParameterName)(name);
|
|
78
|
-
(0,
|
|
82
|
+
const bound = timeZone && typeof value === 'string' ? (0, time_zone_utils_1.readInTimeZone)(value, timeZone, name) : value;
|
|
83
|
+
(0, where_utils_1.andWhereNarrowing)(builder, `${column} ${operator} :${paramName}`, { [paramName]: bound });
|
|
79
84
|
}
|
|
80
85
|
return builder;
|
|
81
86
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
export type PatchPrototypeOptions = {
|
|
2
|
+
/** Leave a method the target already has, own or inherited: TypeORM's, or another library's. */
|
|
3
|
+
onlyMissing?: boolean;
|
|
4
|
+
};
|
|
1
5
|
export declare function patchPrototype<T extends {
|
|
2
6
|
prototype: any;
|
|
3
7
|
}, S extends {
|
|
4
8
|
prototype: any;
|
|
5
|
-
}>(target: T, source: S): void;
|
|
9
|
+
}>(target: T, source: S, options?: PatchPrototypeOptions): void;
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.patchPrototype = void 0;
|
|
5
|
-
function patchPrototype(target, source) {
|
|
5
|
+
function patchPrototype(target, source, options = {}) {
|
|
6
6
|
for (const method of Object.keys(source.prototype)) {
|
|
7
|
+
if (options.onlyMissing && method in target.prototype) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
7
10
|
target.prototype[method] = source.prototype[method];
|
|
8
11
|
}
|
|
9
12
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ObjectLiteral } from 'typeorm';
|
|
1
|
+
import { DatabaseType, ObjectLiteral } from 'typeorm';
|
|
2
2
|
import { KeyProxyCallback } from '../types/modules/proxy-callback.types';
|
|
3
3
|
export declare class KeyHolder {
|
|
4
4
|
private _keys;
|
|
5
5
|
constructor(_keys?: string[]);
|
|
6
|
-
toSql(length?: number, useQuotes?: boolean): string;
|
|
6
|
+
toSql(length?: number, useQuotes?: boolean, driver?: DatabaseType): string;
|
|
7
7
|
toString(length?: number): string;
|
|
8
8
|
valueOf(): string;
|
|
9
9
|
[Symbol.toPrimitive](): string;
|
|
@@ -21,7 +21,12 @@ export declare class KeyProxy {
|
|
|
21
21
|
}
|
|
22
22
|
/** Typed keys are `column` or `alias.column`: a deeper path can't be mapped to a query alias. */
|
|
23
23
|
export declare const MAX_KEY_DEPTH = 2;
|
|
24
|
-
export declare const
|
|
24
|
+
export declare const createKeyTooDeepError: (keys: string[]) => TypeError;
|
|
25
|
+
/**
|
|
26
|
+
* Keys of a typed callback. Paths deeper than `alias.column` throw, unless `allowDeepPaths` is set by a caller
|
|
27
|
+
* that checks them against the query's metadata (paths into JSON columns).
|
|
28
|
+
*/
|
|
29
|
+
export declare const getKeyHolder: <Type extends ObjectLiteral>(callback: KeyHolder | KeyProxyCallback<Type>, allowDeepPaths?: boolean) => KeyHolder | Array<KeyHolder>;
|
|
25
30
|
export declare const getKey: <Type extends ObjectLiteral>(callback: string | KeyHolder | KeyProxyCallback<Type>) => string;
|
|
26
31
|
/**
|
|
27
32
|
* Alias to prefix a single-level key with: a string, or a function called only when it is needed,
|
|
@@ -32,4 +37,4 @@ export type KeyPrefix = string | (() => string | undefined);
|
|
|
32
37
|
* SQL key of a typed callback: `(u) => u.id` → `prefix.id`, `(u) => u.images.url` → `images.url`
|
|
33
38
|
* (an `alias.column` path keeps its own alias). Strings are returned as is.
|
|
34
39
|
*/
|
|
35
|
-
export declare const getSqlKeyFromProxyCallback: <Type extends ObjectLiteral>(callback: string | KeyHolder | KeyProxyCallback<Type>, addPrefix?: KeyPrefix, useQuotes?: boolean) => string | string[];
|
|
40
|
+
export declare const getSqlKeyFromProxyCallback: <Type extends ObjectLiteral>(callback: string | KeyHolder | KeyProxyCallback<Type>, addPrefix?: KeyPrefix, useQuotes?: boolean, driver?: DatabaseType) => string | string[];
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getSqlKeyFromProxyCallback = exports.getKey = exports.getKeyHolder = exports.MAX_KEY_DEPTH = exports.KeyProxy = exports.TypedProxy = exports.KeyHolder = void 0;
|
|
3
|
+
exports.getSqlKeyFromProxyCallback = exports.getKey = exports.getKeyHolder = exports.createKeyTooDeepError = exports.MAX_KEY_DEPTH = exports.KeyProxy = exports.TypedProxy = exports.KeyHolder = void 0;
|
|
4
4
|
const sql_utils_1 = require("./sql.utils");
|
|
5
5
|
class KeyHolder {
|
|
6
6
|
constructor(_keys = []) {
|
|
7
7
|
this._keys = _keys;
|
|
8
8
|
this._keys = [..._keys];
|
|
9
9
|
}
|
|
10
|
-
toSql(length = 2, useQuotes) {
|
|
10
|
+
toSql(length = 2, useQuotes, driver) {
|
|
11
11
|
const string = this.toString(length);
|
|
12
|
-
const sql = useQuotes ? (0, sql_utils_1.stringToSQLIdentifier)(string) : string;
|
|
12
|
+
const sql = useQuotes ? (0, sql_utils_1.stringToSQLIdentifier)(string, driver) : string;
|
|
13
13
|
return sql;
|
|
14
14
|
}
|
|
15
15
|
toString(length = 2) {
|
|
@@ -71,24 +71,31 @@ KeyProxy.defaultHandler = {
|
|
|
71
71
|
};
|
|
72
72
|
/** Typed keys are `column` or `alias.column`: a deeper path can't be mapped to a query alias. */
|
|
73
73
|
exports.MAX_KEY_DEPTH = 2;
|
|
74
|
+
const createKeyTooDeepError = (keys) => new TypeError(`Typed key "${keys.join('.')}" is ${keys.length} levels deep, but typed methods support only ` +
|
|
75
|
+
`"column" or "alias.column", or a path into a JSON column in where methods. Join the nested relation and ` +
|
|
76
|
+
`address it by its alias with an alias-keyed type, e.g. type Joins = { someAlias: SomeEntity }; (j) => j.someAlias.column`);
|
|
77
|
+
exports.createKeyTooDeepError = createKeyTooDeepError;
|
|
74
78
|
const assertKeyDepth = (keyHolder) => {
|
|
75
79
|
const keys = keyHolder.getKeys();
|
|
76
80
|
if (keys.length > exports.MAX_KEY_DEPTH) {
|
|
77
|
-
throw
|
|
78
|
-
`"column" or "alias.column". Join the nested relation and address it by its alias ` +
|
|
79
|
-
`with an alias-keyed type, e.g. type Joins = { someAlias: SomeEntity }; (j) => j.someAlias.column`);
|
|
81
|
+
throw (0, exports.createKeyTooDeepError)(keys);
|
|
80
82
|
}
|
|
81
83
|
return keyHolder;
|
|
82
84
|
};
|
|
83
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Keys of a typed callback. Paths deeper than `alias.column` throw, unless `allowDeepPaths` is set by a caller
|
|
87
|
+
* that checks them against the query's metadata (paths into JSON columns).
|
|
88
|
+
*/
|
|
89
|
+
const getKeyHolder = (callback, allowDeepPaths = false) => {
|
|
90
|
+
const check = allowDeepPaths ? (keyHolder) => keyHolder : assertKeyDepth;
|
|
84
91
|
if (typeof callback !== 'function') {
|
|
85
92
|
if (callback instanceof KeyHolder) {
|
|
86
|
-
return
|
|
93
|
+
return check(callback);
|
|
87
94
|
}
|
|
88
95
|
}
|
|
89
96
|
const keyHolder = callback(KeyProxy.create());
|
|
90
97
|
// fail loudly instead of silently dropping levels beyond `alias.column`
|
|
91
|
-
return Array.isArray(keyHolder) ? keyHolder.map(
|
|
98
|
+
return Array.isArray(keyHolder) ? keyHolder.map(check) : check(keyHolder);
|
|
92
99
|
};
|
|
93
100
|
exports.getKeyHolder = getKeyHolder;
|
|
94
101
|
const getKey = (callback) => {
|
|
@@ -102,26 +109,26 @@ exports.getKey = getKey;
|
|
|
102
109
|
* SQL key of a typed callback: `(u) => u.id` → `prefix.id`, `(u) => u.images.url` → `images.url`
|
|
103
110
|
* (an `alias.column` path keeps its own alias). Strings are returned as is.
|
|
104
111
|
*/
|
|
105
|
-
const getSqlKeyFromProxyCallback = (callback, addPrefix, useQuotes) => {
|
|
112
|
+
const getSqlKeyFromProxyCallback = (callback, addPrefix, useQuotes, driver) => {
|
|
106
113
|
if (typeof callback === 'string')
|
|
107
114
|
return callback;
|
|
108
115
|
const keyHolder = (0, exports.getKeyHolder)(callback);
|
|
109
116
|
if (Array.isArray(keyHolder)) {
|
|
110
|
-
return keyHolder.map((holder) => (0, exports.getSqlKeyFromProxyCallback)(holder, addPrefix, useQuotes));
|
|
117
|
+
return keyHolder.map((holder) => (0, exports.getSqlKeyFromProxyCallback)(holder, addPrefix, useQuotes, driver));
|
|
111
118
|
}
|
|
112
119
|
// if select is like this: (proxy) => proxy.relation.column
|
|
113
120
|
if (keyHolder.getKeys().length > 1) {
|
|
114
121
|
// return unchanged
|
|
115
|
-
return keyHolder.toSql(undefined, useQuotes);
|
|
122
|
+
return keyHolder.toSql(undefined, useQuotes, driver);
|
|
116
123
|
}
|
|
117
124
|
// else if select is one layer depth like this: (proxy) => proxy.column
|
|
118
125
|
// then add alias before the key; resolve the alias only now, a path above doesn't need it
|
|
119
126
|
const prefix = typeof addPrefix === 'function' ? addPrefix() : addPrefix;
|
|
120
127
|
if (typeof prefix === 'string' && prefix.length >= 1) {
|
|
121
128
|
// a new holder: a KeyHolder passed in by the caller must stay as it was
|
|
122
|
-
return new KeyHolder([prefix, ...keyHolder.getKeys()]).toSql(undefined, useQuotes);
|
|
129
|
+
return new KeyHolder([prefix, ...keyHolder.getKeys()]).toSql(undefined, useQuotes, driver);
|
|
123
130
|
}
|
|
124
|
-
return keyHolder.toSql(undefined, useQuotes);
|
|
131
|
+
return keyHolder.toSql(undefined, useQuotes, driver);
|
|
125
132
|
};
|
|
126
133
|
exports.getSqlKeyFromProxyCallback = getSqlKeyFromProxyCallback;
|
|
127
134
|
// example of typed select
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
|
|
2
|
+
import type { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
|
|
2
3
|
import type { KeyProxyCallback } from '../types/modules/proxy-callback.types';
|
|
4
|
+
import { JsonPathSqlOptions } from './json-path.utils';
|
|
3
5
|
/**
|
|
4
6
|
* Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
|
|
5
7
|
* a query alias (the main alias when `aliasName` is undefined). `undefined` when the alias has no
|
|
6
8
|
* entity or the property isn't a column.
|
|
7
9
|
*/
|
|
8
10
|
export declare function getColumnDatabaseName(builder: WhereExpressionBuilder, aliasName: string | undefined, propertyPath: string): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* The entity column a field names, the way `toFieldSql` resolves it: `alias.property` or
|
|
13
|
+
* `alias.column_name` on an alias with an entity, or a database column name of the main alias.
|
|
14
|
+
*/
|
|
15
|
+
export declare function findFieldColumn(builder: WhereExpressionBuilder, field: string): ColumnMetadata | undefined;
|
|
9
16
|
/**
|
|
10
17
|
* SQL for a field passed as a string to the untyped methods (`whereIsIn('user.first-name', …)`).
|
|
11
18
|
*
|
|
@@ -27,15 +34,18 @@ export declare function toFieldSql(builder: WhereExpressionBuilder, field: strin
|
|
|
27
34
|
*
|
|
28
35
|
* The property is mapped to its database column name (`@Column({ name })`, embedded columns)
|
|
29
36
|
* through the metadata of the alias it belongs to (see `resolveTypedKey`). Aliases without
|
|
30
|
-
* metadata (subqueries) and unknown properties keep the property name.
|
|
37
|
+
* metadata (subqueries) and unknown properties keep the property name. A path into a JSON column
|
|
38
|
+
* gives its JSON accessor, cast on PostgreSQL to the type of `jsonOptions.value`.
|
|
31
39
|
*/
|
|
32
|
-
export declare function getTypedColumnKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type
|
|
40
|
+
export declare function getTypedColumnKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type>, jsonOptions?: JsonPathSqlOptions): string | string[];
|
|
41
|
+
/** Whether a typed callback selects a path inside a JSON column, `u => u.metadata.tags`, rather than a column. */
|
|
42
|
+
export declare function isTypedJsonPath<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type>): boolean;
|
|
33
43
|
/**
|
|
34
44
|
* Quoted column of a typed callback that must select exactly one column
|
|
35
45
|
* (`whereIsAnyTyped`, `whereArrayContainsTyped`, …). Throws a `TypeError` for several columns:
|
|
36
46
|
* one condition per column has no single obvious meaning (AND? OR?), so don't guess.
|
|
37
47
|
*/
|
|
38
|
-
export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type>, methodName: string): string;
|
|
48
|
+
export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type>, methodName: string, jsonOptions?: JsonPathSqlOptions): string;
|
|
39
49
|
/**
|
|
40
50
|
* Key for typed select / order / distinct on a SelectQueryBuilder.
|
|
41
51
|
*
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.getTypedColumnKey = exports.toFieldSql = exports.getColumnDatabaseName = void 0;
|
|
3
|
+
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = void 0;
|
|
4
4
|
const proxy_key_utils_1 = require("./proxy-key.utils");
|
|
5
|
+
const json_path_utils_1 = require("./json-path.utils");
|
|
5
6
|
const sql_utils_1 = require("./sql.utils");
|
|
6
7
|
/**
|
|
7
8
|
* Whether single-level typed keys are qualified with the main alias (`user.firstName`).
|
|
@@ -53,6 +54,21 @@ function findColumn(metadata, name) {
|
|
|
53
54
|
var _a;
|
|
54
55
|
return ((_a = metadata.columns.find((column) => column.databaseName === name)) !== null && _a !== void 0 ? _a : metadata.findColumnWithPropertyPath(name));
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The entity column a field names, the way `toFieldSql` resolves it: `alias.property` or
|
|
59
|
+
* `alias.column_name` on an alias with an entity, or a database column name of the main alias.
|
|
60
|
+
*/
|
|
61
|
+
function findFieldColumn(builder, field) {
|
|
62
|
+
var _a;
|
|
63
|
+
const queryBuilder = builder;
|
|
64
|
+
const dot = field.indexOf('.');
|
|
65
|
+
if (dot > 0) {
|
|
66
|
+
const metadata = getAliasMetadata(queryBuilder, field.slice(0, dot));
|
|
67
|
+
return metadata && findColumn(metadata, field.slice(dot + 1));
|
|
68
|
+
}
|
|
69
|
+
return (_a = getAliasMetadata(queryBuilder, undefined)) === null || _a === void 0 ? void 0 : _a.columns.find((column) => column.databaseName === field);
|
|
70
|
+
}
|
|
71
|
+
exports.findFieldColumn = findFieldColumn;
|
|
56
72
|
/**
|
|
57
73
|
* SQL for a field passed as a string to the untyped methods (`whereIsIn('user.first-name', …)`).
|
|
58
74
|
*
|
|
@@ -94,12 +110,34 @@ function toFieldSql(builder, field) {
|
|
|
94
110
|
return (0, sql_utils_1.stringToSQLIdentifier)(field, (0, sql_utils_1.getDriverType)(builder));
|
|
95
111
|
}
|
|
96
112
|
exports.toFieldSql = toFieldSql;
|
|
113
|
+
/**
|
|
114
|
+
* The column that `keys` name in an entity: the longest prefix of the keys that is a column's property path
|
|
115
|
+
* (`address.city` for an embedded entity). The keys after it are a path inside the column, allowed only in a
|
|
116
|
+
* JSON column (`details` + `['meta', 'endTs']`). `undefined` when no prefix is a column, or a column that isn't
|
|
117
|
+
* JSON is followed by more keys (a relation's join column, `user` in `user.email`).
|
|
118
|
+
*/
|
|
119
|
+
function findColumnPath(metadata, keys) {
|
|
120
|
+
for (let length = keys.length; metadata && length >= 1; length--) {
|
|
121
|
+
const propertyPath = keys.slice(0, length).join('.');
|
|
122
|
+
const column = metadata.findColumnWithPropertyPath(propertyPath);
|
|
123
|
+
if (!column) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (length === keys.length) {
|
|
127
|
+
return { propertyPath, column };
|
|
128
|
+
}
|
|
129
|
+
return (0, json_path_utils_1.isJsonColumn)(column) ? { propertyPath, column, jsonPath: keys.slice(length) } : undefined;
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
97
133
|
/**
|
|
98
134
|
* Splits typed keys into the alias and the property path they refer to:
|
|
99
135
|
* - `t => t.status` → the query's main alias + `status` (no alias on UPDATE / DELETE);
|
|
100
136
|
* - `j => j.t.status`, `u => u.images.url` → the first key, when it is an alias of the query;
|
|
101
137
|
* - `p => p.address.city` → when the first key isn't an alias but the main entity has a column
|
|
102
138
|
* with that path (an embedded entity), the main alias + `address.city`;
|
|
139
|
+
* - `e => e.details.meta.endTs`, `j => j.e.details.outcome` → a JSON column of the alias' entity
|
|
140
|
+
* followed by the keys inside it (`jsonPath`), at any depth;
|
|
103
141
|
* - anything else → the first key as the alias (an alias the query builder doesn't know yet,
|
|
104
142
|
* e.g. `q => q.base.chatRoomId` before `.from(…, 'base')`).
|
|
105
143
|
*
|
|
@@ -108,6 +146,20 @@ exports.toFieldSql = toFieldSql;
|
|
|
108
146
|
* before `from()` the property name is used as the column name.
|
|
109
147
|
*/
|
|
110
148
|
function resolveTypedKey(queryBuilder, keys) {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
const resolved = resolveTypedKeyPath(queryBuilder, keys);
|
|
151
|
+
// deeper than `alias.column` only inside a JSON column: a nested relation can't be mapped to an alias
|
|
152
|
+
if (!resolved.jsonPath && keys.length > proxy_key_utils_1.MAX_KEY_DEPTH) {
|
|
153
|
+
throw (0, proxy_key_utils_1.createKeyTooDeepError)(keys);
|
|
154
|
+
}
|
|
155
|
+
const column = (_a = resolved.column) !== null && _a !== void 0 ? _a : (_b = resolved.metadata) === null || _b === void 0 ? void 0 : _b.findColumnWithPropertyPath(resolved.propertyPath);
|
|
156
|
+
// a @CalculatedColumn: TypeORM would write "alias"."property", a column the table doesn't have
|
|
157
|
+
if ((column === null || column === void 0 ? void 0 : column.isVirtualProperty) && !column.query) {
|
|
158
|
+
throw new TypeError(`${keys.join('.')} is a @CalculatedColumn, not a column of the table: use the SQL expression that fills it`);
|
|
159
|
+
}
|
|
160
|
+
return resolved;
|
|
161
|
+
}
|
|
162
|
+
function resolveTypedKeyPath(queryBuilder, keys) {
|
|
111
163
|
const prefixing = isAliasPrefixingEnabled(queryBuilder);
|
|
112
164
|
if (keys.length === 1) {
|
|
113
165
|
const aliasName = prefixing ? requireMainAliasName(queryBuilder, keys[0]) : undefined;
|
|
@@ -122,59 +174,80 @@ function resolveTypedKey(queryBuilder, keys) {
|
|
|
122
174
|
const mainAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.mainAlias;
|
|
123
175
|
// UPDATE / DELETE have no alias to qualify with: `u => u.user.firstName` is `"firstName"` there
|
|
124
176
|
if (!prefixing && (mainAlias === null || mainAlias === void 0 ? void 0 : mainAlias.name) === firstKey) {
|
|
177
|
+
const metadata = getAliasMetadata(queryBuilder, undefined);
|
|
125
178
|
return {
|
|
126
179
|
aliasName: undefined,
|
|
127
180
|
propertyPath: restKeys.join('.'),
|
|
128
|
-
metadata
|
|
181
|
+
metadata,
|
|
182
|
+
...findColumnPath(metadata, restKeys),
|
|
129
183
|
};
|
|
130
184
|
}
|
|
131
185
|
const queryAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.aliases.find((alias) => alias.name === firstKey);
|
|
132
186
|
if (!queryAlias && mainAlias) {
|
|
133
187
|
const mainMetadata = getAliasMetadata(queryBuilder, undefined);
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
188
|
+
const columnPath = findColumnPath(mainMetadata, keys);
|
|
189
|
+
if (columnPath) {
|
|
136
190
|
return {
|
|
137
191
|
aliasName: prefixing ? mainAlias.name : undefined,
|
|
138
|
-
propertyPath: embeddedPath,
|
|
139
192
|
metadata: mainMetadata,
|
|
193
|
+
...columnPath,
|
|
140
194
|
};
|
|
141
195
|
}
|
|
142
196
|
}
|
|
197
|
+
const metadata = (queryAlias === null || queryAlias === void 0 ? void 0 : queryAlias.hasMetadata) ? queryAlias.metadata : undefined;
|
|
143
198
|
return {
|
|
144
199
|
aliasName: firstKey,
|
|
145
200
|
propertyPath: restKeys.join('.'),
|
|
146
|
-
metadata
|
|
201
|
+
metadata,
|
|
202
|
+
...findColumnPath(metadata, restKeys),
|
|
147
203
|
};
|
|
148
204
|
}
|
|
149
|
-
/**
|
|
150
|
-
|
|
205
|
+
/**
|
|
206
|
+
* `"alias"."column"` (or `"column"` without an alias) with the database column name, followed by the path
|
|
207
|
+
* inside a JSON column: `"exec"."details" -> 'meta' ->> 'endTs'` (see `toJsonPathSql`).
|
|
208
|
+
*/
|
|
209
|
+
function toQuotedColumnKey(queryBuilder, { aliasName, propertyPath, metadata, column, jsonPath }, jsonOptions) {
|
|
151
210
|
var _a, _b;
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
? queryBuilder.escape(
|
|
155
|
-
: `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(
|
|
211
|
+
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;
|
|
212
|
+
const columnSql = aliasName === undefined
|
|
213
|
+
? queryBuilder.escape(columnName)
|
|
214
|
+
: `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
|
|
215
|
+
if (!column || !jsonPath) {
|
|
216
|
+
return columnSql;
|
|
217
|
+
}
|
|
218
|
+
const driver = (0, sql_utils_1.getDriverType)(queryBuilder);
|
|
219
|
+
return (0, json_path_utils_1.toJsonPathSql)(driver, columnSql, column, jsonPath, jsonOptions);
|
|
156
220
|
}
|
|
157
221
|
/**
|
|
158
222
|
* SQL key for a typed callback on `builder`: `"alias"."column"`, or `"column"` on UPDATE / DELETE.
|
|
159
223
|
*
|
|
160
224
|
* The property is mapped to its database column name (`@Column({ name })`, embedded columns)
|
|
161
225
|
* through the metadata of the alias it belongs to (see `resolveTypedKey`). Aliases without
|
|
162
|
-
* metadata (subqueries) and unknown properties keep the property name.
|
|
226
|
+
* metadata (subqueries) and unknown properties keep the property name. A path into a JSON column
|
|
227
|
+
* gives its JSON accessor, cast on PostgreSQL to the type of `jsonOptions.value`.
|
|
163
228
|
*/
|
|
164
|
-
function getTypedColumnKey(builder, callback) {
|
|
229
|
+
function getTypedColumnKey(builder, callback, jsonOptions) {
|
|
165
230
|
const queryBuilder = builder;
|
|
166
|
-
const toKey = (keyHolder) => toQuotedColumnKey(queryBuilder, resolveTypedKey(queryBuilder, keyHolder.getKeys()));
|
|
167
|
-
|
|
231
|
+
const toKey = (keyHolder) => toQuotedColumnKey(queryBuilder, resolveTypedKey(queryBuilder, keyHolder.getKeys()), jsonOptions);
|
|
232
|
+
// the depth is checked against the query's metadata, where a JSON column may be deeper
|
|
233
|
+
const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(callback, true);
|
|
168
234
|
return Array.isArray(keyHolder) ? keyHolder.map(toKey) : toKey(keyHolder);
|
|
169
235
|
}
|
|
170
236
|
exports.getTypedColumnKey = getTypedColumnKey;
|
|
237
|
+
/** Whether a typed callback selects a path inside a JSON column, `u => u.metadata.tags`, rather than a column. */
|
|
238
|
+
function isTypedJsonPath(builder, callback) {
|
|
239
|
+
const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(callback, true);
|
|
240
|
+
return (!Array.isArray(keyHolder) &&
|
|
241
|
+
Boolean(resolveTypedKey(builder, keyHolder.getKeys()).jsonPath));
|
|
242
|
+
}
|
|
243
|
+
exports.isTypedJsonPath = isTypedJsonPath;
|
|
171
244
|
/**
|
|
172
245
|
* Quoted column of a typed callback that must select exactly one column
|
|
173
246
|
* (`whereIsAnyTyped`, `whereArrayContainsTyped`, …). Throws a `TypeError` for several columns:
|
|
174
247
|
* one condition per column has no single obvious meaning (AND? OR?), so don't guess.
|
|
175
248
|
*/
|
|
176
|
-
function getTypedSingleColumnKey(builder, callback, methodName) {
|
|
177
|
-
const key = getTypedColumnKey(builder, callback);
|
|
249
|
+
function getTypedSingleColumnKey(builder, callback, methodName, jsonOptions) {
|
|
250
|
+
const key = getTypedColumnKey(builder, callback, jsonOptions);
|
|
178
251
|
if (Array.isArray(key)) {
|
|
179
252
|
throw new TypeError(`${methodName} expects the callback to select a single column, got ${key.length}: ${key.join(', ')}`);
|
|
180
253
|
}
|
|
@@ -195,6 +268,10 @@ function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
|
195
268
|
const queryBuilder = builder;
|
|
196
269
|
const toKey = (keyHolder) => {
|
|
197
270
|
const resolved = resolveTypedKey(queryBuilder, keyHolder.getKeys());
|
|
271
|
+
if (resolved.jsonPath) {
|
|
272
|
+
throw new TypeError(`Typed key "${keyHolder.getKeys().join('.')}" points into the JSON column "${resolved.propertyPath}": ` +
|
|
273
|
+
`select, order, distinct and group by take columns only. For an expression use tableHelper().jsonColumn()`);
|
|
274
|
+
}
|
|
198
275
|
if (useQuotes) {
|
|
199
276
|
return toQuotedColumnKey(queryBuilder, resolved);
|
|
200
277
|
}
|
|
@@ -3,6 +3,8 @@ export declare const wrapWithDoubleQoutes: (str: string) => string;
|
|
|
3
3
|
export declare const wrapWithBackQoutes: (str: string) => string;
|
|
4
4
|
/** MySQL, MariaDB and Aurora MySQL: backtick identifiers, `<=>` null-safe comparison. */
|
|
5
5
|
export declare const isMysqlLikeDriver: (driver?: DatabaseType) => boolean;
|
|
6
|
+
/** PostgreSQL and the databases speaking its dialect. */
|
|
7
|
+
export declare const isPostgresLikeDriver: (driver?: DatabaseType) => boolean;
|
|
6
8
|
/** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
|
|
7
9
|
export declare const isSqliteLikeDriver: (driver?: DatabaseType) => boolean;
|
|
8
10
|
/** Database type of the query builder's connection, e.g. `'postgres'`; undefined without one. */
|
package/dist/utils/sql.utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.assertArrayType = exports.createUniqueParameterName = exports.stringToSQLValue = exports.stringToSQLIdentifier = exports.quoteIdentifierPath = exports.quoteIdentifier = exports.getDriverType = exports.isSqliteLikeDriver = exports.isMysqlLikeDriver = 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;
|
|
@@ -29,6 +29,10 @@ const SQLITE_LIKE_DRIVERS = [
|
|
|
29
29
|
'nativescript',
|
|
30
30
|
'react-native',
|
|
31
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;
|
|
32
36
|
/** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
|
|
33
37
|
const isSqliteLikeDriver = (driver) => driver !== undefined && SQLITE_LIKE_DRIVERS.includes(driver);
|
|
34
38
|
exports.isSqliteLikeDriver = isSqliteLikeDriver;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { ObjectLiteral } from "typeorm";
|
|
1
|
+
import { DatabaseType, ObjectLiteral } from "typeorm";
|
|
2
2
|
export type TableHelperOptions = {
|
|
3
3
|
useQuotes?: boolean;
|
|
4
|
+
/** The database the SQL is for: MySQL and MariaDB quote identifiers with backticks. Double quotes without it. */
|
|
5
|
+
driver?: DatabaseType;
|
|
4
6
|
};
|
|
5
7
|
type JsonFieldTuple<T, Depth extends 0[] = []> = Depth['length'] extends 4 ? [string, ...string[]] : {
|
|
6
8
|
[K in keyof T & string]: [NonNullable<T[K]>] extends [Record<string, unknown>] ? [K, ...JsonFieldTuple<NonNullable<T[K]>, [...Depth, 0]>] | [K] : [K];
|
|
@@ -17,6 +19,60 @@ export type TableHelperCountOptions = {
|
|
|
17
19
|
*/
|
|
18
20
|
syntax?: 'case' | 'filter';
|
|
19
21
|
};
|
|
22
|
+
export type TableHelperJsonOptions = {
|
|
23
|
+
/** `jsonb_build_object` / `jsonb_agg` instead of the `json` functions. PostgreSQL only. */
|
|
24
|
+
jsonb?: boolean;
|
|
25
|
+
};
|
|
26
|
+
type ColumnName<T> = keyof T & string;
|
|
27
|
+
export type TableHelperJsonAggOptions<T> = TableHelperJsonOptions & {
|
|
28
|
+
/** Orders the array by a column of the helper's table, ascending or in the given direction. Not on MySQL. */
|
|
29
|
+
orderBy?: AggregateOrder<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Aggregates only the rows that match, e.g. `images.isNotNull('id')`: after a LEFT JOIN without a match
|
|
32
|
+
* the row has `NULL` columns, which would otherwise come out as `[{"id": null}]`. Not on MySQL.
|
|
33
|
+
*/
|
|
34
|
+
filter?: string;
|
|
35
|
+
};
|
|
36
|
+
/** Keys of the entity whose values are numbers. */
|
|
37
|
+
type NumericColumnName<T> = {
|
|
38
|
+
[K in keyof T & string]: [NonNullable<T[K]>] extends [number | bigint] ? K : never;
|
|
39
|
+
}[keyof T & string];
|
|
40
|
+
type AggregateOrder<T> = ColumnName<T> | readonly [ColumnName<T>, 'ASC' | 'DESC'];
|
|
41
|
+
export type TableHelperSumOptions = {
|
|
42
|
+
/** Sums only the rows that match this condition, e.g. `t.where('status', "= 'paid'")`. */
|
|
43
|
+
filter?: string;
|
|
44
|
+
/** How `filter` is written, as for `count`: `'case'` (default) or `'filter'` (PostgreSQL and SQLite 3.30+). */
|
|
45
|
+
syntax?: 'case' | 'filter';
|
|
46
|
+
};
|
|
47
|
+
export type TableHelperArrayAggOptions<T> = {
|
|
48
|
+
/** `array_agg(DISTINCT column)`: each value once. Then `orderBy` can only be the aggregated column. */
|
|
49
|
+
distinct?: boolean;
|
|
50
|
+
/** Orders the array by a column of the helper's table, ascending or in the given direction. */
|
|
51
|
+
orderBy?: AggregateOrder<T>;
|
|
52
|
+
/** Aggregates only the rows that match, e.g. `images.isNotNull('id')` after a LEFT JOIN without a match. */
|
|
53
|
+
filter?: string;
|
|
54
|
+
};
|
|
55
|
+
/** A column of the helper's table whose property is a `Date`. */
|
|
56
|
+
type DateColumnName<T> = {
|
|
57
|
+
[K in keyof T & string]: [NonNullable<T[K]>] extends [Date] ? K : never;
|
|
58
|
+
}[keyof T & string];
|
|
59
|
+
/** A date column of the helper's table, or an SQL expression: `{ sql: 'MAX("order"."paidAt")' }`. */
|
|
60
|
+
export type TableHelperExpression<T> = DateColumnName<T> | {
|
|
61
|
+
sql: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A time zone: a name or an ISO 8601 offset (`'Europe/Kyiv'`, `'UTC'`, `'+02:00'` = UTC+2), written into the SQL as an escaped string
|
|
65
|
+
* literal; a column of the helper's table, `{ column: 'timezone' }`, with an optional `fallback` zone for `NULL`;
|
|
66
|
+
* or an SQL expression, `{ sql: services.column('timezone') }`.
|
|
67
|
+
*/
|
|
68
|
+
export type TableHelperTimeZone<T> = string | {
|
|
69
|
+
column: ColumnName<T>;
|
|
70
|
+
fallback?: string;
|
|
71
|
+
} | {
|
|
72
|
+
sql: string;
|
|
73
|
+
};
|
|
74
|
+
/** The units of PostgreSQL's `date_trunc`. */
|
|
75
|
+
export type DateTruncUnit = 'microseconds' | 'milliseconds' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'decade' | 'century' | 'millennium';
|
|
20
76
|
export type JsonFieldRest<V> = [NonNullable<V>] extends [Record<string, unknown>] ? JsonFieldTuple<NonNullable<V>> : string[];
|
|
21
77
|
/**
|
|
22
78
|
* Creates a type-safe SQL helper for building column references, WHERE expressions,
|
|
@@ -51,7 +107,8 @@ export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, opti
|
|
|
51
107
|
/** Returns `"alias"."column" IS NOT NULL`. */
|
|
52
108
|
isNotNull(columnName: keyof T): string;
|
|
53
109
|
/**
|
|
54
|
-
* Returns `CAST(COUNT("alias"."column") AS integer)`: the rows where the column isn't `NULL`.
|
|
110
|
+
* Returns `CAST(COUNT("alias"."column") AS integer)`: the rows where the column isn't `NULL`. With the `driver` option
|
|
111
|
+
* MySQL casts `AS SIGNED`, a `BIGINT`, which its TypeORM driver returns as a string by default.
|
|
55
112
|
* Without a column it is `COUNT(*)`, all the rows of the result, in every database. To count the rows
|
|
56
113
|
* of a table joined with LEFT JOIN, count one of its non-null columns: `images.count('id')`.
|
|
57
114
|
* @example
|
|
@@ -62,6 +119,41 @@ export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, opti
|
|
|
62
119
|
* // CAST(COUNT(*) FILTER (WHERE "user"."status" = 'active') AS integer)
|
|
63
120
|
*/
|
|
64
121
|
count(columnName?: keyof T | undefined, countOptions?: TableHelperCountOptions): string;
|
|
122
|
+
/**
|
|
123
|
+
* Returns `COALESCE(SUM("alias"."column"), 0)`: 0, not `NULL`, for a group without rows or a LEFT JOIN
|
|
124
|
+
* without a match. Only a column whose property is a number. PostgreSQL sums integers into a `bigint`
|
|
125
|
+
* and MySQL into a `DECIMAL`, which the drivers return as strings.
|
|
126
|
+
* @example
|
|
127
|
+
* t.sum('amountCents', { filter: t.where('status', "= 'paid'") })
|
|
128
|
+
* // COALESCE(SUM(CASE WHEN "order"."status" = 'paid' THEN "order"."amountCents" END), 0)
|
|
129
|
+
*/
|
|
130
|
+
sum(columnName: NumericColumnName<T>, sumOptions?: TableHelperSumOptions): string;
|
|
131
|
+
/**
|
|
132
|
+
* Returns the PostgreSQL array of a column over the rows of a group, `{}` for a group without rows.
|
|
133
|
+
* Other databases have no arrays and throw a `TypeError`: use `jsonAgg` there.
|
|
134
|
+
* @example
|
|
135
|
+
* images.arrayAgg('id', { distinct: true, orderBy: ['id', 'DESC'], filter: images.isNotNull('id') })
|
|
136
|
+
* // COALESCE(array_agg(DISTINCT "image"."id" ORDER BY "image"."id" DESC) FILTER (WHERE "image"."id" IS NOT NULL), '{}')
|
|
137
|
+
*/
|
|
138
|
+
arrayAgg(columnName: ColumnName<T>, aggOptions?: TableHelperArrayAggOptions<T>): string;
|
|
139
|
+
/**
|
|
140
|
+
* Returns a JSON object of columns, keyed by the column names or by the keys of a `{ key: column }` map.
|
|
141
|
+
* Every column is checked against the entity type.
|
|
142
|
+
* @example
|
|
143
|
+
* t.jsonBuildObject(['id', 'url']) // json_build_object('id', "image"."id", 'url', "image"."url")
|
|
144
|
+
* t.jsonBuildObject({ imageId: 'id', src: 'url' }) // json_build_object('imageId', "image"."id", 'src', "image"."url")
|
|
145
|
+
* // MySQL: JSON_OBJECT(…), SQLite: json_object(…); { jsonb: true }: jsonb_build_object(…)
|
|
146
|
+
*/
|
|
147
|
+
jsonBuildObject(columns: readonly ColumnName<T>[] | Readonly<Record<string, ColumnName<T>>>, jsonOptions?: TableHelperJsonOptions): string;
|
|
148
|
+
/**
|
|
149
|
+
* Returns the JSON array of an expression over the rows of a group, `[]` for a group without rows
|
|
150
|
+
* (the aggregate alone gives `NULL` there). Use it with GROUP BY, or in a subquery.
|
|
151
|
+
* @example
|
|
152
|
+
* images.jsonAgg(images.jsonBuildObject(['id', 'url']), { orderBy: ['id', 'DESC'], filter: images.isNotNull('id') })
|
|
153
|
+
* // COALESCE(json_agg(json_build_object(…) ORDER BY "image"."id" DESC) FILTER (WHERE "image"."id" IS NOT NULL), CAST('[]' AS json))
|
|
154
|
+
* // MySQL: COALESCE(JSON_ARRAYAGG(…), JSON_ARRAY()), without ORDER BY and FILTER; SQLite: json_group_array(…)
|
|
155
|
+
*/
|
|
156
|
+
jsonAgg(expression: string, aggOptions?: TableHelperJsonAggOptions<T>): string;
|
|
65
157
|
/** Returns `CAST(expression AS type)`. */
|
|
66
158
|
cast(expression: string, type: string): string;
|
|
67
159
|
/**
|
|
@@ -82,5 +174,23 @@ export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, opti
|
|
|
82
174
|
* qb.whereTyped(u => u.metadata, `${t.jsonPath('metadata', '->>', 'role')} =`, 'admin')
|
|
83
175
|
*/
|
|
84
176
|
jsonPath<K_1 extends keyof T>(_columnName: K_1, jsonOperator: '->>' | '->', ...fieldNames: JsonFieldRest<T[K_1]>): string;
|
|
177
|
+
/**
|
|
178
|
+
* Returns `(expression AT TIME ZONE zone)` (PostgreSQL). A `timestamptz` gives the wall-clock `timestamp`
|
|
179
|
+
* in that zone; a `timestamp` is read as wall-clock time there and gives a `timestamptz`.
|
|
180
|
+
* A zone name is written as an escaped literal, so it is safe to take from a request.
|
|
181
|
+
* @example
|
|
182
|
+
* t.atTimeZone('paidAt', 'Europe/Kyiv') // ("order"."paidAt" AT TIME ZONE 'Europe/Kyiv')
|
|
183
|
+
* t.atTimeZone('startsAt', { column: 'timezone', fallback: 'UTC' }) // ("order"."startsAt" AT TIME ZONE COALESCE("order"."timezone", 'UTC'))
|
|
184
|
+
*/
|
|
185
|
+
atTimeZone(value: TableHelperExpression<T>, timeZone: TableHelperTimeZone<T>): string;
|
|
186
|
+
/**
|
|
187
|
+
* Returns `date_trunc('unit', expression)` (PostgreSQL). With a time zone it is the three-argument form
|
|
188
|
+
* (PostgreSQL 12+): a `timestamptz` is truncated at the boundaries of that zone, e.g. its local midnight
|
|
189
|
+
* for `'day'`, and the result is a `timestamptz` again.
|
|
190
|
+
* @example
|
|
191
|
+
* t.dateTrunc('day', 'paidAt') // date_trunc('day', "order"."paidAt")
|
|
192
|
+
* t.dateTrunc('day', 'paidAt', 'Europe/Kyiv') // date_trunc('day', "order"."paidAt", 'Europe/Kyiv')
|
|
193
|
+
*/
|
|
194
|
+
dateTrunc(unit: DateTruncUnit, value: TableHelperExpression<T>, timeZone?: TableHelperTimeZone<T> | undefined): string;
|
|
85
195
|
};
|
|
86
196
|
export {};
|