objection 3.1.5 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -16
- package/lib/model/AjvValidator.js +173 -18
- package/lib/model/Model.js +3 -1
- package/lib/model/graph/ModelGraphNode.js +12 -0
- package/lib/model/modelToJson.js +2 -1
- package/lib/queryBuilder/QueryBuilder.js +339 -47
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +5 -0
- package/lib/queryBuilder/ReferenceBuilder.js +5 -3
- package/lib/queryBuilder/RelationExpression.js +21 -9
- package/lib/queryBuilder/StaticHookArguments.js +12 -1
- package/lib/queryBuilder/graph/GraphFetcher.js +3 -1
- package/lib/queryBuilder/graph/GraphOptions.js +21 -2
- package/lib/queryBuilder/graph/GraphUpsert.js +52 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +20 -14
- package/lib/queryBuilder/graph/insert/DeferredRelationGraphInsertAction.js +57 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +113 -8
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +10 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +15 -3
- package/lib/queryBuilder/join/JoinResultParser.js +22 -0
- package/lib/queryBuilder/join/RelationJoiner.js +71 -5
- package/lib/queryBuilder/operations/DelegateOperation.js +4 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +7 -1
- package/lib/queryBuilder/operations/InsertGraphOperation.js +2 -2
- package/lib/queryBuilder/operations/InsertOperation.js +121 -12
- package/lib/queryBuilder/operations/NoneOperation.js +37 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +5 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +4 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +28 -3
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +20 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +3 -2
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +5 -0
- package/lib/queryBuilder/operations/WhereGroupOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +7 -5
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +4 -2
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +9 -20
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasPostgresOperation.js +3 -3
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectPostgresOperation.js +1 -1
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +45 -32
- package/lib/queryBuilder/operations/select/Selection.js +95 -4
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +6 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +5 -1
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +9 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +81 -43
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs +5 -4
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +9 -0
- package/lib/queryBuilder/whereGroups.js +125 -0
- package/lib/relations/Relation.js +5 -2
- package/lib/relations/RelationProperty.js +7 -3
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +1 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +5 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +6 -2
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +5 -2
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +26 -3
- package/lib/relations/manyToMany/ManyToManyMySqlModifyMixin.js +66 -0
- package/lib/relations/manyToMany/ManyToManyPostgresModifyMixin.js +27 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +70 -48
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +6 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateMySqlOperation.js +16 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +5 -1
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelatePostgresOperation.js +16 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateMySqlOperation.js +12 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +8 -2
- package/lib/relations/manyToMany/update/ManyToManyUpdatePostgresOperation.js +12 -0
- package/lib/utils/deprecate.js +6 -0
- package/lib/utils/identifierMapping.js +23 -3
- package/lib/utils/objectUtils.js +9 -5
- package/lib/utils/parseFieldExpression.js +28 -0
- package/package.json +22 -14
- package/typings/objection/index.d.ts +339 -68
|
@@ -24,6 +24,8 @@ class RelationJoiner {
|
|
|
24
24
|
this.modifiers = null;
|
|
25
25
|
|
|
26
26
|
this.options = defaultOptions();
|
|
27
|
+
// The `joinOperation` options of the individual `withGraphJoined` calls.
|
|
28
|
+
this.joinOperations = [];
|
|
27
29
|
this.tableTree = null;
|
|
28
30
|
this.internalSelections = null;
|
|
29
31
|
}
|
|
@@ -49,6 +51,11 @@ class RelationJoiner {
|
|
|
49
51
|
return this;
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
setJoinOperations(joinOperations) {
|
|
55
|
+
this.joinOperations = joinOperations;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
/**
|
|
53
60
|
* Fetches the column information needed for building the select clauses.
|
|
54
61
|
*
|
|
@@ -117,12 +124,18 @@ class RelationJoiner {
|
|
|
117
124
|
// also contain selects.
|
|
118
125
|
userSelectQuery.modify(tableNode.relation.modify);
|
|
119
126
|
|
|
127
|
+
if (selectsOnlyAliasesKeepingSelectAll(userSelectQuery.findAllSelections())) {
|
|
128
|
+
// All columns are selected from the joined subquery in the main query,
|
|
129
|
+
// so the subquery needs to select them too.
|
|
130
|
+
subqueryToJoin.select(`${subqueryToJoin.tableRef()}.*`);
|
|
131
|
+
}
|
|
132
|
+
|
|
120
133
|
// Save the query that contains the user specified selects
|
|
121
134
|
// for later use.
|
|
122
135
|
userSelectQueries.set(tableNode, userSelectQuery);
|
|
123
136
|
|
|
124
137
|
tableNode.relation.join(builder, {
|
|
125
|
-
joinOperation: this.
|
|
138
|
+
joinOperation: this._getJoinOperation(tableNode),
|
|
126
139
|
|
|
127
140
|
ownerTable: tableNode.parentNode.alias,
|
|
128
141
|
relatedTableAlias: tableNode.alias,
|
|
@@ -139,6 +152,26 @@ class RelationJoiner {
|
|
|
139
152
|
}
|
|
140
153
|
}
|
|
141
154
|
|
|
155
|
+
// Returns the `joinOperation` of the last `withGraphJoined` call that
|
|
156
|
+
// contains the relation of `tableNode`, falling back to the default.
|
|
157
|
+
_getJoinOperation(tableNode) {
|
|
158
|
+
const path = [];
|
|
159
|
+
|
|
160
|
+
for (let node = tableNode; node.parentNode; node = node.parentNode) {
|
|
161
|
+
path.unshift(node.relationProperty);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (let i = this.joinOperations.length - 1; i >= 0; --i) {
|
|
165
|
+
const { expression, joinOperation } = this.joinOperations[i];
|
|
166
|
+
|
|
167
|
+
if (containsPath(expression, path)) {
|
|
168
|
+
return joinOperation;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return this.options.joinOperation;
|
|
173
|
+
}
|
|
174
|
+
|
|
142
175
|
_buildSelects({ builder, tableNode, userSelectQueries }) {
|
|
143
176
|
const { selections, internalSelections } = getSelectionsForNode({
|
|
144
177
|
builder,
|
|
@@ -151,9 +184,21 @@ class RelationJoiner {
|
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
// Save the selections that were added internally (not by the user)
|
|
154
|
-
// so that we can later remove the corresponding properties
|
|
155
|
-
//
|
|
156
|
-
|
|
187
|
+
// so that we can later remove the corresponding properties. The root
|
|
188
|
+
// ones are registered with the builder, which removes them from the
|
|
189
|
+
// result models after all eager operations have run, as they may still
|
|
190
|
+
// need them. The nested ones are removed when parsing the result.
|
|
191
|
+
this.internalSelections = [];
|
|
192
|
+
|
|
193
|
+
for (const selection of internalSelections) {
|
|
194
|
+
if (selection.table === tableNode.alias) {
|
|
195
|
+
builder.addInternalSelection(
|
|
196
|
+
tableNode.modelClass.columnNameToPropertyName(selection.column),
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
this.internalSelections.push(selection);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
157
202
|
|
|
158
203
|
builder.select(selectionsToStrings(selections));
|
|
159
204
|
}
|
|
@@ -168,9 +213,21 @@ function defaultOptions() {
|
|
|
168
213
|
};
|
|
169
214
|
}
|
|
170
215
|
|
|
216
|
+
function containsPath(expression, path) {
|
|
217
|
+
for (const name of path) {
|
|
218
|
+
expression = expression.childExpression(name);
|
|
219
|
+
|
|
220
|
+
if (!expression) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
171
228
|
function createSubqueryToJoin({ builder, tableNode, modifiers }) {
|
|
172
229
|
const { relation, expression, modelClass } = tableNode;
|
|
173
|
-
const modifierQuery = modelClass.query().childQueryOf(builder);
|
|
230
|
+
const modifierQuery = modelClass.query().childQueryOf(builder, { isJoinChildQuery: true });
|
|
174
231
|
|
|
175
232
|
for (const modifierName of expression.node.$modify) {
|
|
176
233
|
const modifier = createModifier({
|
|
@@ -324,9 +381,18 @@ function isNotSelectAll(selection) {
|
|
|
324
381
|
}
|
|
325
382
|
|
|
326
383
|
function isSelectAllSelectionSet(selections) {
|
|
384
|
+
selections = selections.filter(isNotKeepingSelectAll);
|
|
327
385
|
return selections.length === 0 || selections.some(isSelectAll);
|
|
328
386
|
}
|
|
329
387
|
|
|
388
|
+
function isNotKeepingSelectAll(selection) {
|
|
389
|
+
return !selection.keepsSelectAll;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function selectsOnlyAliasesKeepingSelectAll(selections) {
|
|
393
|
+
return selections.length > 0 && selections.every((it) => it.keepsSelectAll);
|
|
394
|
+
}
|
|
395
|
+
|
|
330
396
|
function checkAliasLength(modelClass, alias) {
|
|
331
397
|
if (alias.length > ID_LENGTH_LIMIT) {
|
|
332
398
|
throw modelClass.createValidationError({
|
|
@@ -11,6 +11,10 @@ class DelegateOperation extends QueryBuilderOperation {
|
|
|
11
11
|
this.delegate = opt.delegate;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
get modelOptions() {
|
|
15
|
+
return this.delegate.modelOptions;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
is(OperationClass) {
|
|
15
19
|
return super.is(OperationClass) || this.delegate.is(OperationClass);
|
|
16
20
|
}
|
|
@@ -22,10 +22,16 @@ class InsertAndFetchOperation extends DelegateOperation {
|
|
|
22
22
|
const modelClass = builder.modelClass();
|
|
23
23
|
const insertedModels = await super.onAfter2(builder, inserted);
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
// Models without identifiers can't be fetched. This happens when a row
|
|
26
|
+
// with a database generated identifier is ignored by `onConflict()`.
|
|
27
|
+
const insertedModelArray = asArray(insertedModels).filter((model) => model && model.$hasId());
|
|
26
28
|
const idProps = modelClass.getIdPropertyArray();
|
|
27
29
|
const ids = insertedModelArray.map((model) => model.$id());
|
|
28
30
|
|
|
31
|
+
if (ids.length === 0) {
|
|
32
|
+
return insertedModels;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
const fetchedModels = await modelClass
|
|
30
36
|
.query()
|
|
31
37
|
.childQueryOf(builder)
|
|
@@ -4,7 +4,7 @@ const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
|
4
4
|
const { StaticHookArguments } = require('../StaticHookArguments');
|
|
5
5
|
const { after, mapAfterAllReturn } = require('../../utils/promiseUtils');
|
|
6
6
|
const { isPostgres, isSqlite, isMySql, isMsSql } = require('../../utils/knexUtils');
|
|
7
|
-
const { isObject } = require('../../utils/objectUtils');
|
|
7
|
+
const { isObject, isString } = require('../../utils/objectUtils');
|
|
8
8
|
|
|
9
9
|
// Base class for all insert operations.
|
|
10
10
|
class InsertOperation extends QueryBuilderOperation {
|
|
@@ -36,26 +36,46 @@ class InsertOperation extends QueryBuilderOperation {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
onBuildKnex(knexBuilder, builder) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
const knex = builder.knex();
|
|
40
|
+
|
|
41
|
+
if (!builder.has(/returning/)) {
|
|
42
|
+
if (!isSqlite(knex) && !isMySql(knex)) {
|
|
43
|
+
// If the user hasn't specified a `returning` clause, we make sure
|
|
44
|
+
// that at least the identifier is returned. With `onConflict()` the
|
|
45
|
+
// conflict columns are returned too, so that the returned rows can
|
|
46
|
+
// be matched to the models when some of the rows are ignored.
|
|
47
|
+
knexBuilder = knexBuilder.returning(getDefaultReturning(builder));
|
|
48
|
+
} else if (isSqlite(knex) && builder.has('onConflict')) {
|
|
49
|
+
// Without a `returning` clause, SQLite returns the id of the last
|
|
50
|
+
// inserted row, which belongs to a previous insert if the row was
|
|
51
|
+
// ignored. Use `returning` to find out what was actually inserted.
|
|
52
|
+
knexBuilder = knexBuilder.returning(getDefaultReturning(builder));
|
|
53
|
+
}
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
return knexBuilder.insert(this.models.map((model) => model.$toDatabaseJson(builder)));
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
onAfter1(
|
|
59
|
+
onAfter1(builder, ret) {
|
|
49
60
|
if (!Array.isArray(ret) || !ret.length || ret === this.models) {
|
|
50
61
|
// Early exit if there is nothing to do.
|
|
51
62
|
return this.models;
|
|
52
63
|
}
|
|
53
64
|
|
|
65
|
+
const isOnConflict = builder.has('onConflict');
|
|
66
|
+
|
|
54
67
|
if (isObject(ret[0])) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
if (isOnConflict && ret.length !== this.models.length) {
|
|
69
|
+
// With `onConflict()`, ignored rows (and rows not updated because of a
|
|
70
|
+
// `where` clause after `merge()`) are not returned, so the returned rows
|
|
71
|
+
// can't be merged to the models based on their position.
|
|
72
|
+
mergeMatchingRows(builder, this.models, ret);
|
|
73
|
+
} else {
|
|
74
|
+
// If the user specified a `returning` clause the result may be an array of objects.
|
|
75
|
+
// Merge all values of the objects to our models.
|
|
76
|
+
for (let i = 0, l = this.models.length; i < l; ++i) {
|
|
77
|
+
this.models[i].$setDatabaseJson(ret[i]);
|
|
78
|
+
}
|
|
59
79
|
}
|
|
60
80
|
} else {
|
|
61
81
|
// If the return value is not an array of objects, we assume it is an array of identifiers.
|
|
@@ -63,8 +83,9 @@ class InsertOperation extends QueryBuilderOperation {
|
|
|
63
83
|
const model = this.models[i];
|
|
64
84
|
|
|
65
85
|
// Don't set the id if the model already has one. MySQL and Sqlite don't return the correct
|
|
66
|
-
// primary key value if the id is not generated in db, but given explicitly.
|
|
67
|
-
|
|
86
|
+
// primary key value if the id is not generated in db, but given explicitly. With
|
|
87
|
+
// `onConflict()`, MySQL returns `0` for ignored rows, which is not a valid id.
|
|
88
|
+
if (!model.$id() && !(isOnConflict && !ret[i])) {
|
|
68
89
|
model.$id(ret[i]);
|
|
69
90
|
}
|
|
70
91
|
}
|
|
@@ -92,6 +113,94 @@ class InsertOperation extends QueryBuilderOperation {
|
|
|
92
113
|
}
|
|
93
114
|
}
|
|
94
115
|
|
|
116
|
+
function getDefaultReturning(builder) {
|
|
117
|
+
const idColumns = builder.modelClass().getIdColumnArray();
|
|
118
|
+
const columns = Array.from(new Set(idColumns.concat(getConflictColumns(builder))));
|
|
119
|
+
|
|
120
|
+
return columns.length === 1 ? columns[0] : columns;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getConflictColumns(builder) {
|
|
124
|
+
if (!builder.has('onConflict')) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const op = builder.findOperation('onConflict');
|
|
129
|
+
const arg = op.args ? op.args[0] : undefined;
|
|
130
|
+
const columns = Array.isArray(arg) ? arg : [arg];
|
|
131
|
+
|
|
132
|
+
// Raw conflict targets (or no target at all) can't be used for matching.
|
|
133
|
+
return columns.every(isString) ? columns : [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Merges the rows returned by an insert with `onConflict()` into the models with
|
|
137
|
+
// matching key values. Models whose rows were not returned are left untouched.
|
|
138
|
+
function mergeMatchingRows(builder, models, rows) {
|
|
139
|
+
const jsons = models.map((model) => model.$toDatabaseJson(builder));
|
|
140
|
+
const idColumns = builder.modelClass().getIdColumnArray();
|
|
141
|
+
|
|
142
|
+
// Use the conflict columns, or else the identifiers, to match the rows to
|
|
143
|
+
// the models. The columns must be present in the input and the result.
|
|
144
|
+
const keyColumns = [getConflictColumns(builder), idColumns].find((columns) => {
|
|
145
|
+
return (
|
|
146
|
+
columns.length > 0 &&
|
|
147
|
+
columns.every((col) => {
|
|
148
|
+
return (
|
|
149
|
+
rows.every((row) => row[col] !== undefined) &&
|
|
150
|
+
jsons.every((json) => json[col] !== undefined)
|
|
151
|
+
);
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!keyColumns) {
|
|
157
|
+
throw createMatchError();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const createKey = (obj) => JSON.stringify(keyColumns.map((col) => normalizeKey(obj[col])));
|
|
161
|
+
const indicesByKey = new Map();
|
|
162
|
+
|
|
163
|
+
jsons.forEach((json, index) => {
|
|
164
|
+
const key = createKey(json);
|
|
165
|
+
const indices = indicesByKey.get(key);
|
|
166
|
+
|
|
167
|
+
if (indices) {
|
|
168
|
+
indices.push(index);
|
|
169
|
+
} else {
|
|
170
|
+
indicesByKey.set(key, [index]);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
const indices = indicesByKey.get(createKey(row));
|
|
176
|
+
const index = indices ? indices.shift() : undefined;
|
|
177
|
+
|
|
178
|
+
if (index === undefined) {
|
|
179
|
+
throw createMatchError();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
models[index].$setDatabaseJson(row);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeKey(value) {
|
|
187
|
+
if (value === null || value === undefined) {
|
|
188
|
+
return null;
|
|
189
|
+
} else if (isObject(value)) {
|
|
190
|
+
return JSON.stringify(value);
|
|
191
|
+
} else {
|
|
192
|
+
return String(value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function createMatchError() {
|
|
197
|
+
return new Error(
|
|
198
|
+
'Could not match the rows returned by an insert with `onConflict()` to the inserted models. ' +
|
|
199
|
+
'Pass the conflict columns to `onConflict()` and make sure they are returned, or provide ' +
|
|
200
|
+
'the identifiers of the inserted models.',
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
95
204
|
function callBeforeInsert(builder, models) {
|
|
96
205
|
const maybePromise = callInstanceBeforeInsert(builder, models);
|
|
97
206
|
return after(maybePromise, () => callStaticBeforeInsert(builder));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
4
|
+
|
|
5
|
+
const AggregateSelector =
|
|
6
|
+
/^(count|countDistinct|min|max|sum|sumDistinct|avg|avgDistinct|groupBy\w*)$/;
|
|
7
|
+
|
|
8
|
+
class NoneOperation extends QueryBuilderOperation {
|
|
9
|
+
onBefore1(builder, result) {
|
|
10
|
+
// Resolve the query without executing it, like `cancelQuery()` does.
|
|
11
|
+
if (builder.isFind()) {
|
|
12
|
+
// Aggregate queries are executed to get the result the database
|
|
13
|
+
// returns for no rows, e.g. `[{ count: 0 }]`.
|
|
14
|
+
if (!builder.has(AggregateSelector)) {
|
|
15
|
+
builder.resolve([]);
|
|
16
|
+
}
|
|
17
|
+
} else if (builder.isUpdate() || builder.isDelete()) {
|
|
18
|
+
builder.resolve(builder.has(/returning/) ? [] : 0);
|
|
19
|
+
} else {
|
|
20
|
+
throw new Error('none() can only be used with find, update and delete queries');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Called by `buildKnexQuery()` after all other operations have been
|
|
27
|
+
// built, so that the query matches no rows when it does get executed,
|
|
28
|
+
// for example as a subquery or in `resultSize()`. The existing where
|
|
29
|
+
// clauses are removed so that `orWhere()` calls can't match any rows.
|
|
30
|
+
onAfterBuildKnex(knexBuilder) {
|
|
31
|
+
return knexBuilder.clearWhere().where(false);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
NoneOperation,
|
|
37
|
+
};
|
|
@@ -106,6 +106,11 @@ function convertArray(arr, builder, i, opName, skipUndefined) {
|
|
|
106
106
|
`undefined passed as an item in argument #${i} for '${opName}' operation. Call skipUndefined() method to ignore the undefined values.`,
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
|
+
} else if (opName === 'orderBy' && isObject(item)) {
|
|
110
|
+
// knex's `orderBy()` only reads `{ column, order, nulls }` objects from arrays,
|
|
111
|
+
// so wrap refs, raws and query builders in one and convert its properties.
|
|
112
|
+
const obj = isPlainObject(item) ? item : { column: item };
|
|
113
|
+
return convertPlainObject(obj, builder, i, opName, skipUndefined);
|
|
109
114
|
} else if (hasToKnexRawMethod(item)) {
|
|
110
115
|
return convertToKnexRaw(item, builder);
|
|
111
116
|
} else if (isObjectionQueryBuilderBase(item)) {
|
|
@@ -41,6 +41,9 @@ class QueryBuilderOperation {
|
|
|
41
41
|
|
|
42
42
|
// Operations this operation added in any of its hooks.
|
|
43
43
|
this.childOperations = [];
|
|
44
|
+
|
|
45
|
+
// The where group this operation belongs to. See `whereGroups.js`.
|
|
46
|
+
this.whereGroup = null;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
is(OperationClass) {
|
|
@@ -217,6 +220,7 @@ class QueryBuilderOperation {
|
|
|
217
220
|
|
|
218
221
|
clone.adderHookName = this.adderHookName;
|
|
219
222
|
clone.parentOperation = this.parentOperation;
|
|
223
|
+
clone.whereGroup = this.whereGroup;
|
|
220
224
|
|
|
221
225
|
clone.childOperations = this.childOperations.map((childOp) => {
|
|
222
226
|
const childOpClone = childOp.clone();
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { flatten } = require('../../utils/objectUtils');
|
|
3
|
+
const { flatten, isPlainObject } = require('../../utils/objectUtils');
|
|
4
4
|
const { ObjectionToKnexConvertingOperation } = require('./ObjectionToKnexConvertingOperation');
|
|
5
5
|
|
|
6
|
-
// This class's
|
|
6
|
+
// This class's purpose is to normalize the arguments into an array and to
|
|
7
|
+
// pass an optional trailing options object (e.g. `includeTriggerModifications`)
|
|
8
|
+
// on to knex.
|
|
7
9
|
//
|
|
8
10
|
// In knex, if a single column is given to `returning` it returns an array with the that column's value
|
|
9
11
|
// in it. If an array is given with a one item inside, the return value is an object.
|
|
10
12
|
class ReturningOperation extends ObjectionToKnexConvertingOperation {
|
|
13
|
+
constructor(name, opt) {
|
|
14
|
+
super(name, opt);
|
|
15
|
+
this.options = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
onAdd(builder, args) {
|
|
19
|
+
args = Array.from(args);
|
|
20
|
+
|
|
21
|
+
if (args.length > 1 && isPlainObject(args[args.length - 1])) {
|
|
22
|
+
this.options = args.pop();
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
args = flatten(args);
|
|
13
26
|
|
|
14
27
|
// Don't add an empty returning list.
|
|
@@ -21,7 +34,19 @@ class ReturningOperation extends ObjectionToKnexConvertingOperation {
|
|
|
21
34
|
|
|
22
35
|
onBuildKnex(knexBuilder, builder) {
|
|
23
36
|
// Always pass an array of columns to knex.returning.
|
|
24
|
-
|
|
37
|
+
const columns = this.getKnexArgs(builder);
|
|
38
|
+
|
|
39
|
+
if (this.options) {
|
|
40
|
+
return knexBuilder.returning(columns, this.options);
|
|
41
|
+
} else {
|
|
42
|
+
return knexBuilder.returning(columns);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
clone() {
|
|
47
|
+
const clone = super.clone();
|
|
48
|
+
clone.options = this.options;
|
|
49
|
+
return clone;
|
|
25
50
|
}
|
|
26
51
|
}
|
|
27
52
|
|
|
@@ -14,6 +14,7 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
14
14
|
|
|
15
15
|
this.id = null;
|
|
16
16
|
this.skipIdWhere = false;
|
|
17
|
+
this.selectOperations = null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
get model() {
|
|
@@ -25,6 +26,19 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
25
26
|
return this.delegate.onAdd(builder, args.slice(1));
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
onBefore3(builder, result) {
|
|
30
|
+
// Selects have no effect on the update query, so remember them here to
|
|
31
|
+
// apply them to the fetch query. Collect them before the query is built,
|
|
32
|
+
// so that selects added by `onBuild` hooks in the shared context aren't
|
|
33
|
+
// copied, as those hooks run again for the fetch query.
|
|
34
|
+
this.selectOperations = new Set();
|
|
35
|
+
builder.forEachOperation(builder.constructor.SelectSelector, (op) => {
|
|
36
|
+
this.selectOperations.add(op);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return super.onBefore3(builder, result);
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
onBuild(builder) {
|
|
29
43
|
if (!this.skipIdWhere) {
|
|
30
44
|
builder.findById(this.id);
|
|
@@ -43,6 +57,7 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
43
57
|
const fetched = await builder
|
|
44
58
|
.emptyInstance()
|
|
45
59
|
.childQueryOf(builder)
|
|
60
|
+
.copyFrom(builder, (op) => !!this.selectOperations?.has(op))
|
|
46
61
|
.modify((builder) => {
|
|
47
62
|
if (!this.skipIdWhere) {
|
|
48
63
|
builder.findById(this.id);
|
|
@@ -52,6 +67,11 @@ class UpdateAndFetchOperation extends DelegateOperation {
|
|
|
52
67
|
|
|
53
68
|
if (fetched) {
|
|
54
69
|
this.model.$set(fetched);
|
|
70
|
+
// A custom `select` may not include the id. The row was fetched by
|
|
71
|
+
// id, so make sure the result still has it.
|
|
72
|
+
if (!this.model.$hasId()) {
|
|
73
|
+
this.model.$id(this.id);
|
|
74
|
+
}
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
await super.onAfter2(builder, numUpdated);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { ref } = require('../../queryBuilder/ReferenceBuilder');
|
|
4
4
|
const { isEmpty } = require('../../utils/objectUtils');
|
|
5
|
+
const { toPostgresJsonPathLiteral } = require('../../utils/parseFieldExpression');
|
|
5
6
|
const { isKnexRaw, isKnexQueryBuilder } = require('../../utils/knexUtils');
|
|
6
7
|
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
7
8
|
const { StaticHookArguments } = require('../StaticHookArguments');
|
|
@@ -104,7 +105,7 @@ function convertFieldExpressionsToRaw(builder, model, json) {
|
|
|
104
105
|
// "col" : raw(`jsonb_set("col", '{attr}', to_jsonb("other"#>'{lol}'), true)`)
|
|
105
106
|
|
|
106
107
|
let parsed = ref(key);
|
|
107
|
-
let
|
|
108
|
+
let jsonPath = toPostgresJsonPathLiteral(parsed.parsedExpr.access);
|
|
108
109
|
let valuePlaceholder = '?';
|
|
109
110
|
|
|
110
111
|
if (isKnexQueryBuilder(val) || isKnexRaw(val)) {
|
|
@@ -114,7 +115,7 @@ function convertFieldExpressionsToRaw(builder, model, json) {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
convertedJson[parsed.column] = knex.raw(
|
|
117
|
-
`jsonb_set(??,
|
|
118
|
+
`jsonb_set(??, ${jsonPath}, ${valuePlaceholder}, true)`,
|
|
118
119
|
[convertedJson[parsed.column] || parsed.column, val],
|
|
119
120
|
);
|
|
120
121
|
|
|
@@ -40,6 +40,11 @@ class UpsertGraphOperation extends QueryBuilderOperation {
|
|
|
40
40
|
return true;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
onBefore1(builder, result) {
|
|
44
|
+
GraphUpsert.warnOnConflict(builder, this.name);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
onAfter1(builder) {
|
|
44
49
|
if (hasOtherSqlModifyingQueryBuilderCalls(builder)) {
|
|
45
50
|
throw new Error(
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { QueryBuilderOperation } = require('./QueryBuilderOperation');
|
|
4
|
+
|
|
5
|
+
// Builds a set of where operations inside a single knex `where(callback)`
|
|
6
|
+
// group, i.e. wraps them in parentheses. See `wrapWhereGroups`.
|
|
7
|
+
class WhereGroupOperation extends QueryBuilderOperation {
|
|
8
|
+
constructor(name, opt) {
|
|
9
|
+
super(name, opt);
|
|
10
|
+
this.operations = [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
onBuildKnex(knexBuilder, builder) {
|
|
14
|
+
const operations = this.operations;
|
|
15
|
+
|
|
16
|
+
return knexBuilder.where(function () {
|
|
17
|
+
let knex = this;
|
|
18
|
+
|
|
19
|
+
const build = (op) => {
|
|
20
|
+
if (op.hasOnBuildKnex()) {
|
|
21
|
+
knex = builder.callOperationMethod(op, 'onBuildKnex', [knex, builder]) || knex;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (const op of operations) {
|
|
26
|
+
build(op);
|
|
27
|
+
op.forEachDescendantOperation(build);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clone() {
|
|
33
|
+
const clone = super.clone();
|
|
34
|
+
clone.operations = this.operations.map((op) => op.clone());
|
|
35
|
+
return clone;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
WhereGroupOperation,
|
|
41
|
+
};
|
|
@@ -8,14 +8,16 @@ class EagerOperation extends QueryBuilderOperation {
|
|
|
8
8
|
super(name, opt);
|
|
9
9
|
|
|
10
10
|
this.expression = RelationExpression.create();
|
|
11
|
-
this.modifiersAtPath = [];
|
|
12
11
|
this.graphOptions = this.opt.defaultGraphOptions;
|
|
12
|
+
// The `joinOperation` options passed to the `withGraphJoined` calls,
|
|
13
|
+
// together with the expressions of the calls.
|
|
14
|
+
this.joinOperations = [];
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
buildFinalExpression() {
|
|
17
|
+
buildFinalExpression(builder) {
|
|
16
18
|
const expression = this.expression.clone();
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
builder.graphModifiersAtPath().forEach((modifier, i) => {
|
|
19
21
|
const modifierName = getModifierName(i);
|
|
20
22
|
|
|
21
23
|
expression.expressionsAtPath(modifier.path).forEach((expr) => {
|
|
@@ -30,7 +32,7 @@ class EagerOperation extends QueryBuilderOperation {
|
|
|
30
32
|
// `modifiers()` returns a clone so we can modify it.
|
|
31
33
|
const modifiers = builder.modifiers();
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
builder.graphModifiersAtPath().forEach((modifier, i) => {
|
|
34
36
|
const modifierName = getModifierName(i);
|
|
35
37
|
|
|
36
38
|
modifiers[modifierName] = modifier.modifier;
|
|
@@ -41,8 +43,8 @@ class EagerOperation extends QueryBuilderOperation {
|
|
|
41
43
|
|
|
42
44
|
cloneFrom(eagerOp) {
|
|
43
45
|
this.expression = eagerOp.expression.clone();
|
|
44
|
-
this.modifiersAtPath = eagerOp.modifiersAtPath.slice();
|
|
45
46
|
this.graphOptions = Object.assign({}, eagerOp.graphOptions);
|
|
47
|
+
this.joinOperations = eagerOp.joinOperations.slice();
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
clone() {
|
|
@@ -21,17 +21,19 @@ class JoinEagerOperation extends EagerOperation {
|
|
|
21
21
|
|
|
22
22
|
onBefore3(builder) {
|
|
23
23
|
return this.joiner
|
|
24
|
-
.setExpression(this.buildFinalExpression())
|
|
24
|
+
.setExpression(this.buildFinalExpression(builder))
|
|
25
25
|
.setModifiers(this.buildFinalModifiers(builder))
|
|
26
26
|
.setOptions(this.graphOptions)
|
|
27
|
+
.setJoinOperations(this.joinOperations)
|
|
27
28
|
.fetchColumnInfo(builder);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
onBuild(builder) {
|
|
31
32
|
this.joiner
|
|
32
|
-
.setExpression(this.buildFinalExpression())
|
|
33
|
+
.setExpression(this.buildFinalExpression(builder))
|
|
33
34
|
.setModifiers(this.buildFinalModifiers(builder))
|
|
34
35
|
.setOptions(this.graphOptions)
|
|
36
|
+
.setJoinOperations(this.joinOperations)
|
|
35
37
|
.build(builder);
|
|
36
38
|
}
|
|
37
39
|
|