objection 3.7.0 → 3.9.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 +123 -50
- package/lib/model/Model.js +2 -0
- package/lib/model/graph/ModelGraphNode.js +2 -1
- package/lib/model/modelJsonAttributes.js +20 -11
- package/lib/model/modelQueryProps.js +49 -5
- package/lib/model/modelSet.js +6 -8
- package/lib/model/modelToJson.js +17 -2
- package/lib/model/modelValidate.js +2 -1
- package/lib/model/modelValues.js +43 -50
- package/lib/queryBuilder/QueryBuilder.js +106 -38
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +15 -3
- package/lib/queryBuilder/StaticHookArguments.js +4 -1
- package/lib/queryBuilder/graph/GraphData.js +39 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +9 -1
- package/lib/queryBuilder/graph/GraphOptions.js +77 -38
- package/lib/queryBuilder/graph/GraphUpsert.js +98 -14
- package/lib/queryBuilder/graph/delete/GraphDelete.js +55 -2
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +13 -5
- package/lib/queryBuilder/graph/insert/GraphInsert.js +6 -1
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +3 -2
- package/lib/queryBuilder/join/JoinResultParser.js +13 -8
- package/lib/queryBuilder/join/RelationJoiner.js +105 -12
- package/lib/queryBuilder/join/TableNode.js +6 -68
- package/lib/queryBuilder/join/utils.js +2 -2
- package/lib/queryBuilder/operations/DelegateOperation.js +22 -0
- package/lib/queryBuilder/operations/DeleteOperation.js +13 -0
- package/lib/queryBuilder/operations/FirstOperation.js +5 -3
- package/lib/queryBuilder/operations/InsertGraphOperation.js +1 -0
- package/lib/queryBuilder/operations/InsertOperation.js +2 -2
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +2 -1
- package/lib/queryBuilder/operations/InstanceFindOperation.js +2 -1
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +4 -1
- package/lib/queryBuilder/operations/LimitPerParentOperation.js +152 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +31 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +22 -5
- package/lib/queryBuilder/operations/UpdateOperation.js +54 -5
- 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 +3 -3
- package/lib/queryBuilder/operations/select/SelectOperation.js +8 -4
- package/lib/queryBuilder/operations/select/Selection.js +39 -16
- package/lib/queryBuilder/parsers/relationExpressionParser.js +5 -3
- package/lib/queryBuilder/parsers/relationExpressionParser.pegjs +6 -2
- package/lib/queryBuilder/whereGroups.js +35 -40
- package/lib/queryBuilder/withoutOrder.js +33 -0
- package/lib/relations/Relation.js +3 -0
- package/lib/relations/RelationFindOperation.js +13 -5
- package/lib/relations/RelationOwner.js +2 -5
- package/lib/relations/RelationProperty.js +3 -14
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +5 -1
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +31 -9
- package/lib/relations/manyToMany/ManyToManyRelation.js +7 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +7 -2
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +19 -12
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +123 -17
- package/lib/utils/identifierMapping.js +2 -2
- package/lib/utils/knexUtils.js +21 -0
- package/lib/utils/objectUtils.js +5 -0
- package/lib/utils/parseFieldExpression.js +9 -0
- 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 +31 -13
- package/lib/utils/deprecate.js +0 -21
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { RelationFindOperation } = require('../../RelationFindOperation');
|
|
4
4
|
const { getTempColumn } = require('../../../utils/tmpColumnUtils');
|
|
5
|
-
const {
|
|
5
|
+
const { warnOnce } = require('../../../utils/warnings');
|
|
6
6
|
|
|
7
7
|
class ManyToManyFindOperation extends RelationFindOperation {
|
|
8
8
|
constructor(name, opt) {
|
|
@@ -83,6 +83,11 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
83
83
|
return related;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// @override
|
|
87
|
+
ownerKeys() {
|
|
88
|
+
return this.ownerJoinColumnAlias;
|
|
89
|
+
}
|
|
90
|
+
|
|
86
91
|
// The related models are assigned to their owners by the owner join columns
|
|
87
92
|
// selected from the join table. If they get lost, e.g. in a custom
|
|
88
93
|
// `$parseDatabaseJson()`, the related models are silently dropped (#2258).
|
|
@@ -93,7 +98,7 @@ class ManyToManyFindOperation extends RelationFindOperation {
|
|
|
93
98
|
const alias = this.ownerJoinColumnAlias[c];
|
|
94
99
|
|
|
95
100
|
if (related.some((rel) => rel[alias] === undefined)) {
|
|
96
|
-
|
|
101
|
+
warnOnce(
|
|
97
102
|
`Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
|
|
98
103
|
`some related models are missing the join table column ` +
|
|
99
104
|
`"${joinTableOwnerProp.propDescription(c)}" selected as "${alias}", so they are not ` +
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { hasBatchInsert } = require('../../../utils/knexUtils');
|
|
3
4
|
const { RelationInsertOperation } = require('../../RelationInsertOperation');
|
|
4
5
|
const { ManyToManyRelateOperation } = require('../relate/ManyToManyRelateOperation');
|
|
5
6
|
|
|
@@ -19,13 +20,6 @@ class ManyToManyInsertOperation extends RelationInsertOperation {
|
|
|
19
20
|
async onAfter1(builder, ret) {
|
|
20
21
|
const inserted = await super.onAfter1(builder, ret);
|
|
21
22
|
|
|
22
|
-
const relateOp = new ManyToManyRelateOperation('relate', {
|
|
23
|
-
dontCopyReturning: true,
|
|
24
|
-
dontCopyOnConflict: true,
|
|
25
|
-
relation: this.relation,
|
|
26
|
-
owner: this.owner,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
23
|
const modelsToRelate = inserted.filter((it) => {
|
|
30
24
|
return this.relation.relatedProp.hasProps(it);
|
|
31
25
|
});
|
|
@@ -36,13 +30,26 @@ class ManyToManyInsertOperation extends RelationInsertOperation {
|
|
|
36
30
|
}
|
|
37
31
|
}
|
|
38
32
|
|
|
39
|
-
if (modelsToRelate.length
|
|
40
|
-
|
|
33
|
+
if (modelsToRelate.length > 0) {
|
|
34
|
+
// Without batch inserts, `insertGraph()` inserts the related models one at
|
|
35
|
+
// a time, and so we need to do the same with their join rows:
|
|
36
|
+
const batches = hasBatchInsert(builder.knex())
|
|
37
|
+
? [modelsToRelate]
|
|
38
|
+
: modelsToRelate.map((model) => [model]);
|
|
39
|
+
|
|
40
|
+
for (const batch of batches) {
|
|
41
|
+
const relateOp = new ManyToManyRelateOperation('relate', {
|
|
42
|
+
dontCopyReturning: true,
|
|
43
|
+
dontCopyOnConflict: true,
|
|
44
|
+
relation: this.relation,
|
|
45
|
+
owner: this.owner,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
relateOp.onAdd(builder, [batch]);
|
|
49
|
+
await relateOp.queryExecutor(builder);
|
|
50
|
+
}
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
relateOp.onAdd(builder, [modelsToRelate]);
|
|
44
|
-
await relateOp.queryExecutor(builder);
|
|
45
|
-
|
|
46
53
|
return inserted;
|
|
47
54
|
}
|
|
48
55
|
}
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const { UpdateOperation } = require('../../../queryBuilder/operations/UpdateOperation');
|
|
4
4
|
const { addWhereGroup } = require('../../../queryBuilder/whereGroups');
|
|
5
|
+
const { chunk } = require('../../../utils/objectUtils');
|
|
6
|
+
const { isMySql, isMsSql } = require('../../../utils/knexUtils');
|
|
7
|
+
|
|
8
|
+
// Stay below the limits of bound values per statement of SQLite before 3.32
|
|
9
|
+
// (999) and SQL Server (2100), leaving room for the patched values:
|
|
10
|
+
const maxBatchValues = 500;
|
|
11
|
+
|
|
12
|
+
const LimitSelector = /^(orderBy\w*|limit|offset)$/;
|
|
5
13
|
|
|
6
14
|
class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
7
15
|
constructor(name, opt) {
|
|
@@ -11,6 +19,8 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
11
19
|
this.owner = opt.owner;
|
|
12
20
|
|
|
13
21
|
this.hasExtraProps = false;
|
|
22
|
+
this.relatedIdsQuery = null;
|
|
23
|
+
this.relatedIds = null;
|
|
14
24
|
this.joinTablePatch = {};
|
|
15
25
|
this.joinTablePatchFilterQuery = null;
|
|
16
26
|
}
|
|
@@ -50,32 +60,126 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
50
60
|
return res;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
onBuild(builder) {
|
|
64
|
+
if (this.modifiesJoinTable && this.modifyFilterSubquery) {
|
|
65
|
+
// MySQL and SQL Server are the only databases that support a limit in
|
|
66
|
+
// updates. As the related rows are updated by their ids, the limit and
|
|
67
|
+
// the order pick these ids instead of limiting each batch of them.
|
|
68
|
+
// Without a limit or an offset, the order picks nothing and the query
|
|
69
|
+
// stays as it is.
|
|
70
|
+
const knex = builder.knex();
|
|
71
|
+
const hasLimit = (isMySql(knex) || isMsSql(knex)) && builder.has(/^(limit|offset)$/);
|
|
72
|
+
|
|
73
|
+
// The related rows to update are identified by their ids first, see
|
|
74
|
+
// `onAfterBuild()`.
|
|
75
|
+
this.relatedIdsQuery = this.createRelatedIdsQuery(builder, hasLimit);
|
|
76
|
+
if (hasLimit) {
|
|
77
|
+
this.relatedIdsQuery.copyFrom(builder, LimitSelector);
|
|
78
|
+
builder.clear(LimitSelector);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return super.onBuild(builder);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Selects the ids of the related rows to update. Fetch raw rows, so that no
|
|
86
|
+
// hooks run for them.
|
|
87
|
+
createRelatedIdsQuery(builder, hasLimit) {
|
|
88
|
+
const { relatedModelClass } = this.relation;
|
|
89
|
+
|
|
90
|
+
if (hasLimit) {
|
|
91
|
+
// The limit picks related rows, not join rows, so filter the related
|
|
92
|
+
// table by the ids of the matching join rows:
|
|
93
|
+
const relatedIdsQuery = relatedModelClass.query().childQueryOf(builder);
|
|
94
|
+
const idRefs = relatedModelClass.getIdRelationProperty().refs(relatedIdsQuery);
|
|
95
|
+
return this.applyModifyFilterForRelatedTable(relatedIdsQuery).select(idRefs);
|
|
96
|
+
} else {
|
|
97
|
+
// Select the ids from the filter subquery itself. Filtering the related
|
|
98
|
+
// table by them again lets Postgres pick plans that run the subquery
|
|
99
|
+
// once per related row, which takes minutes for a few thousand (#161).
|
|
100
|
+
const relatedIdsQuery = this.modifyFilterSubquery.clone();
|
|
101
|
+
const idRefs = relatedModelClass.getIdRelationProperty().refs(relatedIdsQuery);
|
|
102
|
+
return relatedIdsQuery.distinct(idRefs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Update the join rows before the related rows, while the filters still
|
|
107
|
+
// match them (#161). A query resolved without updating anything, e.g. with
|
|
108
|
+
// `none()`, leaves the join rows untouched too.
|
|
109
|
+
async onAfterBuild(builder) {
|
|
56
110
|
if (this.hasExtraProps && !this.isResolvedWithoutUpdate(builder)) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
111
|
+
if (this.relatedIdsQuery) {
|
|
112
|
+
// The filters can also reference the extras that the join rows change,
|
|
113
|
+
// so identify the related rows to modify first.
|
|
114
|
+
const rows = await this.relatedIdsQuery.toKnexQuery();
|
|
115
|
+
this.relatedIds = rows.map((row) => Object.values(row));
|
|
116
|
+
}
|
|
62
117
|
|
|
118
|
+
let numJoinRowsUpdated;
|
|
119
|
+
if (this.relatedIdsQuery?.has(/^(limit|offset)$/)) {
|
|
120
|
+
// The filters match the join rows of more related rows than the limit
|
|
121
|
+
// picks, so only update the join rows of the picked ones.
|
|
122
|
+
for (const ids of this.relatedIdBatches()) {
|
|
123
|
+
await this.updateJoinRows(builder, ids);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
numJoinRowsUpdated = await this.updateJoinRows(builder);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (this.hasNothingToUpdate) {
|
|
130
|
+
// Only the join rows change, so count the related rows that they
|
|
131
|
+
// belong to, as the update of the related rows does. Without their
|
|
132
|
+
// ids, the join rows all belong to the updated model, see
|
|
133
|
+
// `applyIdFilterForJoinTable()`.
|
|
134
|
+
builder.resolve(this.relatedIds?.length ?? Math.min(numJoinRowsUpdated, 1));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
updateJoinRows(builder, relatedIds = null) {
|
|
140
|
+
const joinTableUpdateQuery = this.relation
|
|
141
|
+
.getJoinModelClass(builder.knex())
|
|
142
|
+
.query()
|
|
143
|
+
.childQueryOf(builder)
|
|
144
|
+
.patch(this.joinTablePatch);
|
|
145
|
+
|
|
146
|
+
addWhereGroup(joinTableUpdateQuery, () =>
|
|
147
|
+
this.skipRelationFilter
|
|
148
|
+
? this.applyIdFilterForJoinTable(joinTableUpdateQuery)
|
|
149
|
+
: this.applyModifyFilterForJoinTable(joinTableUpdateQuery),
|
|
150
|
+
);
|
|
151
|
+
if (relatedIds) {
|
|
63
152
|
addWhereGroup(joinTableUpdateQuery, () =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
addWhereGroup(joinTableUpdateQuery, () =>
|
|
69
|
-
joinTableUpdateQuery.modify(this.relation.joinTableModify),
|
|
153
|
+
joinTableUpdateQuery.whereInComposite(
|
|
154
|
+
this.relation.joinTableRelatedProp.refs(joinTableUpdateQuery),
|
|
155
|
+
relatedIds,
|
|
156
|
+
),
|
|
70
157
|
);
|
|
158
|
+
}
|
|
159
|
+
addWhereGroup(joinTableUpdateQuery, () =>
|
|
160
|
+
joinTableUpdateQuery.modify(this.relation.joinTableModify),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
return joinTableUpdateQuery;
|
|
164
|
+
}
|
|
71
165
|
|
|
72
|
-
|
|
73
|
-
|
|
166
|
+
// Update the related rows in batches of their ids, as databases limit the
|
|
167
|
+
// number of values in one statement.
|
|
168
|
+
queryBatches(builder) {
|
|
169
|
+
if (this.relatedIds) {
|
|
170
|
+
const batches = this.relatedIdBatches();
|
|
171
|
+
// Without ids, still execute the query once for the dialect's result:
|
|
172
|
+
return batches.length > 0 ? batches : [[]];
|
|
74
173
|
} else {
|
|
75
|
-
return
|
|
174
|
+
return null;
|
|
76
175
|
}
|
|
77
176
|
}
|
|
78
177
|
|
|
178
|
+
relatedIdBatches() {
|
|
179
|
+
const idSize = this.relation.relatedModelClass.getIdRelationProperty().size;
|
|
180
|
+
return chunk(this.relatedIds, Math.floor(maxBatchValues / idSize));
|
|
181
|
+
}
|
|
182
|
+
|
|
79
183
|
// Filters the join rows by the ids of the owner and the updated model.
|
|
80
184
|
applyIdFilterForJoinTable(builder) {
|
|
81
185
|
return builder
|
|
@@ -103,6 +207,8 @@ class ManyToManyUpdateOperationBase extends UpdateOperation {
|
|
|
103
207
|
const clone = super.clone();
|
|
104
208
|
|
|
105
209
|
clone.hasExtraProps = this.hasExtraProps;
|
|
210
|
+
clone.relatedIdsQuery = this.relatedIdsQuery;
|
|
211
|
+
clone.relatedIds = this.relatedIds;
|
|
106
212
|
clone.joinTablePatch = this.joinTablePatch;
|
|
107
213
|
clone.joinTablePatchFilterQuery = this.joinTablePatchFilterQuery;
|
|
108
214
|
|
|
@@ -171,8 +171,8 @@ function keyMapper(mapper) {
|
|
|
171
171
|
// Returns a function that only calls `mapper` for the part of the input string
|
|
172
172
|
// before the first `separator`, and leaves the rest untouched. For field
|
|
173
173
|
// expressions like `jsonColumn:some.jsonPath` this maps the column but keeps
|
|
174
|
-
// the json path as is.
|
|
175
|
-
// the boundary
|
|
174
|
+
// the json path as is. The mapper doesn't know the schema, so the first `:`
|
|
175
|
+
// marks the boundary also in a declared column name like `ns:name`.
|
|
176
176
|
function mapFirstPart(mapper, separator) {
|
|
177
177
|
return (str) => {
|
|
178
178
|
const idx = str.indexOf(separator);
|
package/lib/utils/knexUtils.js
CHANGED
|
@@ -36,6 +36,25 @@ function isMsSql(knex) {
|
|
|
36
36
|
return getDialect(knex) === 'mssql';
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Only Postgres and SQL Server return the identifiers of all the rows inserted
|
|
40
|
+
// by one query.
|
|
41
|
+
function hasBatchInsert(knex) {
|
|
42
|
+
return isPostgres(knex) || isMsSql(knex);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// MySQL supports window functions since 8.0, MariaDB since 10.2. Without a
|
|
46
|
+
// version, knex assumes the latest one, and so do we.
|
|
47
|
+
function hasWindowFunctions(knex) {
|
|
48
|
+
const { version, isMariaDB } = knex.client;
|
|
49
|
+
|
|
50
|
+
if (isMySql(knex) && version) {
|
|
51
|
+
const [major, minor] = version.split('.').map(Number);
|
|
52
|
+
return isMariaDB ? major > 10 || (major === 10 && minor >= 2) : major >= 8;
|
|
53
|
+
} else {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
function isKnexQueryBuilder(value) {
|
|
40
59
|
return (
|
|
41
60
|
hasConstructor(value) &&
|
|
@@ -69,6 +88,8 @@ module.exports = {
|
|
|
69
88
|
isSqlite,
|
|
70
89
|
isMsSql,
|
|
71
90
|
isOracle,
|
|
91
|
+
hasBatchInsert,
|
|
92
|
+
hasWindowFunctions,
|
|
72
93
|
isKnexQueryBuilder,
|
|
73
94
|
isKnexJoinBuilder,
|
|
74
95
|
isKnexRaw,
|
package/lib/utils/objectUtils.js
CHANGED
|
@@ -16,6 +16,10 @@ function isObject(value) {
|
|
|
16
16
|
return value !== null && typeof value === 'object';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function hasOwn(obj, prop) {
|
|
20
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
// Quick and dirty check if an object is a plain object and not
|
|
20
24
|
// for example an instance of some class.
|
|
21
25
|
function isPlainObject(value) {
|
|
@@ -502,6 +506,7 @@ module.exports = {
|
|
|
502
506
|
isString,
|
|
503
507
|
isRegExp,
|
|
504
508
|
isObject,
|
|
509
|
+
hasOwn,
|
|
505
510
|
isNumber,
|
|
506
511
|
isFunction,
|
|
507
512
|
jsonEquals,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const jsonFieldExpressionParser = require('../queryBuilder/parsers/jsonFieldExpressionParser');
|
|
4
|
+
const { isObject, hasOwn } = require('./objectUtils');
|
|
4
5
|
const cache = new Map();
|
|
5
6
|
|
|
6
7
|
function parseFieldExpression(expr) {
|
|
@@ -46,6 +47,13 @@ function freezeParsedExpr(parsedExpr) {
|
|
|
46
47
|
return parsedExpr;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// Keys with a ':' in the data of patches and updates are field expressions like
|
|
51
|
+
// `'meta:a.b'` that write into json columns, unless they are `declared`: the
|
|
52
|
+
// `properties` of the schema, or an object with their column names as keys.
|
|
53
|
+
function isFieldExpressionKey(key, declared) {
|
|
54
|
+
return key.includes(':') && !(isObject(declared) && hasOwn(declared, key));
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
// Formats the json path of a parsed field expression as a Postgres text array
|
|
50
58
|
// literal, e.g. `{a,"",b}`. Keys that would otherwise be misread (empty keys,
|
|
51
59
|
// keys with whitespace, delimiters, quotes or backslashes and `NULL`) are
|
|
@@ -81,6 +89,7 @@ function toPostgresJsonPathLiteral(access) {
|
|
|
81
89
|
|
|
82
90
|
module.exports = {
|
|
83
91
|
parseFieldExpression,
|
|
92
|
+
isFieldExpressionKey,
|
|
84
93
|
toPostgresJsonPath,
|
|
85
94
|
toPostgresJsonPathLiteral,
|
|
86
95
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const LOGGED_WARNINGS = new Set();
|
|
4
|
+
|
|
5
|
+
// Log each warning only once.
|
|
6
|
+
function warnOnce(message) {
|
|
7
|
+
if (!LOGGED_WARNINGS.has(message)) {
|
|
8
|
+
LOGGED_WARNINGS.add(message);
|
|
9
|
+
console.warn(`[objection] ${message}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function deprecate(message) {
|
|
14
|
+
warnOnce(message);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Only meant for tests that need to see a warning again.
|
|
18
|
+
function resetWarnings() {
|
|
19
|
+
LOGGED_WARNINGS.clear();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
warnOnce,
|
|
24
|
+
deprecate,
|
|
25
|
+
resetWarnings,
|
|
26
|
+
};
|
package/lib/utils/wrapDbError.js
CHANGED
|
@@ -2,35 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
const { wrapError, DBError, UniqueViolationError } = require('db-errors');
|
|
4
4
|
|
|
5
|
-
// db-errors only recognizes mssql unique key violations, but
|
|
6
|
-
//
|
|
5
|
+
// db-errors only recognizes mssql unique key violations, but primary key
|
|
6
|
+
// violations have the same error number, with a different message (#2688).
|
|
7
7
|
const MSSQL_PRIMARY_KEY_REGEX =
|
|
8
8
|
/Violation of PRIMARY KEY constraint '(.+)'\. Cannot insert duplicate key in object '(.+)\.(.+)'\. The duplicate key value is \((.+)\)/;
|
|
9
9
|
|
|
10
10
|
function wrapDbError(err) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
if (!isTediousError(err)) {
|
|
12
|
+
return wrapError(err);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// db-errors parses the errors of the `mssql` package, which wraps the errors
|
|
16
|
+
// of tedious and keeps their number and class in `originalError.info`. knex
|
|
17
|
+
// uses tedious directly, so its errors are passed on in that shape.
|
|
18
|
+
const wrapped = wrapError({
|
|
19
|
+
code: err.code,
|
|
20
|
+
originalError: { message: err.message, info: { number: err.number, class: err.class } },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!(wrapped instanceof DBError)) {
|
|
24
|
+
return err;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
wrapped.nativeError = err;
|
|
28
|
+
|
|
29
|
+
const match = wrapped.constructor === DBError && MSSQL_PRIMARY_KEY_REGEX.exec(err.message);
|
|
30
|
+
|
|
31
|
+
if (match) {
|
|
32
|
+
return new UniqueViolationError({
|
|
33
|
+
nativeError: err,
|
|
34
|
+
client: 'mssql',
|
|
35
|
+
table: match[3],
|
|
36
|
+
schema: match[2],
|
|
37
|
+
constraint: match[1],
|
|
38
|
+
});
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
return wrapped;
|
|
32
42
|
}
|
|
33
43
|
|
|
44
|
+
function isTediousError(err) {
|
|
45
|
+
return err?.code === 'EREQUEST' && Number.isInteger(err.number);
|
|
46
|
+
}
|
|
47
|
+
|
|
34
48
|
module.exports = {
|
|
35
49
|
wrapDbError,
|
|
36
50
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "objection",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"description": "An SQL-friendly ORM for Node.js",
|
|
5
5
|
"main": "lib/objection.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"test:fast": "mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --bail --exclude \"tests/unit/relations/files/**\"",
|
|
10
10
|
"test:typings": "tsc && prettier --check \"typings/**/*.ts\" \"tests/ts/**/*.ts\"",
|
|
11
11
|
"db:up": "docker compose up -d --wait",
|
|
12
|
-
"db:down": "docker compose down -v",
|
|
12
|
+
"db:down": "docker compose --profile mssql down -v",
|
|
13
13
|
"build:parsers": "npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/jsonFieldExpressionParser.js lib/queryBuilder/parsers/jsonFieldExpressionParser.pegjs && npx --yes peggy@5.1.0 --format commonjs -o lib/queryBuilder/parsers/relationExpressionParser.js lib/queryBuilder/parsers/relationExpressionParser.pegjs && prettier --write \"lib/queryBuilder/parsers/*Parser.js\"",
|
|
14
14
|
"prettier": "prettier --write \"{examples,lib,tests,testUtils,typings,doc}/**/*.{js,ts}\"",
|
|
15
15
|
"eslint": "eslint --format codeframe \"examples/**/*.js\" \"lib/**/*.js\" \"tests/**/*.js\" \"testUtils/**/*.js\" \"*.js\"",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"db-errors": "^0.2.3"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"knex": ">=2.3.0"
|
|
69
|
+
"knex": ">=2.3.0 <4"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/node": "^22.15.3",
|
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
"pg": "^8.15.6",
|
|
83
83
|
"prettier": "3.9.9",
|
|
84
84
|
"sqlite3": "^5.1.7",
|
|
85
|
-
"
|
|
85
|
+
"tedious": "^20.3.7",
|
|
86
|
+
"typescript": "^5.8.3",
|
|
86
87
|
"vitepress": "1.6.4"
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -374,20 +374,30 @@ declare namespace Objection {
|
|
|
374
374
|
|
|
375
375
|
/**
|
|
376
376
|
* Converts the query builder NQB to the same result kind as QB:
|
|
377
|
-
* array,
|
|
377
|
+
* array, page, single or maybe-single. Any other result type, e.g. after
|
|
378
378
|
* `castTo()` or `patch()`, falls back to QB, without narrowing.
|
|
379
|
+
* QB's model type M and result type R are separate parameters so that QB
|
|
380
|
+
* only appears in the result, and TypeScript can relate the kinds of two
|
|
381
|
+
* query builders, e.g. of a custom query builder and its base class, through
|
|
382
|
+
* QB alone. The page and `object` checks come before the single ones, so
|
|
383
|
+
* that results like `number` or `Page<M>` are ruled out for a generic M too.
|
|
379
384
|
*/
|
|
380
|
-
type WithResultKind<
|
|
381
|
-
|
|
382
|
-
|
|
385
|
+
type WithResultKind<
|
|
386
|
+
QB extends AnyQueryBuilder,
|
|
387
|
+
NQB extends AnyQueryBuilder,
|
|
388
|
+
M extends Model = ModelType<QB>,
|
|
389
|
+
R = ResultType<QB>,
|
|
390
|
+
> = [R] extends [M[]]
|
|
383
391
|
? ArrayQueryBuilder<NQB>
|
|
384
|
-
: [
|
|
385
|
-
?
|
|
386
|
-
: [
|
|
387
|
-
?
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
: [R] extends [Page<M>]
|
|
393
|
+
? PageQueryBuilder<NQB>
|
|
394
|
+
: [R] extends [object | undefined]
|
|
395
|
+
? [R] extends [M]
|
|
396
|
+
? SingleQueryBuilder<NQB>
|
|
397
|
+
: [R] extends [M | undefined]
|
|
398
|
+
? MaybeSingleQueryBuilder<NQB>
|
|
399
|
+
: QB
|
|
400
|
+
: QB;
|
|
391
401
|
|
|
392
402
|
/**
|
|
393
403
|
* If T is an array, returns the item type, otherwise returns T.
|
|
@@ -1022,8 +1032,11 @@ declare namespace Objection {
|
|
|
1022
1032
|
/**
|
|
1023
1033
|
* With `returning()`, `relate()` returns the inserted join rows for
|
|
1024
1034
|
* many-to-many relations, the updated related rows for has-many relations
|
|
1025
|
-
* and the updated owner rows for belongs-to-one relations.
|
|
1026
|
-
*
|
|
1035
|
+
* and the updated owner rows for belongs-to-one relations. MySQL has no
|
|
1036
|
+
* `returning`: there, many-to-many relations return the join rows built from
|
|
1037
|
+
* the input, without columns generated by the database, and the other
|
|
1038
|
+
* relations still return the number of affected rows. The typings can't tell
|
|
1039
|
+
* these apart, so the rows are typed as `any`.
|
|
1027
1040
|
*/
|
|
1028
1041
|
interface RelateReturningMethod<M extends Model> {
|
|
1029
1042
|
(...args: ReturningArgs): QueryBuilder<M, any[]>;
|
|
@@ -1085,6 +1098,10 @@ declare namespace Objection {
|
|
|
1085
1098
|
export interface InsertGraphOptions {
|
|
1086
1099
|
relate?: boolean | string | string[];
|
|
1087
1100
|
allowRefs?: boolean;
|
|
1101
|
+
/**
|
|
1102
|
+
* @deprecated Has no effect, since `insertGraph()` rejects `#unrelate` and
|
|
1103
|
+
* `#delete` markers. Removed in objection 4.0.
|
|
1104
|
+
*/
|
|
1088
1105
|
allowMarkers?: boolean;
|
|
1089
1106
|
}
|
|
1090
1107
|
|
|
@@ -1140,6 +1157,7 @@ declare namespace Objection {
|
|
|
1140
1157
|
aliases?: { [key: string]: string };
|
|
1141
1158
|
joinOperation?: string;
|
|
1142
1159
|
maxBatchSize?: number;
|
|
1160
|
+
limitPerParent?: boolean;
|
|
1143
1161
|
}
|
|
1144
1162
|
|
|
1145
1163
|
export interface WithGraphOptions extends GraphOptions {
|
package/lib/utils/deprecate.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const LOGGED_DEPRECATIONS = new Set();
|
|
4
|
-
|
|
5
|
-
function deprecate(message) {
|
|
6
|
-
// Only log deprecation messages once.
|
|
7
|
-
if (!LOGGED_DEPRECATIONS.has(message)) {
|
|
8
|
-
LOGGED_DEPRECATIONS.add(message);
|
|
9
|
-
console.warn(message);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Only meant for tests that need to see a deprecation message again.
|
|
14
|
-
function resetDeprecations() {
|
|
15
|
-
LOGGED_DEPRECATIONS.clear();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
module.exports = {
|
|
19
|
-
deprecate,
|
|
20
|
-
resetDeprecations,
|
|
21
|
-
};
|