typeorm-extensions 2.2.0-rc.2 → 2.2.0-rc.4
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/dist/extensions/get-many-with-totals.extension.js +7 -3
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +1 -1
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +6 -5
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +1 -1
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +2 -2
- package/dist/extensions/typed/typed-select/typed-select.extension.js +2 -2
- package/dist/types/extensions/typed-group-by.types.d.ts +2 -2
- package/dist/utils/query-builder.utils.d.ts +11 -3
- package/dist/utils/query-builder.utils.js +89 -5
- package/dist/utils/where.utils.js +6 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ const SelectQueryBuilder_1 = require("typeorm/query-builder/SelectQueryBuilder")
|
|
|
5
5
|
const pagination_utils_1 = require("../utils/pagination.utils");
|
|
6
6
|
const constants_1 = require("../constants");
|
|
7
7
|
const prototype_utils_1 = require("../utils/prototype.utils");
|
|
8
|
+
const query_builder_utils_1 = require("../utils/query-builder.utils");
|
|
8
9
|
// patching
|
|
9
10
|
const extension = {
|
|
10
11
|
prototype: {
|
|
@@ -111,9 +112,12 @@ async function readWithTotals(builder, paginationFilter, options, read) {
|
|
|
111
112
|
}
|
|
112
113
|
// allOrderBys, not orderBys: it is what the SQL is built from, so without an explicit order it
|
|
113
114
|
// falls back to the `orderBy` of @Entity, and respects the `disable-global-order` option
|
|
114
|
-
const orderBy = Object.entries((_a = builder.expressionMap.allOrderBys) !== null && _a !== void 0 ? _a : {}).map(([
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
const orderBy = Object.entries((_a = builder.expressionMap.allOrderBys) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => {
|
|
116
|
+
const selector = (0, query_builder_utils_1.resolveDeferredKeys)(builder, key);
|
|
117
|
+
return typeof value === 'string'
|
|
118
|
+
? { selector, order: value }
|
|
119
|
+
: { selector, order: value.order, nulls: value.nulls };
|
|
120
|
+
});
|
|
117
121
|
return {
|
|
118
122
|
list,
|
|
119
123
|
total,
|
|
@@ -5,7 +5,7 @@ const query_builder_utils_1 = require("../../../utils/query-builder.utils");
|
|
|
5
5
|
// patching and implementation
|
|
6
6
|
typeorm_1.SelectQueryBuilder.prototype.distinctOnTyped = function (...distinctOn) {
|
|
7
7
|
if (Array.isArray(distinctOn) && distinctOn.length) {
|
|
8
|
-
const keys = distinctOn.map((callback) => (0, query_builder_utils_1.getTypedSelectionKey)(this, callback)).flat();
|
|
8
|
+
const keys = distinctOn.map((callback) => (0, query_builder_utils_1.getTypedSelectionKey)(this, callback, undefined, 'distinctOnTyped')).flat();
|
|
9
9
|
return this.distinctOn(keys);
|
|
10
10
|
}
|
|
11
11
|
return this;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ObjectLiteral } from 'typeorm';
|
|
2
|
-
import type {
|
|
2
|
+
import type { KeyProxyCallback } from '../../../types/modules/proxy-callback.types';
|
|
3
3
|
import type { TypedGroupByOptions } from '../../../types/extensions/typed-group-by.types';
|
|
4
4
|
import type { TypedRoot } from '../../../types/modules/typed-root.types';
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* One column, `user => user.id`, or several, `user => [user.id, user.email]`. A JSON column groups as a whole;
|
|
7
|
+
* a relation without a join column in its table, or an embedded entity, throws a `TypeError`.
|
|
8
|
+
*/
|
|
9
|
+
type GroupBySelection<Type extends ObjectLiteral> = KeyProxyCallback<Type>;
|
|
9
10
|
export interface TypedGroupByMethods<Entity extends ObjectLiteral> {
|
|
10
11
|
/**
|
|
11
12
|
* Sets GROUP BY to the columns the callback selects: `user => user.id`, or several at once,
|
|
@@ -23,7 +23,7 @@ const extension = {
|
|
|
23
23
|
(0, prototype_utils_1.patchPrototype)(typeorm_1.SelectQueryBuilder, extension);
|
|
24
24
|
// implementation
|
|
25
25
|
function toGroupByKeys(builder, selection, options) {
|
|
26
|
-
const keys = (0, query_builder_utils_1.getTypedSelectionKey)(builder, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
26
|
+
const keys = (0, query_builder_utils_1.getTypedSelectionKey)(builder, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'groupByTyped');
|
|
27
27
|
return Array.isArray(keys) ? keys : [keys];
|
|
28
28
|
}
|
|
29
29
|
function addGroupByKeys(builder, keys) {
|
|
@@ -10,7 +10,7 @@ typeorm_1.SelectQueryBuilder.prototype.orderByTyped = function (selection, order
|
|
|
10
10
|
return this.orderBy(selection.toSql(undefined, options === null || options === void 0 ? void 0 : options.useQuotes, (0, sql_utils_1.getDriverType)(this)), order, nulls);
|
|
11
11
|
}
|
|
12
12
|
if (typeof selection === 'function') {
|
|
13
|
-
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
13
|
+
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'orderByTyped');
|
|
14
14
|
return this.orderBy(key, order, nulls);
|
|
15
15
|
}
|
|
16
16
|
return this;
|
|
@@ -20,7 +20,7 @@ typeorm_1.SelectQueryBuilder.prototype.addOrderByTyped = function (selection, or
|
|
|
20
20
|
return this.addOrderBy(selection.toSql(undefined, options === null || options === void 0 ? void 0 : options.useQuotes, (0, sql_utils_1.getDriverType)(this)), order, nulls);
|
|
21
21
|
}
|
|
22
22
|
if (typeof selection === 'function') {
|
|
23
|
-
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
23
|
+
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'addOrderByTyped');
|
|
24
24
|
return this.addOrderBy(key, order, nulls);
|
|
25
25
|
}
|
|
26
26
|
return this;
|
|
@@ -29,11 +29,11 @@ const attachSelectMethod = (query, method, selection, selectionAliasName, useQuo
|
|
|
29
29
|
if (Array.isArray(selection)) {
|
|
30
30
|
const arrayOfKeys = selection.flatMap((select) => select instanceof proxy_key_utils_1.KeyHolder
|
|
31
31
|
? (0, proxy_key_utils_1.getSqlKeyFromProxyCallback)(select, () => query.alias, useQuotes, (0, sql_utils_1.getDriverType)(query))
|
|
32
|
-
: (0, query_builder_utils_1.getTypedSelectionKey)(query, select, useQuotes));
|
|
32
|
+
: (0, query_builder_utils_1.getTypedSelectionKey)(query, select, useQuotes, `${method}Typed`));
|
|
33
33
|
return query[method](arrayOfKeys);
|
|
34
34
|
}
|
|
35
35
|
else if (typeof selection === 'function') {
|
|
36
|
-
const keyArrayOrSingleKey = (0, query_builder_utils_1.getTypedSelectionKey)(query, selection, useQuotes);
|
|
36
|
+
const keyArrayOrSingleKey = (0, query_builder_utils_1.getTypedSelectionKey)(query, selection, useQuotes, `${method}Typed`);
|
|
37
37
|
if (Array.isArray(keyArrayOrSingleKey)) {
|
|
38
38
|
return query[method](keyArrayOrSingleKey);
|
|
39
39
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export type TypedGroupByOptions = {
|
|
2
2
|
/**
|
|
3
3
|
* Group by the quoted database column (`"ticket"."ticket_status"`) instead of `ticket.status`,
|
|
4
|
-
* which TypeORM maps to the column itself.
|
|
5
|
-
*
|
|
4
|
+
* which TypeORM maps to the column itself. Keys of an alias without metadata (a subquery in FROM)
|
|
5
|
+
* are quoted unless this is `false`: PostgreSQL folds an unquoted `sub.firstName` to `firstname`.
|
|
6
6
|
*/
|
|
7
7
|
useQuotes?: boolean;
|
|
8
8
|
};
|
|
@@ -2,6 +2,11 @@ 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
|
+
*/
|
|
9
|
+
export declare function resolveDeferredKeys(builder: WhereExpressionBuilder, statement: string): string;
|
|
5
10
|
/**
|
|
6
11
|
* Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
|
|
7
12
|
* a query alias (the main alias when `aliasName` is undefined). `undefined` when the alias has no
|
|
@@ -47,10 +52,13 @@ export declare function isTypedJsonPath<Type extends ObjectLiteral>(builder: Whe
|
|
|
47
52
|
*/
|
|
48
53
|
export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, callback: KeyProxyCallback<Type>, methodName: string, jsonOptions?: JsonPathSqlOptions): string;
|
|
49
54
|
/**
|
|
50
|
-
* Key for typed select / order / distinct on a SelectQueryBuilder.
|
|
55
|
+
* Key for typed select / order / distinct / group by on a SelectQueryBuilder.
|
|
51
56
|
*
|
|
52
57
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
53
58
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
54
|
-
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys.
|
|
59
|
+
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
60
|
+
* metadata (a subquery or a CTE in FROM or a join) is quoted unless `useQuotes` is `false`:
|
|
61
|
+
* TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
62
|
+
* Strings are kept as is.
|
|
55
63
|
*/
|
|
56
|
-
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean): string | string[];
|
|
64
|
+
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean, columnsFor?: string): string | string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = void 0;
|
|
3
|
+
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = exports.resolveDeferredKeys = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
4
5
|
const proxy_key_utils_1 = require("./proxy-key.utils");
|
|
5
6
|
const json_path_utils_1 = require("./json-path.utils");
|
|
6
7
|
const sql_utils_1 = require("./sql.utils");
|
|
@@ -47,6 +48,55 @@ function findQueryAlias(queryBuilder, aliasName) {
|
|
|
47
48
|
}
|
|
48
49
|
return undefined;
|
|
49
50
|
}
|
|
51
|
+
// private-use characters: no identifier or SQL that TypeORM builds contains them
|
|
52
|
+
const DEFERRED_KEY_START = '\uE000';
|
|
53
|
+
const DEFERRED_PROPERTY = '\uE001';
|
|
54
|
+
const DEFERRED_FIELD = '\uE002';
|
|
55
|
+
const DEFERRED_KEY_END = '\uE003';
|
|
56
|
+
const DEFERRED_KEY = /\uE000([^\uE000-\uE003]+)([\uE001\uE002])([^\uE000-\uE003]+)\uE003/g;
|
|
57
|
+
const TWO_PART_FIELD = /^([A-Za-z_][\w$]*)\.([A-Za-z_][\w$]*)$/;
|
|
58
|
+
/**
|
|
59
|
+
* Placeholder for a column of an alias the query doesn't have yet, e.g. a join added after the call. TypeORM maps
|
|
60
|
+
* property names when it builds the SQL, and so does `resolveDeferredKeys` with these, once the alias is known.
|
|
61
|
+
*/
|
|
62
|
+
function deferColumnKey(aliasName, separator, name) {
|
|
63
|
+
return `${DEFERRED_KEY_START}${aliasName}${separator}${name}${DEFERRED_KEY_END}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Replaces placeholders of `deferColumnKey` in SQL built by `builder`: the database column through the entity of
|
|
67
|
+
* the alias, quoted, `"usr"."receipts_email"`. An alias still unknown or without an entity keeps the name.
|
|
68
|
+
*/
|
|
69
|
+
function resolveDeferredKeys(builder, statement) {
|
|
70
|
+
if (typeof statement !== 'string' || !statement.includes(DEFERRED_KEY_START)) {
|
|
71
|
+
return statement;
|
|
72
|
+
}
|
|
73
|
+
const queryBuilder = builder;
|
|
74
|
+
return statement.replace(DEFERRED_KEY, (_match, aliasName, separator, name) => {
|
|
75
|
+
const metadata = getAliasMetadata(queryBuilder, aliasName);
|
|
76
|
+
const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
|
|
77
|
+
const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
|
|
78
|
+
return `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
exports.resolveDeferredKeys = resolveDeferredKeys;
|
|
82
|
+
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
|
+
function installDeferredKeyResolution() {
|
|
85
|
+
const prototype = typeorm_1.QueryBuilder.prototype;
|
|
86
|
+
if (prototype[DEFERRED_KEYS_INSTALLED]) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
prototype[DEFERRED_KEYS_INSTALLED] = true;
|
|
90
|
+
for (const method of ['replacePropertyNames', 'replacePropertyNamesForTheWholeQuery']) {
|
|
91
|
+
const original = prototype[method];
|
|
92
|
+
if (typeof original === 'function') {
|
|
93
|
+
prototype[method] = function (statement) {
|
|
94
|
+
return resolveDeferredKeys(this, original.call(this, statement));
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
installDeferredKeyResolution();
|
|
50
100
|
/** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
|
|
51
101
|
function getAliasMetadata(queryBuilder, aliasName) {
|
|
52
102
|
var _a;
|
|
@@ -112,6 +162,10 @@ function toFieldSql(builder, field) {
|
|
|
112
162
|
const columnSql = queryBuilder.escape(column.databaseName);
|
|
113
163
|
return isAliasPrefixingEnabled(queryBuilder) ? `${queryBuilder.escape(aliasName)}.${columnSql}` : columnSql;
|
|
114
164
|
}
|
|
165
|
+
const parts = TWO_PART_FIELD.exec(field);
|
|
166
|
+
if (parts && !findQueryAlias(queryBuilder, aliasName)) {
|
|
167
|
+
return deferColumnKey(parts[1], DEFERRED_FIELD, parts[2]);
|
|
168
|
+
}
|
|
115
169
|
}
|
|
116
170
|
else {
|
|
117
171
|
// only by database name: an unqualified property name may mean another table's column
|
|
@@ -222,6 +276,9 @@ function resolveTypedKeyPath(queryBuilder, keys) {
|
|
|
222
276
|
*/
|
|
223
277
|
function toQuotedColumnKey(queryBuilder, { aliasName, propertyPath, metadata, column, jsonPath }, jsonOptions) {
|
|
224
278
|
var _a, _b;
|
|
279
|
+
if (aliasName !== undefined && !metadata && !findQueryAlias(queryBuilder, aliasName)) {
|
|
280
|
+
return deferColumnKey(aliasName, DEFERRED_PROPERTY, propertyPath);
|
|
281
|
+
}
|
|
225
282
|
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;
|
|
226
283
|
const columnSql = aliasName === undefined
|
|
227
284
|
? queryBuilder.escape(columnName)
|
|
@@ -269,13 +326,37 @@ function getTypedSingleColumnKey(builder, callback, methodName, jsonOptions) {
|
|
|
269
326
|
}
|
|
270
327
|
exports.getTypedSingleColumnKey = getTypedSingleColumnKey;
|
|
271
328
|
/**
|
|
272
|
-
*
|
|
329
|
+
* Throws for a key that names no column of its table: a relation whose join column is on the other side
|
|
330
|
+
* (one-to-many, many-to-many, the inverse side of a one-to-one) or an embedded entity. TypeORM leaves such a key
|
|
331
|
+
* as written, and the database rejects it.
|
|
332
|
+
*/
|
|
333
|
+
function assertTableColumnKey({ metadata, propertyPath }, keys, methodName) {
|
|
334
|
+
const relation = metadata === null || metadata === void 0 ? void 0 : metadata.findRelationWithPropertyPath(propertyPath);
|
|
335
|
+
if (relation && !relation.isManyToOne && !relation.isOneToOneOwner) {
|
|
336
|
+
throw new TypeError(`${methodName}: "${keys.join('.')}" is a relation without a join column in its table. ` +
|
|
337
|
+
`Join it and address its columns by the join alias`);
|
|
338
|
+
}
|
|
339
|
+
if (!relation && (metadata === null || metadata === void 0 ? void 0 : metadata.findEmbeddedWithPropertyPath(propertyPath))) {
|
|
340
|
+
throw new TypeError(`${methodName}: "${keys.join('.')}" is an embedded entity, not a column: address its columns, e.g. ` +
|
|
341
|
+
`"${keys.join('.')}.someColumn"`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// an alias the query doesn't know yet may still get metadata (a join added later), so it stays unquoted
|
|
345
|
+
function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
346
|
+
const alias = aliasName === undefined ? undefined : findQueryAlias(queryBuilder, aliasName);
|
|
347
|
+
return alias !== undefined && !alias.hasMetadata;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Key for typed select / order / distinct / group by on a SelectQueryBuilder.
|
|
273
351
|
*
|
|
274
352
|
* Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
|
|
275
353
|
* which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
|
|
276
|
-
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys.
|
|
354
|
+
* (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. An alias of the query without
|
|
355
|
+
* metadata (a subquery or a CTE in FROM or a join) is quoted unless `useQuotes` is `false`:
|
|
356
|
+
* TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
357
|
+
* Strings are kept as is.
|
|
277
358
|
*/
|
|
278
|
-
function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
359
|
+
function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
|
|
279
360
|
if (typeof selection === 'string') {
|
|
280
361
|
return selection;
|
|
281
362
|
}
|
|
@@ -286,7 +367,10 @@ function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
|
286
367
|
throw new TypeError(`Typed key "${keyHolder.getKeys().join('.')}" points into the JSON column "${resolved.propertyPath}": ` +
|
|
287
368
|
`select, order, distinct and group by take columns only. For an expression use tableHelper().jsonColumn()`);
|
|
288
369
|
}
|
|
289
|
-
if (
|
|
370
|
+
if (columnsFor !== undefined) {
|
|
371
|
+
assertTableColumnKey(resolved, keyHolder.getKeys(), columnsFor);
|
|
372
|
+
}
|
|
373
|
+
if (useQuotes !== null && useQuotes !== void 0 ? useQuotes : isAliasWithoutMetadata(queryBuilder, resolved.aliasName)) {
|
|
290
374
|
return toQuotedColumnKey(queryBuilder, resolved);
|
|
291
375
|
}
|
|
292
376
|
return resolved.aliasName === undefined
|
|
@@ -13,6 +13,8 @@ function getValuePlaceholder(value, paramName) {
|
|
|
13
13
|
return `:${paramName}`;
|
|
14
14
|
}
|
|
15
15
|
exports.getValuePlaceholder = getValuePlaceholder;
|
|
16
|
+
/** A condition that ends with an operator, `=`, `>=`, `LIKE`, `IN`, `@>`: it needs a value after it. */
|
|
17
|
+
const OPERATOR_AT_END = /(?:[=<>!~@&|?]|\b(?:LIKE|ILIKE|IN|SIMILAR TO|DISTINCT FROM|BETWEEN|ANY|ALL))\s*$/i;
|
|
16
18
|
const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
17
19
|
/**
|
|
18
20
|
* Objects that are parameter values themselves, not `{ name: value }` params objects:
|
|
@@ -76,6 +78,10 @@ function attachWhere(builder, method, field, conditions, params, options) {
|
|
|
76
78
|
const hasParams = typeof params !== 'undefined';
|
|
77
79
|
// `addValue` defaults to `true` unless `conditions` already references one of the `params` keys
|
|
78
80
|
addValue = addValue !== null && addValue !== void 0 ? addValue : !hasParamPlaceholder(conditions, params);
|
|
81
|
+
if (addValue && !hasParams && OPERATOR_AT_END.test(conditions)) {
|
|
82
|
+
throw new TypeError(`The value after "${fieldName} ${conditions.trim()}" is undefined, which leaves the condition without ` +
|
|
83
|
+
`an operand. Pass the value, null to compare with NULL, or a condition without one ('IS NULL')`);
|
|
84
|
+
}
|
|
79
85
|
if (addValue && hasParams) {
|
|
80
86
|
assertSingleValueParams(fieldName, params);
|
|
81
87
|
// pick first value from params object or pass as is (a Date or a Buffer is a value too)
|