objection 3.4.0 → 3.4.1

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.
@@ -616,9 +616,14 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
616
616
  // whereJson(Not)SupersetOf / whereJson(Not)SubsetOf are now supported by knex >= 1.0, but for now
617
617
  // objection handles them differently and only for postgres.
618
618
  // Changing them to utilize knex methods directly may require a major version bump and upgrade guide.
619
+ // On MySQL, the simple `(column, object | array)` form is passed on to knex (ditojs#113).
619
620
  whereJsonSupersetOf(...args) {
620
621
  return this.addOperation(
621
- new WhereJsonPostgresOperation('whereJsonSupersetOf', { operator: '@>', bool: 'and' }),
622
+ new WhereJsonPostgresOperation('whereJsonSupersetOf', {
623
+ operator: '@>',
624
+ bool: 'and',
625
+ knexMethod: 'whereJsonSupersetOf',
626
+ }),
622
627
  args,
623
628
  );
624
629
  }
@@ -629,7 +634,11 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
629
634
 
630
635
  orWhereJsonSupersetOf(...args) {
631
636
  return this.addOperation(
632
- new WhereJsonPostgresOperation('orWhereJsonSupersetOf', { operator: '@>', bool: 'or' }),
637
+ new WhereJsonPostgresOperation('orWhereJsonSupersetOf', {
638
+ operator: '@>',
639
+ bool: 'or',
640
+ knexMethod: 'whereJsonSupersetOf',
641
+ }),
633
642
  args,
634
643
  );
635
644
  }
@@ -640,13 +649,14 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
640
649
  operator: '@>',
641
650
  bool: 'and',
642
651
  prefix: 'not',
652
+ knexMethod: 'whereJsonNotSupersetOf',
643
653
  }),
644
654
  args,
645
655
  );
646
656
  }
647
657
 
648
658
  andWhereJsonNotSupersetOf(...args) {
649
- return this.andWhereJsonNotSupersetOf(...args);
659
+ return this.whereJsonNotSupersetOf(...args);
650
660
  }
651
661
 
652
662
  orWhereJsonNotSupersetOf(...args) {
@@ -655,6 +665,7 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
655
665
  operator: '@>',
656
666
  bool: 'or',
657
667
  prefix: 'not',
668
+ knexMethod: 'whereJsonNotSupersetOf',
658
669
  }),
659
670
  args,
660
671
  );
@@ -662,7 +673,11 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
662
673
 
663
674
  whereJsonSubsetOf(...args) {
664
675
  return this.addOperation(
665
- new WhereJsonPostgresOperation('whereJsonSubsetOf', { operator: '<@', bool: 'and' }),
676
+ new WhereJsonPostgresOperation('whereJsonSubsetOf', {
677
+ operator: '<@',
678
+ bool: 'and',
679
+ knexMethod: 'whereJsonSubsetOf',
680
+ }),
666
681
  args,
667
682
  );
668
683
  }
@@ -673,7 +688,11 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
673
688
 
674
689
  orWhereJsonSubsetOf(...args) {
675
690
  return this.addOperation(
676
- new WhereJsonPostgresOperation('orWhereJsonSubsetOf', { operator: '<@', bool: 'or' }),
691
+ new WhereJsonPostgresOperation('orWhereJsonSubsetOf', {
692
+ operator: '<@',
693
+ bool: 'or',
694
+ knexMethod: 'whereJsonSubsetOf',
695
+ }),
677
696
  args,
678
697
  );
679
698
  }
@@ -684,6 +703,7 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
684
703
  operator: '<@',
685
704
  bool: 'and',
686
705
  prefix: 'not',
706
+ knexMethod: 'whereJsonNotSubsetOf',
687
707
  }),
688
708
  args,
689
709
  );
@@ -699,6 +719,7 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
699
719
  operator: '<@',
700
720
  bool: 'or',
701
721
  prefix: 'not',
722
+ knexMethod: 'whereJsonNotSubsetOf',
702
723
  }),
703
724
  args,
704
725
  );
@@ -772,20 +793,34 @@ class QueryBuilderBase extends QueryBuilderOperationSupport {
772
793
  );
773
794
  }
774
795
 
796
+ // Not passed on to knex like `whereJsonSupersetOf()`, as MySQL's `json_contains()`
797
+ // also matches `{}` in arrays of objects.
775
798
  whereJsonIsArray(fieldExpression) {
776
- return this.whereJsonSupersetOf(fieldExpression, []);
799
+ return this.addOperation(
800
+ new WhereJsonPostgresOperation('whereJsonSupersetOf', { operator: '@>', bool: 'and' }),
801
+ [fieldExpression, []],
802
+ );
777
803
  }
778
804
 
779
805
  orWhereJsonIsArray(fieldExpression) {
780
- return this.orWhereJsonSupersetOf(fieldExpression, []);
806
+ return this.addOperation(
807
+ new WhereJsonPostgresOperation('orWhereJsonSupersetOf', { operator: '@>', bool: 'or' }),
808
+ [fieldExpression, []],
809
+ );
781
810
  }
782
811
 
783
812
  whereJsonIsObject(fieldExpression) {
784
- return this.whereJsonSupersetOf(fieldExpression, {});
813
+ return this.addOperation(
814
+ new WhereJsonPostgresOperation('whereJsonSupersetOf', { operator: '@>', bool: 'and' }),
815
+ [fieldExpression, {}],
816
+ );
785
817
  }
786
818
 
787
819
  orWhereJsonIsObject(fieldExpression) {
788
- return this.orWhereJsonSupersetOf(fieldExpression, {});
820
+ return this.addOperation(
821
+ new WhereJsonPostgresOperation('orWhereJsonSupersetOf', { operator: '@>', bool: 'or' }),
822
+ [fieldExpression, {}],
823
+ );
789
824
  }
790
825
 
791
826
  whereColumn(...args) {
@@ -5,6 +5,7 @@ const {
5
5
  toPostgresJsonPathLiteral,
6
6
  } = require('../utils/parseFieldExpression');
7
7
  const { isObject } = require('../utils/objectUtils');
8
+ const { warnIfNotPostgres } = require('./operations/jsonApi/postgresJsonApi');
8
9
 
9
10
  class ReferenceBuilder {
10
11
  constructor(expr) {
@@ -163,7 +164,13 @@ class ReferenceBuilder {
163
164
  }
164
165
 
165
166
  toKnexRaw(builder) {
166
- return builder.knex().raw(...this._createRawArgs(builder));
167
+ const knex = builder.knex();
168
+
169
+ if (this._parsedExpr.access.length > 0) {
170
+ warnIfNotPostgres(knex);
171
+ }
172
+
173
+ return knex.raw(...this._createRawArgs(builder));
167
174
  }
168
175
 
169
176
  _parseExpression(expr) {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { uniqBy } = require('../../utils/objectUtils');
4
4
  const { Selection } = require('../operations/select/Selection');
5
+ const { SelectOperation } = require('../operations/select/SelectOperation');
5
6
  const { createModifier } = require('../../utils/createModifier');
6
7
  const { map: mapPromise } = require('../../utils/promiseUtils');
7
8
  const { ValidationErrorType } = require('../../model/ValidationError');
@@ -124,7 +125,7 @@ class RelationJoiner {
124
125
  // also contain selects.
125
126
  userSelectQuery.modify(tableNode.relation.modify);
126
127
 
127
- if (selectsOnlyAliasesKeepingSelectAll(userSelectQuery.findAllSelections())) {
128
+ if (selectsOnlyAliasesKeepingSelectAll(findJoinSelections(userSelectQuery))) {
128
129
  // All columns are selected from the joined subquery in the main query,
129
130
  // so the subquery needs to select them too.
130
131
  subqueryToJoin.select(`${subqueryToJoin.tableRef()}.*`);
@@ -268,7 +269,7 @@ function ensureIdAndRelationPropsAreSelected({ builder, tableNode }) {
268
269
  ];
269
270
 
270
271
  const selectStrings = uniqBy(cols)
271
- .filter((col) => !builder.hasSelectionAs(col, col))
272
+ .filter((col) => !hasSelectionAs(builder, col))
272
273
  .map((col) => `${tableRef}.${col}`);
273
274
 
274
275
  return builder.select(selectStrings);
@@ -276,7 +277,7 @@ function ensureIdAndRelationPropsAreSelected({ builder, tableNode }) {
276
277
 
277
278
  function getSelectionsForNode({ builder, tableNode, userSelectQueries }) {
278
279
  const userSelectQuery = userSelectQueries.get(tableNode);
279
- const userSelections = userSelectQuery.findAllSelections();
280
+ const userSelections = findUserSelections(userSelectQuery, tableNode);
280
281
  const userSelectedAllColumns = isSelectAllSelectionSet(userSelections);
281
282
 
282
283
  let selections = [];
@@ -298,7 +299,7 @@ function getSelectionsForNode({ builder, tableNode, userSelectQueries }) {
298
299
  const idSelections = getIdSelectionsForNode({ tableNode });
299
300
 
300
301
  for (const idSelection of idSelections) {
301
- if (!userSelectQuery.hasSelectionAs(idSelection.column, idSelection.column)) {
302
+ if (!hasUserSelectionAs(userSelectQuery, tableNode, idSelection.column)) {
302
303
  selections.push(idSelection);
303
304
  internalSelections.push(idSelection);
304
305
  }
@@ -318,6 +319,54 @@ function getSelectionsForNode({ builder, tableNode, userSelectQueries }) {
318
319
  };
319
320
  }
320
321
 
322
+ // Like `builder.findAllSelections()`, but aggregates select their aliases
323
+ // instead of the columns they're passed. Aggregates without an alias are left
324
+ // out, as their column name differs between databases.
325
+ function findJoinSelections(builder) {
326
+ let selections = [];
327
+
328
+ builder.forEachOperation(true, (op) => {
329
+ if (op.constructor === SelectOperation) {
330
+ selections = selections.concat(op.aggregateSelections || op.selections);
331
+ }
332
+ });
333
+
334
+ return selections;
335
+ }
336
+
337
+ // Like `builder.hasSelectionAs(column, column)`, but ignoring the columns
338
+ // passed to aggregates, as they don't select them.
339
+ function hasSelectionAs(builder, column) {
340
+ if (builder.has(isAggregateOperation)) {
341
+ // Explicit, as a query with only aggregates doesn't select all columns.
342
+ return builder.clone().clear(isAggregateOperation).hasSelectionAs(column, column, true);
343
+ } else {
344
+ return builder.hasSelectionAs(column, column);
345
+ }
346
+ }
347
+
348
+ // Only the joined subqueries select aggregates by their aliases. The root
349
+ // query keeps treating the columns passed to them as selected.
350
+ function findUserSelections(builder, tableNode) {
351
+ if (tableNode.parentNode) {
352
+ return findJoinSelections(builder);
353
+ } else {
354
+ return builder.findAllSelections();
355
+ }
356
+ }
357
+
358
+ function hasUserSelectionAs(builder, tableNode, column) {
359
+ if (tableNode.parentNode) {
360
+ return hasSelectionAs(builder, column);
361
+ } else {
362
+ return builder.hasSelectionAs(column, column);
363
+ }
364
+ }
365
+
366
+ function isAggregateOperation(op) {
367
+ return op.constructor === SelectOperation && op.aggregateSelections !== null;
368
+ }
369
+
321
370
  function mapUserSelectionsFromSubqueryToMainQuery({ userSelections, tableNode }) {
322
371
  return userSelections.filter(isNotSelectAll).map((selection) => {
323
372
  return new Selection(
@@ -5,6 +5,7 @@ const { isEmpty } = require('../../utils/objectUtils');
5
5
  const { toPostgresJsonPathLiteral } = require('../../utils/parseFieldExpression');
6
6
  const { isKnexRaw, isKnexQueryBuilder } = require('../../utils/knexUtils');
7
7
  const { QueryBuilderOperation } = require('./QueryBuilderOperation');
8
+ const { warnIfNotPostgres } = require('./jsonApi/postgresJsonApi');
8
9
  const { StaticHookArguments } = require('../StaticHookArguments');
9
10
 
10
11
  class UpdateOperation extends QueryBuilderOperation {
@@ -103,6 +104,7 @@ function convertFieldExpressionsToRaw(builder, model, json) {
103
104
  if (key.indexOf(':') > -1) {
104
105
  // 'col:attr' : ref('other:lol') is transformed to
105
106
  // "col" : raw(`jsonb_set("col", '{attr}', to_jsonb("other"#>'{lol}'), true)`)
107
+ warnIfNotPostgres(knex);
106
108
 
107
109
  let parsed = ref(key);
108
110
  let jsonPath = toPostgresJsonPathLiteral(parsed.parsedExpr.access);
@@ -5,6 +5,7 @@ const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConver
5
5
 
6
6
  class WhereJsonHasPostgresOperation extends ObjectionToKnexConvertingOperation {
7
7
  onBuildKnex(knexBuilder, builder) {
8
+ jsonApi.warnIfNotPostgres(builder.knex());
8
9
  const args = this.getKnexArgs(builder);
9
10
 
10
11
  const rawArgs = jsonApi.whereJsonFieldRightStringArrayOnLeftQuery(
@@ -5,6 +5,7 @@ const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConver
5
5
 
6
6
  class WhereJsonNotObjectPostgresOperation extends ObjectionToKnexConvertingOperation {
7
7
  onBuildKnex(knexBuilder, builder) {
8
+ jsonApi.warnIfNotPostgres(builder.knex());
8
9
  return this.whereJsonNotObject(knexBuilder, builder.knex(), this.getKnexArgs(builder)[0]);
9
10
  }
10
11
 
@@ -2,9 +2,28 @@
2
2
 
3
3
  const jsonApi = require('./postgresJsonApi');
4
4
  const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
5
+ const { parseFieldExpression } = require('../../../utils/parseFieldExpression');
6
+ const { isPlainObject, isString, isFunction } = require('../../../utils/objectUtils');
7
+ const { isMySql } = require('../../../utils/knexUtils');
5
8
 
6
9
  class WhereJsonPostgresOperation extends ObjectionToKnexConvertingOperation {
7
10
  onBuildKnex(knexBuilder, builder) {
11
+ const knex = builder.knex();
12
+
13
+ if (isMySql(knex) && this.canDelegateToKnex(knexBuilder)) {
14
+ // On MySQL, knex's version of the method works (ditojs#113). The `or`
15
+ // variants are wrapped in `orWhere()`, as knex < 3 ignores their `or`.
16
+ const [column, value] = this.getKnexArgs(builder);
17
+ const { knexMethod } = this.opt;
18
+
19
+ if (this.opt.bool === 'or') {
20
+ return knexBuilder.orWhere((qb) => qb[knexMethod](column, value));
21
+ } else {
22
+ return knexBuilder[knexMethod](column, value);
23
+ }
24
+ }
25
+
26
+ jsonApi.warnIfNotPostgres(knex);
8
27
  const args = this.getKnexArgs(builder);
9
28
 
10
29
  const rawArgs = jsonApi.whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams(
@@ -22,6 +41,20 @@ class WhereJsonPostgresOperation extends ObjectionToKnexConvertingOperation {
22
41
 
23
42
  return knexBuilder;
24
43
  }
44
+
45
+ // Only the simple form with a plain column and a plain object or array value
46
+ // can be passed on to knex, as knex doesn't understand field expressions.
47
+ canDelegateToKnex(knexBuilder) {
48
+ const [column, value] = this.args;
49
+
50
+ return (
51
+ !!this.opt.knexMethod &&
52
+ isFunction(knexBuilder[this.opt.knexMethod]) &&
53
+ isString(column) &&
54
+ parseFieldExpression(column).access.length === 0 &&
55
+ (isPlainObject(value) || Array.isArray(value))
56
+ );
57
+ }
25
58
  }
26
59
 
27
60
  module.exports = {
@@ -2,7 +2,8 @@
2
2
 
3
3
  const parser = require('../../../utils/parseFieldExpression');
4
4
  const { asArray, isObject, isString } = require('../../../utils/objectUtils');
5
- const { isKnexRaw, isKnexQueryBuilder } = require('../../../utils/knexUtils');
5
+ const { isKnexRaw, isKnexQueryBuilder, isPostgres } = require('../../../utils/knexUtils');
6
+ const { deprecate } = require('../../../utils/deprecate');
6
7
 
7
8
  /**
8
9
  * @typedef {String} FieldExpression
@@ -44,6 +45,19 @@ const { isKnexRaw, isKnexQueryBuilder } = require('../../../utils/knexUtils');
44
45
  * not currently supported
45
46
  */
46
47
 
48
+ // JSON field expressions and the `whereJson*()` methods generate Postgres-only
49
+ // SQL (ditojs#113). As some of it happens to run on other databases, only warn.
50
+ // TODO: Consider throwing an error instead in objection 4.0.
51
+ function warnIfNotPostgres(knex) {
52
+ if (!isPostgres(knex)) {
53
+ deprecate(
54
+ 'JSON field expressions and the whereJson*() methods of objection are only supported ' +
55
+ 'on PostgreSQL. Use the JSON methods of knex like whereJsonPath() or jsonExtract() ' +
56
+ 'with other databases.',
57
+ );
58
+ }
59
+ }
60
+
47
61
  // Returns the SQL and bindings of a field expression. The column is passed as an
48
62
  // identifier binding (`??`), so that knex's `wrapIdentifier` (e.g. used by
49
63
  // `knexSnakeCaseMappers()`) is applied to it.
@@ -168,6 +182,7 @@ function normalizeOperator(knex, operator) {
168
182
  }
169
183
 
170
184
  module.exports = {
185
+ warnIfNotPostgres,
171
186
  parseFieldExpression: parseFieldExpression,
172
187
  whereJsonbRefOnLeftJsonbValOrRefOnRight: whereJsonbRefOnLeftJsonbValOrRefOnRight,
173
188
  whereJsonbRefOnLeftJsonbValOrRefOnRightRawQueryParams:
@@ -1,15 +1,30 @@
1
1
  'use strict';
2
2
 
3
- const { flatten } = require('../../../utils/objectUtils');
3
+ const { flatten, isString, isPlainObject } = require('../../../utils/objectUtils');
4
4
  const { Selection } = require('./Selection');
5
5
  const { ObjectionToKnexConvertingOperation } = require('../ObjectionToKnexConvertingOperation');
6
6
 
7
7
  const COUNT_REGEX = /count/i;
8
+ const ALIAS_REGEX = /\s+as\s+/i;
9
+
10
+ const AGGREGATE_METHODS = new Set([
11
+ 'count',
12
+ 'countDistinct',
13
+ 'min',
14
+ 'max',
15
+ 'sum',
16
+ 'sumDistinct',
17
+ 'avg',
18
+ 'avgDistinct',
19
+ ]);
8
20
 
9
21
  class SelectOperation extends ObjectionToKnexConvertingOperation {
10
22
  constructor(name, opt) {
11
23
  super(name, opt);
12
24
  this.selections = [];
25
+ // The selections that aggregates select, by their aliases. `null` for
26
+ // non-aggregate operations.
27
+ this.aggregateSelections = AGGREGATE_METHODS.has(name) ? [] : null;
13
28
  }
14
29
 
15
30
  onAdd(builder, args) {
@@ -31,6 +46,12 @@ class SelectOperation extends ObjectionToKnexConvertingOperation {
31
46
  }
32
47
  }
33
48
 
49
+ if (this.aggregateSelections) {
50
+ this.aggregateSelections = getAggregateAliases(selections).map(
51
+ (alias) => new Selection(null, null, alias),
52
+ );
53
+ }
54
+
34
55
  return ret;
35
56
  }
36
57
 
@@ -57,10 +78,27 @@ class SelectOperation extends ObjectionToKnexConvertingOperation {
57
78
  clone() {
58
79
  const clone = super.clone();
59
80
  clone.selections = this.selections.slice();
81
+ clone.aggregateSelections = this.aggregateSelections && this.aggregateSelections.slice();
60
82
  return clone;
61
83
  }
62
84
  }
63
85
 
86
+ // Returns the aliases of the columns an aggregate selects, supporting the same
87
+ // argument forms as knex: `count('col as alias')`, `count('col', { as: 'alias' })`
88
+ // and `count({ alias: 'col' })`. Aggregates without an alias are skipped, as
89
+ // their column name differs between databases.
90
+ function getAggregateAliases([column, options]) {
91
+ if (isString(column) && ALIAS_REGEX.test(column)) {
92
+ return [column.split(ALIAS_REGEX)[1].trim()];
93
+ } else if (isPlainObject(options) && isString(options.as)) {
94
+ return [options.as];
95
+ } else if (isPlainObject(column)) {
96
+ return Object.keys(column);
97
+ } else {
98
+ return [];
99
+ }
100
+ }
101
+
64
102
  module.exports = {
65
103
  SelectOperation,
66
104
  };
@@ -24,6 +24,7 @@ class RelationFindOperation extends FindOperation {
24
24
  this.warnOnMissingOwnerProps();
25
25
 
26
26
  if (this.assignResultToOwner) {
27
+ this.warnOnArrayOwnerProps();
27
28
  this.selectMissingJoinColumns(builder);
28
29
  }
29
30
  }
@@ -100,6 +101,25 @@ class RelationFindOperation extends FindOperation {
100
101
  }
101
102
  }
102
103
 
104
+ // Owner join properties holding an array of keys, e.g. a JSON array column,
105
+ // aren't supported: The query finds the related models, but they can't be
106
+ // assigned to their owners, so all owners silently get no related models
107
+ // (#2667). `$relatedQuery()` without assigning the results isn't affected.
108
+ warnOnArrayOwnerProps() {
109
+ const { ownerProp } = this.relation;
110
+
111
+ for (let c = 0, lc = ownerProp.size; c < lc; ++c) {
112
+ if (this.owner.modelArray.some((owner) => Array.isArray(ownerProp.getProp(owner, c)))) {
113
+ deprecate(
114
+ `Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
115
+ `the join property "${ownerProp.propDescription(c)}" of some owner models holds ` +
116
+ 'an array, which is not supported, so no related models are found for them. ' +
117
+ 'Use a ManyToManyRelation through a join table instead.',
118
+ );
119
+ }
120
+ }
121
+ }
122
+
103
123
  // Related models without the join properties can't be assigned to their
104
124
  // owners and are silently dropped (#2258). This happens for example when the
105
125
  // relation mapping uses column names but the rows come back with different
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { RelationFindOperation } = require('../../RelationFindOperation');
4
4
  const { getTempColumn } = require('../../../utils/tmpColumnUtils');
5
+ const { deprecate } = require('../../../utils/deprecate');
5
6
 
6
7
  class ManyToManyFindOperation extends RelationFindOperation {
7
8
  constructor(name, opt) {
@@ -34,8 +35,12 @@ class ManyToManyFindOperation extends RelationFindOperation {
34
35
  }
35
36
  }
36
37
 
37
- if (this.assignResultToOwner && this.owner.isModels) {
38
- this.selectMissingJoinColumns(builder);
38
+ if (this.owner.isModels) {
39
+ this.warnOnMissingOwnerProps();
40
+
41
+ if (this.assignResultToOwner) {
42
+ this.selectMissingJoinColumns(builder);
43
+ }
39
44
  }
40
45
  }
41
46
 
@@ -60,6 +65,8 @@ class ManyToManyFindOperation extends RelationFindOperation {
60
65
  arr.push(rel);
61
66
  }
62
67
 
68
+ this.warnOnMissingRelatedProps(related);
69
+
63
70
  for (let i = 0, l = owners.length; i < l; ++i) {
64
71
  const own = owners[i];
65
72
  const key = ownerProp.propKey(own);
@@ -76,6 +83,27 @@ class ManyToManyFindOperation extends RelationFindOperation {
76
83
  return related;
77
84
  }
78
85
 
86
+ // The related models are assigned to their owners by the owner join columns
87
+ // selected from the join table. If they get lost, e.g. in a custom
88
+ // `$parseDatabaseJson()`, the related models are silently dropped (#2258).
89
+ warnOnMissingRelatedProps(related) {
90
+ const { joinTableOwnerProp } = this.relation;
91
+
92
+ for (let c = 0, lc = this.ownerJoinColumnAlias.length; c < lc; ++c) {
93
+ const alias = this.ownerJoinColumnAlias[c];
94
+
95
+ if (related.some((rel) => rel[alias] === undefined)) {
96
+ deprecate(
97
+ `Fetching relation "${this.relation.name}" of ${this.relation.ownerModelClass.name}: ` +
98
+ `some related models are missing the join table column ` +
99
+ `"${joinTableOwnerProp.propDescription(c)}" selected as "${alias}", so they are not ` +
100
+ 'assigned to any owner. Check that $parseDatabaseJson() and the column name mappers ' +
101
+ 'of the related model keep it.',
102
+ );
103
+ }
104
+ }
105
+ }
106
+
79
107
  clone() {
80
108
  const clone = super.clone();
81
109
  clone.ownerJoinColumnAlias = this.ownerJoinColumnAlias.slice();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "objection",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "An SQL-friendly ORM for Node.js",
5
5
  "main": "lib/objection.js",
6
6
  "license": "MIT",