objection 3.6.0 → 3.8.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/lib/model/AjvValidator.js +132 -52
- package/lib/model/modelClone.js +0 -2
- package/lib/queryBuilder/QueryBuilder.js +128 -24
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +15 -3
- package/lib/queryBuilder/RelationExpression.js +6 -4
- package/lib/queryBuilder/StaticHookArguments.js +4 -1
- package/lib/queryBuilder/graph/GraphOptions.js +36 -79
- package/lib/queryBuilder/graph/GraphUpsert.js +35 -7
- package/lib/queryBuilder/graph/insert/GraphInsert.js +2 -0
- package/lib/queryBuilder/join/RelationJoiner.js +104 -12
- package/lib/queryBuilder/join/utils.js +2 -2
- package/lib/queryBuilder/operations/DelegateOperation.js +22 -0
- package/lib/queryBuilder/operations/FirstOperation.js +5 -3
- package/lib/queryBuilder/operations/InsertGraphOperation.js +1 -0
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +2 -1
- package/lib/queryBuilder/operations/InstanceFindOperation.js +2 -1
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +8 -2
- package/lib/queryBuilder/operations/LimitPerParentOperation.js +145 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +31 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +22 -5
- package/lib/queryBuilder/operations/UpdateOperation.js +40 -3
- package/lib/queryBuilder/operations/WhereGroupOperation.js +2 -8
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +5 -0
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +28 -2
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +2 -2
- package/lib/queryBuilder/operations/select/SelectOperation.js +8 -4
- package/lib/queryBuilder/operations/select/Selection.js +21 -20
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +3 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +4 -9
- package/lib/queryBuilder/parsers/relationExpressionParser.js +6 -4
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +7 -3
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +4 -7
- package/lib/queryBuilder/whereGroups.js +35 -40
- package/lib/queryBuilder/withoutOrder.js +33 -0
- package/lib/relations/Relation.js +4 -0
- package/lib/relations/RelationFindOperation.js +30 -43
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +4 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +65 -19
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +31 -42
- package/lib/relations/manyToMany/ManyToManyRelation.js +15 -16
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +0 -13
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +9 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +1 -5
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +9 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +1 -5
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +16 -1
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +92 -16
- package/lib/utils/identifierMapping.js +10 -19
- package/lib/utils/knexUtils.js +14 -0
- package/lib/utils/objectUtils.js +38 -6
- package/lib/utils/parseFieldExpression.js +22 -3
- package/lib/utils/warnings.js +26 -0
- package/lib/utils/wrapDbError.js +34 -20
- package/package.json +5 -4
- package/typings/objection/index.d.ts +54 -26
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +0 -27
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +0 -16
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +0 -12
- package/lib/utils/deprecate.js +0 -21
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
4
|
+
const { RangeOperation } = require('./RangeOperation');
|
|
5
|
+
const { FirstOperation } = require('./FirstOperation');
|
|
6
|
+
const { isString, isNumber } = require('../../utils/objectUtils');
|
|
7
|
+
const { hasWindowFunctions, isKnexRaw } = require('../../utils/knexUtils');
|
|
8
|
+
|
|
9
|
+
// Without underscores, so that column name mappers don't change it.
|
|
10
|
+
const rowNumberColumn = 'objectiontmprownum';
|
|
11
|
+
|
|
12
|
+
// Applies the `limit()`, `offset()` and `first()` of a relation query that
|
|
13
|
+
// fetches the related models of many parents to each parent separately.
|
|
14
|
+
class LimitPerParentOperation extends QueryBuilderOperation {
|
|
15
|
+
constructor(name, opt) {
|
|
16
|
+
super(name, opt);
|
|
17
|
+
this.findOperation = opt.findOperation;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onBuild(builder) {
|
|
21
|
+
builder.addInternalSelection(rowNumberColumn);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Return the limit and offset of the query, built into `knexBuilder` by any
|
|
25
|
+
// of its operations, or `null` if it has none:
|
|
26
|
+
getRange(knexBuilder, builder) {
|
|
27
|
+
// `page()` and `range()` keep limiting all parents, as their total does.
|
|
28
|
+
if (!builder.has(RangeOperation)) {
|
|
29
|
+
const { limit = null, offset = null } = knexBuilder._single;
|
|
30
|
+
// `first()` only limits the query with `useLimitInFirst`:
|
|
31
|
+
const isFirst = builder.has(FirstOperation);
|
|
32
|
+
|
|
33
|
+
if (limit !== null || offset !== null || isFirst) {
|
|
34
|
+
return { limit: isFirst ? 1 : limit, offset: offset ?? 0 };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Called by `buildKnexQuery()` after the default table and select are set.
|
|
42
|
+
onAfterBuildKnex(knexBuilder, builder) {
|
|
43
|
+
const knex = builder.knex();
|
|
44
|
+
const range = this.getRange(knexBuilder, builder);
|
|
45
|
+
|
|
46
|
+
if (!range) {
|
|
47
|
+
return knexBuilder;
|
|
48
|
+
} else if (!hasWindowFunctions(knex)) {
|
|
49
|
+
// `WhereInEagerOperation` fetches the related models of each parent with
|
|
50
|
+
// a separate query then, so only `first()` still needs to be applied:
|
|
51
|
+
return range.limit === null ? knexBuilder : knexBuilder.limit(range.limit);
|
|
52
|
+
} else {
|
|
53
|
+
const { limit, offset } = range;
|
|
54
|
+
// Number the rows of each parent one level up, aliased as the related
|
|
55
|
+
// table, so that the order can refer to the selected columns and their
|
|
56
|
+
// aliases as in the query itself, e.g. to `id` when a joined table has
|
|
57
|
+
// an `id` too. The order moves into the window function, as SQL Server
|
|
58
|
+
// doesn't allow it in subqueries.
|
|
59
|
+
const numberedQuery = knex
|
|
60
|
+
.queryBuilder()
|
|
61
|
+
.select('*')
|
|
62
|
+
.rowNumber(rowNumberColumn, (window) =>
|
|
63
|
+
window
|
|
64
|
+
.orderBy(getWindowOrder(builder, knex))
|
|
65
|
+
.partitionBy(this.findOperation.ownerKeyColumns()),
|
|
66
|
+
)
|
|
67
|
+
.from(
|
|
68
|
+
knexBuilder
|
|
69
|
+
.clone()
|
|
70
|
+
.clear('with')
|
|
71
|
+
.clear('limit')
|
|
72
|
+
.clear('offset')
|
|
73
|
+
.clear('order')
|
|
74
|
+
.as(builder.tableRef()),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Select the rows in range with the query itself, to keep its settings
|
|
78
|
+
// like `timeout()`, and its common table expressions.
|
|
79
|
+
knexBuilder
|
|
80
|
+
.clear('select')
|
|
81
|
+
.clear('where')
|
|
82
|
+
.clear('join')
|
|
83
|
+
.clear('union')
|
|
84
|
+
.clear('group')
|
|
85
|
+
.clear('having')
|
|
86
|
+
.clear('order')
|
|
87
|
+
.clear('limit')
|
|
88
|
+
.clear('offset')
|
|
89
|
+
.select('*')
|
|
90
|
+
.from(numberedQuery.as(rowNumberColumn))
|
|
91
|
+
.where(rowNumberColumn, '>', offset)
|
|
92
|
+
.orderBy(rowNumberColumn);
|
|
93
|
+
|
|
94
|
+
if (limit !== null) {
|
|
95
|
+
// knex also accepts raw offsets:
|
|
96
|
+
const end = isNumber(offset) ? offset + limit : knex.raw('? + ?', [offset, limit]);
|
|
97
|
+
knexBuilder.where(rowNumberColumn, '<=', end);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return knexBuilder;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Convert the `orderBy()` and `orderByRaw()` calls of the query to the order
|
|
106
|
+
// of knex's window functions.
|
|
107
|
+
function getWindowOrder(builder, knex) {
|
|
108
|
+
const order = [];
|
|
109
|
+
|
|
110
|
+
builder.forEachOperation(builder.constructor.OrderBySelector, (op) => {
|
|
111
|
+
const args = op.getKnexArgs(builder);
|
|
112
|
+
|
|
113
|
+
if (op.name === 'orderByRaw') {
|
|
114
|
+
const [sql, bindings] = args;
|
|
115
|
+
order.push({ column: isKnexRaw(sql) ? sql : knex.raw(sql, bindings) });
|
|
116
|
+
} else {
|
|
117
|
+
const [column, direction, nulls] = args;
|
|
118
|
+
const items = Array.isArray(column)
|
|
119
|
+
? column.map((item) => (isString(item) ? { column: item } : item))
|
|
120
|
+
: [{ column, order: direction, nulls }];
|
|
121
|
+
|
|
122
|
+
for (const item of items) {
|
|
123
|
+
if (item.nulls) {
|
|
124
|
+
// Not all databases support `nulls first` and `nulls last`:
|
|
125
|
+
order.push({
|
|
126
|
+
column: knex.raw('(case when ? is null then 0 else 1 end)', [
|
|
127
|
+
isString(item.column) ? knex.ref(item.column) : item.column,
|
|
128
|
+
]),
|
|
129
|
+
order: item.nulls === 'first' ? 'asc' : 'desc',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Unlike in `orderBy()`, knex adds the direction to the window as it is:
|
|
134
|
+
order.push({ column: item.column, order: /^desc$/i.test(item.order) ? 'desc' : 'asc' });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// SQL Server requires an order in the window function:
|
|
140
|
+
return order.length ? order : [{ column: knex.raw('(select null)') }];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
LimitPerParentOperation,
|
|
145
|
+
};
|
|
@@ -6,9 +6,11 @@ const hookNameToHasMethodName = {
|
|
|
6
6
|
onBefore2: 'hasOnBefore2',
|
|
7
7
|
onBefore3: 'hasOnBefore3',
|
|
8
8
|
onBuild: 'hasOnBuild',
|
|
9
|
+
onAfterBuild: 'hasOnAfterBuild',
|
|
9
10
|
onBuildKnex: 'hasOnBuildKnex',
|
|
10
11
|
onRawResult: 'hasOnRawResult',
|
|
11
12
|
queryExecutor: 'hasQueryExecutor',
|
|
13
|
+
queryBatches: 'hasQueryBatches',
|
|
12
14
|
onAfter1: 'hasOnAfter1',
|
|
13
15
|
onAfter2: 'hasOnAfter2',
|
|
14
16
|
onAfter3: 'hasOnAfter3',
|
|
@@ -107,6 +109,20 @@ class QueryBuilderOperation {
|
|
|
107
109
|
return this.onBuild !== QueryBuilderOperation.prototype.onBuild;
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
// This is called after `onBuild` when the query is executed, right before the
|
|
113
|
+
// knex query is built and executed, but not when the query is only built,
|
|
114
|
+
// e.g. with `toKnexQuery()` or `resultSize()`. Use it for work that needs the
|
|
115
|
+
// built query, like queries that have to run before it. It's also called for
|
|
116
|
+
// queries that are resolved or rejected without executing, e.g. with `none()`
|
|
117
|
+
// or `resolve()`, so check `builder.isExplicitlyResolvedOrRejected()` first.
|
|
118
|
+
//
|
|
119
|
+
// This method can be asynchronous.
|
|
120
|
+
// This method should never call any methods that add operations to the builder.
|
|
121
|
+
onAfterBuild(builder) {}
|
|
122
|
+
hasOnAfterBuild() {
|
|
123
|
+
return this.onAfterBuild !== QueryBuilderOperation.prototype.onAfterBuild;
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
// This is called when the knex query is built. Here you should only call knex
|
|
111
127
|
// methods. You may call getters and other immutable methods of the `builder`
|
|
112
128
|
// but you should never call methods that add QueryBuilderOperations.
|
|
@@ -177,6 +193,21 @@ class QueryBuilderOperation {
|
|
|
177
193
|
return this.queryExecutor !== QueryBuilderOperation.prototype.queryExecutor;
|
|
178
194
|
}
|
|
179
195
|
|
|
196
|
+
// This is called after `onAfterBuild` when the query is executed. It can
|
|
197
|
+
// return an array of batches to build and execute the knex query once per
|
|
198
|
+
// batch, e.g. when a filter has more values than the database accepts in one
|
|
199
|
+
// statement. The batch being built is in `builder._queryBatch`, and the
|
|
200
|
+
// results of all batches are merged before `onRawResult` is called. Return
|
|
201
|
+
// null to execute the query once.
|
|
202
|
+
//
|
|
203
|
+
// This method must be synchronous.
|
|
204
|
+
queryBatches(builder) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
hasQueryBatches() {
|
|
208
|
+
return this.queryBatches !== QueryBuilderOperation.prototype.queryBatches;
|
|
209
|
+
}
|
|
210
|
+
|
|
180
211
|
// This is called if an error occurs in the query execution.
|
|
181
212
|
//
|
|
182
213
|
// This method must return a QueryBuilder instance.
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { DelegateOperation } = require('./DelegateOperation');
|
|
4
4
|
const { FindByIdOperation } = require('./FindByIdOperation');
|
|
5
5
|
const { UpdateOperation } = require('./UpdateOperation');
|
|
6
|
+
const { addWhereGroup, assignWhereGroup } = require('../whereGroups');
|
|
6
7
|
|
|
7
8
|
class UpdateAndFetchOperation extends DelegateOperation {
|
|
8
9
|
constructor(name, opt) {
|
|
@@ -41,7 +42,7 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
41
42
|
|
|
42
43
|
onBuild(builder) {
|
|
43
44
|
if (!this.skipIdWhere) {
|
|
44
|
-
builder.findById(this.id);
|
|
45
|
+
addWhereGroup(builder, () => builder.findById(this.id));
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
super.onBuild(builder);
|
|
@@ -78,10 +79,17 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
78
79
|
return fetched ? this.model : undefined;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
toFindOperation() {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
toFindOperation(builder) {
|
|
83
|
+
// The find operation of the delegate keeps its filters, e.g. the ones that
|
|
84
|
+
// many-to-many updates move to a subquery when the query is built.
|
|
85
|
+
const delegate = this.delegate.toFindOperation(builder);
|
|
86
|
+
|
|
87
|
+
// Keep the id condition in its own where group, like `onBuild()` does. The
|
|
88
|
+
// delegate isn't named `findById`, as that would put all the wheres that it
|
|
89
|
+
// adds in the user's group.
|
|
90
|
+
return delegate
|
|
91
|
+
? new FindByIdDelegateOperation('find', { delegate, id: this.id })
|
|
92
|
+
: assignWhereGroup(new FindByIdOperation('findById', { id: this.id }));
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
clone() {
|
|
@@ -94,6 +102,15 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
94
102
|
}
|
|
95
103
|
}
|
|
96
104
|
|
|
105
|
+
// Finds the rows of a delegate find operation by id, like `UpdateAndFetchOperation`
|
|
106
|
+
// updates them.
|
|
107
|
+
class FindByIdDelegateOperation extends DelegateOperation {
|
|
108
|
+
onBuild(builder) {
|
|
109
|
+
super.onBuild(builder);
|
|
110
|
+
addWhereGroup(builder, () => builder.findById(this.opt.id));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
97
114
|
module.exports = {
|
|
98
115
|
UpdateAndFetchOperation,
|
|
99
116
|
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { ref } = require('../../queryBuilder/ReferenceBuilder');
|
|
4
|
-
const { isEmpty } = require('../../utils/objectUtils');
|
|
5
|
-
const {
|
|
4
|
+
const { isEmpty, isObject } = require('../../utils/objectUtils');
|
|
5
|
+
const {
|
|
6
|
+
isFieldExpressionKey,
|
|
7
|
+
toPostgresJsonPathLiteral,
|
|
8
|
+
} = require('../../utils/parseFieldExpression');
|
|
6
9
|
const { isKnexRaw, isKnexQueryBuilder } = require('../../utils/knexUtils');
|
|
7
10
|
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
8
11
|
const { warnIfNotPostgres } = require('./jsonApi/postgresJsonApi');
|
|
@@ -14,6 +17,7 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
14
17
|
|
|
15
18
|
this.model = null;
|
|
16
19
|
this.modelOptions = Object.assign({}, this.opt.modelOptions || {});
|
|
20
|
+
this.hasNothingToUpdate = false;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
onAdd(builder, args) {
|
|
@@ -34,10 +38,19 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
34
38
|
|
|
35
39
|
if (isEmpty(row)) {
|
|
36
40
|
// Resolve the query if there is nothing to update.
|
|
41
|
+
this.hasNothingToUpdate = !builder.isExplicitlyResolvedOrRejected();
|
|
37
42
|
builder.resolve(0);
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
|
|
46
|
+
// Returns true if the query was resolved without updating anything, e.g.
|
|
47
|
+
// with `none()` or `resolve()` in a hook, but not if it was only resolved
|
|
48
|
+
// because there is nothing to update in the table, as the update still
|
|
49
|
+
// applies to everything else (#2184).
|
|
50
|
+
isResolvedWithoutUpdate(builder) {
|
|
51
|
+
return builder.isExplicitlyResolvedOrRejected() && !this.hasNothingToUpdate;
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
onBuildKnex(knexBuilder, builder) {
|
|
42
55
|
const json = this.model.$toDatabaseJson(builder);
|
|
43
56
|
const convertedJson = convertFieldExpressionsToRaw(builder, this.model, json);
|
|
@@ -56,6 +69,7 @@ class UpdateOperation extends QueryBuilderOperation {
|
|
|
56
69
|
clone() {
|
|
57
70
|
const clone = super.clone();
|
|
58
71
|
clone.model = this.model;
|
|
72
|
+
clone.hasNothingToUpdate = this.hasNothingToUpdate;
|
|
59
73
|
return clone;
|
|
60
74
|
}
|
|
61
75
|
}
|
|
@@ -96,12 +110,13 @@ async function callStaticAfterUpdate(builder, result) {
|
|
|
96
110
|
|
|
97
111
|
function convertFieldExpressionsToRaw(builder, model, json) {
|
|
98
112
|
const knex = builder.knex();
|
|
113
|
+
const declaredColumns = getDeclaredColumns(builder.modelClass());
|
|
99
114
|
const convertedJson = {};
|
|
100
115
|
|
|
101
116
|
for (const key of Object.keys(json)) {
|
|
102
117
|
let val = json[key];
|
|
103
118
|
|
|
104
|
-
if (key
|
|
119
|
+
if (isFieldExpressionKey(key, declaredColumns)) {
|
|
105
120
|
// 'col:attr' : ref('other:lol') is transformed to
|
|
106
121
|
// "col" : raw(`jsonb_set("col", '{attr}', to_jsonb("other"#>'{lol}'), true)`)
|
|
107
122
|
warnIfNotPostgres(knex);
|
|
@@ -130,6 +145,28 @@ function convertFieldExpressionsToRaw(builder, model, json) {
|
|
|
130
145
|
return convertedJson;
|
|
131
146
|
}
|
|
132
147
|
|
|
148
|
+
const declaredColumnsByModelClass = new WeakMap();
|
|
149
|
+
|
|
150
|
+
// The keys of `json` are column names, so map the properties with a ':' that the
|
|
151
|
+
// schema declares to their columns, once per model class. Properties that only
|
|
152
|
+
// the schema returned by `$beforeValidate()` declares aren't known here.
|
|
153
|
+
function getDeclaredColumns(modelClass) {
|
|
154
|
+
let declaredColumns = declaredColumnsByModelClass.get(modelClass);
|
|
155
|
+
|
|
156
|
+
if (!declaredColumns) {
|
|
157
|
+
const jsonSchema = modelClass.getJsonSchema();
|
|
158
|
+
const properties = jsonSchema && jsonSchema.properties;
|
|
159
|
+
declaredColumns = Object.fromEntries(
|
|
160
|
+
(isObject(properties) ? Object.keys(properties) : [])
|
|
161
|
+
.filter((key) => key.includes(':'))
|
|
162
|
+
.map((key) => [modelClass.propertyNameToColumnName(key), true]),
|
|
163
|
+
);
|
|
164
|
+
declaredColumnsByModelClass.set(modelClass, declaredColumns);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return declaredColumns;
|
|
168
|
+
}
|
|
169
|
+
|
|
133
170
|
module.exports = {
|
|
134
171
|
UpdateOperation,
|
|
135
172
|
convertFieldExpressionsToRaw,
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
4
4
|
|
|
5
5
|
// Builds a set of where operations inside a single knex `where(callback)`
|
|
6
|
-
// group, i.e. wraps them in parentheses. See `
|
|
6
|
+
// group, i.e. wraps them in parentheses. See `getWhereGroupOperations()`.
|
|
7
7
|
class WhereGroupOperation extends QueryBuilderOperation {
|
|
8
8
|
constructor(name, opt) {
|
|
9
9
|
super(name, opt);
|
|
10
|
-
this.operations =
|
|
10
|
+
this.operations = opt.operations;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
onBuildKnex(knexBuilder, builder) {
|
|
@@ -28,12 +28,6 @@ class WhereGroupOperation extends QueryBuilderOperation {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
clone() {
|
|
33
|
-
const clone = super.clone();
|
|
34
|
-
clone.operations = this.operations.map((op) => op.clone());
|
|
35
|
-
return clone;
|
|
36
|
-
}
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
module.exports = {
|
|
@@ -20,6 +20,11 @@ class JoinEagerOperation extends EagerOperation {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
onBefore3(builder) {
|
|
23
|
+
return this.fetchTableMetadata(builder);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Also called by `resultSize()`, which builds the query without executing it.
|
|
27
|
+
fetchTableMetadata(builder) {
|
|
23
28
|
return this.joiner
|
|
24
29
|
.setExpression(this.buildFinalExpression(builder))
|
|
25
30
|
.setModifiers(this.buildFinalModifiers(builder))
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
const promiseUtils = require('../../../utils/promiseUtils');
|
|
4
4
|
|
|
5
5
|
const { EagerOperation } = require('./EagerOperation');
|
|
6
|
-
const {
|
|
6
|
+
const { LimitPerParentOperation } = require('../LimitPerParentOperation');
|
|
7
|
+
const { isMsSql, isOracle, isSqlite, hasWindowFunctions } = require('../../../utils/knexUtils');
|
|
7
8
|
const { isObject, asArray, flatten, chunk } = require('../../../utils/objectUtils');
|
|
8
9
|
const { ValidationErrorType } = require('../../../model/ValidationError');
|
|
9
10
|
const { createModifier } = require('../../../utils/createModifier');
|
|
@@ -85,7 +86,9 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
85
86
|
|
|
86
87
|
async fetchRelation(builder, models, relation, expr) {
|
|
87
88
|
const modelClass = builder.resultModelClass();
|
|
88
|
-
const batchSize = this.
|
|
89
|
+
const batchSize = this.shouldFetchPerParent(builder, models, relation, expr)
|
|
90
|
+
? 1
|
|
91
|
+
: this.batchSize(builder.knex());
|
|
89
92
|
const modelBatches = chunk(models, batchSize);
|
|
90
93
|
|
|
91
94
|
const result = await promiseUtils.map(
|
|
@@ -136,9 +139,32 @@ class WhereInEagerOperation extends EagerOperation {
|
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
if (this.graphOptions.limitPerParent) {
|
|
143
|
+
queryBuilder.addOperation(
|
|
144
|
+
new LimitPerParentOperation('limitPerParent', { findOperation }),
|
|
145
|
+
[],
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
139
149
|
return queryBuilder;
|
|
140
150
|
}
|
|
141
151
|
|
|
152
|
+
// Without window functions, the related models can only be limited per
|
|
153
|
+
// parent with one query per parent.
|
|
154
|
+
shouldFetchPerParent(builder, models, relation, expr) {
|
|
155
|
+
if (this.graphOptions.limitPerParent && !hasWindowFunctions(builder.knex())) {
|
|
156
|
+
// Build the query to find its limits, also those of the relation's
|
|
157
|
+
// `modify` and of `onBuildKnex()` hooks. This runs its modifiers and
|
|
158
|
+
// build hooks once more than for fetching the relation.
|
|
159
|
+
const query = this.fetchRelationBatch(builder, models, relation, expr);
|
|
160
|
+
query.executeOnBuild();
|
|
161
|
+
const knexQuery = query.executeOnBuildKnex(query.knex().queryBuilder());
|
|
162
|
+
return !!query.findOperation(LimitPerParentOperation)?.getRange(knexQuery, query);
|
|
163
|
+
} else {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
shouldSkipFetched(models, relation, expr) {
|
|
143
169
|
if (!this.graphOptions.skipFetched) {
|
|
144
170
|
return false;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const parser = require('../../../utils/parseFieldExpression');
|
|
4
4
|
const { asArray, isObject, isString } = require('../../../utils/objectUtils');
|
|
5
5
|
const { isKnexRaw, isKnexQueryBuilder, isPostgres } = require('../../../utils/knexUtils');
|
|
6
|
-
const {
|
|
6
|
+
const { warnOnce } = require('../../../utils/warnings');
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {String} FieldExpression
|
|
@@ -50,7 +50,7 @@ const { deprecate } = require('../../../utils/deprecate');
|
|
|
50
50
|
// TODO: Consider throwing an error instead in objection 4.0.
|
|
51
51
|
function warnIfNotPostgres(knex) {
|
|
52
52
|
if (!isPostgres(knex)) {
|
|
53
|
-
|
|
53
|
+
warnOnce(
|
|
54
54
|
'JSON field expressions and the whereJson*() methods of objection are only supported ' +
|
|
55
55
|
'on PostgreSQL. Use the JSON methods of knex like whereJsonPath() or jsonExtract() ' +
|
|
56
56
|
'with other databases.',
|
|
@@ -25,6 +25,8 @@ class SelectOperation extends ObjectionToKnexConvertingOperation {
|
|
|
25
25
|
// The selections that aggregates select, by their aliases. `null` for
|
|
26
26
|
// non-aggregate operations.
|
|
27
27
|
this.aggregateSelections = AGGREGATE_METHODS.has(name) ? [] : null;
|
|
28
|
+
// `distinctOn()` makes the rows distinct on its columns, it doesn't select them.
|
|
29
|
+
this.isDistinctOn = name === 'distinctOn';
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
onAdd(builder, args) {
|
|
@@ -38,11 +40,13 @@ class SelectOperation extends ObjectionToKnexConvertingOperation {
|
|
|
38
40
|
|
|
39
41
|
const ret = super.onAdd(builder, selections);
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
+
if (!this.isDistinctOn) {
|
|
44
|
+
for (const selection of selections) {
|
|
45
|
+
const selectionInstance = Selection.create(selection);
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if (selectionInstance) {
|
|
48
|
+
this.selections.push(selectionInstance);
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -63,11 +63,11 @@ class Selection {
|
|
|
63
63
|
if (selectionInBuilder.column === '*') {
|
|
64
64
|
if (selectionInBuilder.table) {
|
|
65
65
|
if (selectionToTest.column === '*') {
|
|
66
|
-
return
|
|
66
|
+
return isSameIdentifier(builder, selectionToTest.table, selectionInBuilder.table);
|
|
67
67
|
} else {
|
|
68
68
|
return (
|
|
69
69
|
selectionToTest.table === null ||
|
|
70
|
-
|
|
70
|
+
isSameIdentifier(builder, selectionToTest.table, selectionInBuilder.table)
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
} else {
|
|
@@ -80,9 +80,9 @@ class Selection {
|
|
|
80
80
|
return false;
|
|
81
81
|
} else {
|
|
82
82
|
return (
|
|
83
|
-
selectionToTest.column
|
|
83
|
+
isSameIdentifier(builder, selectionToTest.column, selectionInBuilder.column) &&
|
|
84
84
|
(selectionToTest.table === null ||
|
|
85
|
-
|
|
85
|
+
isSameIdentifier(builder, selectionToTest.table, selectionInBuilderTable))
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
@@ -97,30 +97,29 @@ Object.defineProperties(Selection.prototype, {
|
|
|
97
97
|
},
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
//
|
|
101
|
-
// (for example `knexSnakeCaseMappers()`) cancels out
|
|
100
|
+
// Identifiers that only differ in a way that knex's `wrapIdentifier` mapping
|
|
101
|
+
// (for example `knexSnakeCaseMappers()`) cancels out are the same identifier:
|
|
102
102
|
// `dataExtension` and `data_extension` both end up as `data_extension`.
|
|
103
|
-
function
|
|
104
|
-
if (
|
|
103
|
+
function isSameIdentifier(builder, identifier1, identifier2) {
|
|
104
|
+
if (identifier1 === identifier2) {
|
|
105
105
|
return true;
|
|
106
|
-
} else if (!
|
|
106
|
+
} else if (!identifier1 || !identifier2) {
|
|
107
107
|
return false;
|
|
108
|
+
} else {
|
|
109
|
+
return mapIdentifier(builder, identifier1) === mapIdentifier(builder, identifier2);
|
|
108
110
|
}
|
|
111
|
+
}
|
|
109
112
|
|
|
113
|
+
function mapIdentifier(builder, identifier) {
|
|
110
114
|
const knex = builder.unsafeKnex();
|
|
111
115
|
const wrapIdentifier = knex && knex.client && knex.client.config.wrapIdentifier;
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
.split('.')
|
|
120
|
-
.map((part) => wrapIdentifier(part, identity))
|
|
121
|
-
.join('.');
|
|
122
|
-
|
|
123
|
-
return mapTable(table1) === mapTable(table2);
|
|
117
|
+
return wrapIdentifier
|
|
118
|
+
? identifier
|
|
119
|
+
.split('.')
|
|
120
|
+
.map((part) => wrapIdentifier(part, identity))
|
|
121
|
+
.join('.')
|
|
122
|
+
: identifier;
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
function identity(value) {
|
|
@@ -211,4 +210,6 @@ function createSelectionFromString(selection) {
|
|
|
211
210
|
|
|
212
211
|
module.exports = {
|
|
213
212
|
Selection,
|
|
213
|
+
isSameIdentifier,
|
|
214
|
+
mapIdentifier,
|
|
214
215
|
};
|
|
@@ -42,7 +42,9 @@ class WhereInCompositeMsSqlOperation extends ObjectionToKnexConvertingOperation
|
|
|
42
42
|
knexBuilder,
|
|
43
43
|
columns,
|
|
44
44
|
helperColumns,
|
|
45
|
-
|
|
45
|
+
// knex only accepts strings as aliases, so the subquery is aliased in
|
|
46
|
+
// a raw query.
|
|
47
|
+
knex.raw(`? AS V(${helperColumns.map((_) => '??')})`, [values, ...helperColumns]),
|
|
46
48
|
);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
@@ -48,18 +48,13 @@ function isCompositeKey(columns) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function buildCompositeArgs(columns, values) {
|
|
51
|
-
if (!Array.isArray(values)) {
|
|
52
|
-
// If the `values` is not an array of values but a function or a subquery
|
|
53
|
-
// we have no way to implement this method.
|
|
54
|
-
throw new Error(`sqlite doesn't support multi-column where in clauses`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (values.length === 0) {
|
|
51
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
58
52
|
// knex turns an empty `whereIn` into `1 = 0` and an empty `whereNotIn`
|
|
59
|
-
// into `1 = 1`.
|
|
53
|
+
// into `1 = 1`. Subqueries are compared as row values, which sqlite
|
|
54
|
+
// supports since version 3.15.
|
|
60
55
|
return {
|
|
61
56
|
method: 'whereIn',
|
|
62
|
-
args: [columns,
|
|
57
|
+
args: [columns, values],
|
|
63
58
|
};
|
|
64
59
|
}
|
|
65
60
|
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
const { warnOnce } = require('../../utils/warnings');
|
|
8
|
+
|
|
7
9
|
class peg$SyntaxError extends SyntaxError {
|
|
8
10
|
constructor(message, expected, found, location) {
|
|
9
11
|
super(message);
|
|
@@ -943,12 +945,12 @@ function peg$parse(input, options) {
|
|
|
943
945
|
}
|
|
944
946
|
|
|
945
947
|
function assertDuplicateRelation(node, expr) {
|
|
946
|
-
if (expr.$name
|
|
947
|
-
|
|
948
|
-
`Duplicate relation "${expr.$name}" in a relation expression
|
|
948
|
+
if (Object.prototype.hasOwnProperty.call(node, expr.$name)) {
|
|
949
|
+
warnOnce(
|
|
950
|
+
`Duplicate relation "${expr.$name}" in a relation expression, only the last one is used. Use "a.[b, c]" instead of "[a.b, a.c]". This will throw an error in objection 4.0.`,
|
|
949
951
|
);
|
|
950
952
|
|
|
951
|
-
//
|
|
953
|
+
// Throws in objection 4.0, see ditojs/objection#53.
|
|
952
954
|
// const err = new Error();
|
|
953
955
|
// err.duplicateRelationName = expr.$name;
|
|
954
956
|
// throw err;
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
{{
|
|
2
|
+
const { warnOnce } = require('../../utils/warnings');
|
|
3
|
+
}}
|
|
4
|
+
|
|
1
5
|
{
|
|
2
6
|
function assertDuplicateRelation(node, expr) {
|
|
3
|
-
if (expr.$name
|
|
4
|
-
|
|
7
|
+
if (Object.prototype.hasOwnProperty.call(node, expr.$name)) {
|
|
8
|
+
warnOnce(`Duplicate relation "${expr.$name}" in a relation expression, only the last one is used. Use "a.[b, c]" instead of "[a.b, a.c]". This will throw an error in objection 4.0.`);
|
|
5
9
|
|
|
6
|
-
//
|
|
10
|
+
// Throws in objection 4.0, see ditojs/objection#53.
|
|
7
11
|
// const err = new Error();
|
|
8
12
|
// err.duplicateRelationName = expr.$name;
|
|
9
13
|
// throw err;
|