@travetto/model-query 8.0.2 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -12
- package/package.json +3 -3
- package/src/types/aggregate.ts +23 -14
- package/src/types/query.ts +2 -2
- package/src/util/aggregate.ts +28 -9
- package/support/test/aggregate.ts +50 -62
- package/support/test/crud.ts +17 -11
- package/support/test/polymorphism.ts +12 -11
- package/support/test/query.ts +26 -26
package/README.md
CHANGED
|
@@ -35,13 +35,13 @@ export interface ModelQuerySupport {
|
|
|
35
35
|
* @param query The query to search for
|
|
36
36
|
* @param failOnMany Should the query fail on more than one result found, defaults to true
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
getByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, failOnMany?: boolean): Promise<T>;
|
|
39
39
|
/**
|
|
40
40
|
* Find the count of matching documents by query.
|
|
41
41
|
* @param cls The model class
|
|
42
42
|
* @param query The query to count for
|
|
43
43
|
*/
|
|
44
|
-
|
|
44
|
+
countByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number>;
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
@@ -128,28 +128,24 @@ export interface ModelQuerySuggestSupport extends ModelQuerySupport {
|
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
### Aggregate
|
|
131
|
-
This contract provides the ability to run strongly-typed field-level
|
|
131
|
+
This contract provides the ability to run strongly-typed field-level statistics (such as min, max, count, avg, and sum) on models with optional query filtering.
|
|
132
132
|
|
|
133
133
|
**Code: Query Aggregate**
|
|
134
134
|
```typescript
|
|
135
135
|
export interface ModelQueryAggregateSupport extends ModelQuerySupport {
|
|
136
136
|
/**
|
|
137
|
-
* Run an aggregation on a field
|
|
137
|
+
* Run an aggregation on a comparable field.
|
|
138
|
+
* For numeric fields (number/bigint), returns count, min, max, avg, and sum.
|
|
139
|
+
* For date fields, returns count, min, and max.
|
|
138
140
|
* @param modelClass The model class to aggregate
|
|
139
|
-
* @param operation The operation to perform ('sum', 'avg', 'min', 'max')
|
|
140
141
|
* @param field The field to aggregate on
|
|
141
142
|
* @param query Additional query filtering
|
|
142
143
|
*/
|
|
143
|
-
aggregateFieldByQuery<
|
|
144
|
-
T extends ModelType,
|
|
145
|
-
Op extends AggregateOperation,
|
|
146
|
-
F extends (Op extends AggregateNumericOperation ? ValidNumericFields<T> : ValidComparableFields<T>)
|
|
147
|
-
>(
|
|
144
|
+
aggregateFieldByQuery<T extends ModelType, F extends ValidComparableFields<T>>(
|
|
148
145
|
modelClass: Class<T>,
|
|
149
|
-
operation: Op,
|
|
150
146
|
field: F,
|
|
151
147
|
query?: ModelQuery<T>
|
|
152
|
-
): Promise<
|
|
148
|
+
): Promise<FieldAggregateResult<T, F>>;
|
|
153
149
|
}
|
|
154
150
|
```
|
|
155
151
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-query",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.4",
|
|
4
4
|
"description": "Datastore abstraction for advanced query support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"datastore",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@travetto/di": "^8.0.1",
|
|
34
|
-
"@travetto/model": "^8.0.
|
|
34
|
+
"@travetto/model": "^8.0.3",
|
|
35
35
|
"@travetto/schema": "^8.0.2"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@travetto/test": "^8.0.
|
|
38
|
+
"@travetto/test": "^8.0.2"
|
|
39
39
|
},
|
|
40
40
|
"peerDependenciesMeta": {
|
|
41
41
|
"@travetto/test": {
|
package/src/types/aggregate.ts
CHANGED
|
@@ -2,13 +2,9 @@ import type { ModelType } from '@travetto/model';
|
|
|
2
2
|
import type { Class } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import type { ModelQuery } from '../model/query.ts';
|
|
5
|
-
import type { ValidComparableFields
|
|
5
|
+
import type { ValidComparableFields } from '../model/where-clause.ts';
|
|
6
6
|
import type { ModelQuerySupport } from './query.ts';
|
|
7
7
|
|
|
8
|
-
export type AggregateNumericOperation = 'sum' | 'avg';
|
|
9
|
-
export type AggregateComparableOperation = 'min' | 'max';
|
|
10
|
-
export type AggregateOperation = AggregateNumericOperation | AggregateComparableOperation;
|
|
11
|
-
|
|
12
8
|
export type PathType<T, Path extends string> = Path extends `${infer Head}.${infer Tail}`
|
|
13
9
|
? Head extends keyof T
|
|
14
10
|
? PathType<NonNullable<T[Head]> extends (infer Element)[] ? Element : NonNullable<T[Head]>, Tail>
|
|
@@ -23,26 +19,39 @@ export type AggregateResultType<T, F extends string> = [PathType<T, F>] extends
|
|
|
23
19
|
? number | Date | bigint | undefined
|
|
24
20
|
: PathType<T, F> | undefined;
|
|
25
21
|
|
|
22
|
+
export type DateFieldAggregateResult = {
|
|
23
|
+
count: number;
|
|
24
|
+
min?: Date;
|
|
25
|
+
max?: Date;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type NumericFieldAggregateResult<T, F extends string> = {
|
|
29
|
+
count: number;
|
|
30
|
+
min?: AggregateResultType<T, F>;
|
|
31
|
+
max?: AggregateResultType<T, F>;
|
|
32
|
+
avg?: AggregateResultType<T, F>;
|
|
33
|
+
sum?: AggregateResultType<T, F>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type FieldAggregateResult<T, F extends string> =
|
|
37
|
+
NonNullable<PathType<T, F>> extends Date ? DateFieldAggregateResult : NumericFieldAggregateResult<T, F>;
|
|
38
|
+
|
|
26
39
|
/**
|
|
27
40
|
* The contract for a model service with aggregate support
|
|
28
41
|
* @concrete
|
|
29
42
|
*/
|
|
30
43
|
export interface ModelQueryAggregateSupport extends ModelQuerySupport {
|
|
31
44
|
/**
|
|
32
|
-
* Run an aggregation on a field
|
|
45
|
+
* Run an aggregation on a comparable field.
|
|
46
|
+
* For numeric fields (number/bigint), returns count, min, max, avg, and sum.
|
|
47
|
+
* For date fields, returns count, min, and max.
|
|
33
48
|
* @param modelClass The model class to aggregate
|
|
34
|
-
* @param operation The operation to perform ('sum', 'avg', 'min', 'max')
|
|
35
49
|
* @param field The field to aggregate on
|
|
36
50
|
* @param query Additional query filtering
|
|
37
51
|
*/
|
|
38
|
-
aggregateFieldByQuery<
|
|
39
|
-
T extends ModelType,
|
|
40
|
-
Op extends AggregateOperation,
|
|
41
|
-
F extends (Op extends AggregateNumericOperation ? ValidNumericFields<T> : ValidComparableFields<T>)
|
|
42
|
-
>(
|
|
52
|
+
aggregateFieldByQuery<T extends ModelType, F extends ValidComparableFields<T>>(
|
|
43
53
|
modelClass: Class<T>,
|
|
44
|
-
operation: Op,
|
|
45
54
|
field: F,
|
|
46
55
|
query?: ModelQuery<T>
|
|
47
|
-
): Promise<
|
|
56
|
+
): Promise<FieldAggregateResult<T, F>>;
|
|
48
57
|
}
|
package/src/types/query.ts
CHANGED
|
@@ -20,11 +20,11 @@ export interface ModelQuerySupport {
|
|
|
20
20
|
* @param query The query to search for
|
|
21
21
|
* @param failOnMany Should the query fail on more than one result found, defaults to true
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
getByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, failOnMany?: boolean): Promise<T>;
|
|
24
24
|
/**
|
|
25
25
|
* Find the count of matching documents by query.
|
|
26
26
|
* @param cls The model class
|
|
27
27
|
* @param query The query to count for
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
countByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number>;
|
|
30
30
|
}
|
package/src/util/aggregate.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { ModelType } from '@travetto/model';
|
|
|
2
2
|
import { castTo, type Class, hasFunction } from '@travetto/runtime';
|
|
3
3
|
import { DataUtil, SchemaRegistryIndex } from '@travetto/schema';
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { ValidComparableFields } from '../model/where-clause.ts';
|
|
6
|
+
import type { AggregateResultType, FieldAggregateResult, ModelQueryAggregateSupport } from '../types/aggregate.ts';
|
|
6
7
|
|
|
7
8
|
export class ModelQueryAggregateUtil {
|
|
8
9
|
/**
|
|
@@ -11,19 +12,37 @@ export class ModelQueryAggregateUtil {
|
|
|
11
12
|
static isSupported = hasFunction<ModelQueryAggregateSupport>('aggregateFieldByQuery');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Resolve and coerce
|
|
15
|
+
* Resolve and coerce aggregate query results
|
|
15
16
|
* @param modelClass The model class being queried
|
|
16
|
-
* @param operation The aggregate operation
|
|
17
17
|
* @param field The field being aggregated
|
|
18
|
-
* @param
|
|
18
|
+
* @param rawValues The raw result values from the datastore
|
|
19
19
|
*/
|
|
20
|
-
static
|
|
20
|
+
static resolveAggregate<T extends ModelType, F extends ValidComparableFields<T>>(
|
|
21
21
|
modelClass: Class<T>,
|
|
22
|
-
operation: Op,
|
|
23
22
|
field: F,
|
|
24
|
-
|
|
25
|
-
):
|
|
23
|
+
rawValues: { count?: unknown; min?: unknown; max?: unknown; avg?: unknown; sum?: unknown }
|
|
24
|
+
): FieldAggregateResult<T, F> {
|
|
25
|
+
const count = Number(rawValues.count ?? 0);
|
|
26
|
+
if (count === 0) {
|
|
27
|
+
return castTo({ count: 0 });
|
|
28
|
+
}
|
|
26
29
|
const leafFieldType = SchemaRegistryIndex.getNestedFieldConfig(modelClass, field)!.type;
|
|
27
|
-
|
|
30
|
+
const coerce = (value: unknown): AggregateResultType<T, F> => castTo(DataUtil.coerceType(value, leafFieldType, false) ?? undefined);
|
|
31
|
+
|
|
32
|
+
if (leafFieldType === Date) {
|
|
33
|
+
return castTo({
|
|
34
|
+
count,
|
|
35
|
+
min: coerce(rawValues.min),
|
|
36
|
+
max: coerce(rawValues.max)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return castTo({
|
|
41
|
+
count,
|
|
42
|
+
min: coerce(rawValues.min),
|
|
43
|
+
max: coerce(rawValues.max),
|
|
44
|
+
avg: coerce(rawValues.avg),
|
|
45
|
+
sum: coerce(rawValues.sum)
|
|
46
|
+
});
|
|
28
47
|
}
|
|
29
48
|
}
|
|
@@ -10,8 +10,30 @@ import { Aged, BigIntModel, Person } from './model.ts';
|
|
|
10
10
|
|
|
11
11
|
@Suite()
|
|
12
12
|
export abstract class ModelQueryAggregateSuite extends BaseModelSuite<ModelQueryAggregateSupport & ModelCrudSupport> {
|
|
13
|
-
@Test('verify
|
|
14
|
-
async
|
|
13
|
+
@Test('verify date field aggregate')
|
|
14
|
+
async testDateFieldAggregate() {
|
|
15
|
+
const service = await this.service;
|
|
16
|
+
|
|
17
|
+
const firstDate = new Date(2020, 0, 1);
|
|
18
|
+
const secondDate = new Date(2021, 0, 1);
|
|
19
|
+
const thirdDate = new Date(2022, 0, 1);
|
|
20
|
+
|
|
21
|
+
await this.saveAll(Aged, [
|
|
22
|
+
Aged.from({ createdAt: firstDate }),
|
|
23
|
+
Aged.from({ createdAt: secondDate }),
|
|
24
|
+
Aged.from({ createdAt: thirdDate })
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const aggregate = await service.aggregateFieldByQuery(Aged, 'createdAt');
|
|
28
|
+
assert(aggregate.count === 3);
|
|
29
|
+
assert(aggregate.min instanceof Date);
|
|
30
|
+
assert(aggregate.min.getTime() === firstDate.getTime());
|
|
31
|
+
assert(aggregate.max instanceof Date);
|
|
32
|
+
assert(aggregate.max.getTime() === thirdDate.getTime());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test('verify field aggregate')
|
|
36
|
+
async testFieldAggregate() {
|
|
15
37
|
const people = [
|
|
16
38
|
Person.from({ name: 'Bob', age: 20, gender: 'm', address: { street1: '1st St' } }),
|
|
17
39
|
Person.from({ name: 'Alice', age: 30, gender: 'f', address: { street1: '2nd St' } }),
|
|
@@ -23,67 +45,38 @@ export abstract class ModelQueryAggregateSuite extends BaseModelSuite<ModelQuery
|
|
|
23
45
|
const saved = await this.saveAll(Person, people);
|
|
24
46
|
assert(saved === 4);
|
|
25
47
|
|
|
26
|
-
const
|
|
27
|
-
assert(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
assert(
|
|
48
|
+
const aggregate = await service.aggregateFieldByQuery(Person, 'age');
|
|
49
|
+
assert(aggregate.count === 4);
|
|
50
|
+
assert(aggregate.min === 20);
|
|
51
|
+
assert(aggregate.max === 50);
|
|
52
|
+
assert(aggregate.avg === 35);
|
|
53
|
+
assert(aggregate.sum === 140);
|
|
31
54
|
|
|
32
|
-
const
|
|
33
|
-
assert(minimumResult === 20);
|
|
34
|
-
|
|
35
|
-
const maximumResult = await service.aggregateFieldByQuery(Person, 'max', 'age');
|
|
36
|
-
assert(maximumResult === 50);
|
|
37
|
-
|
|
38
|
-
// Filtered aggregations
|
|
39
|
-
const filteredSumResult = await service.aggregateFieldByQuery(Person, 'sum', 'age', {
|
|
55
|
+
const filteredAggregate = await service.aggregateFieldByQuery(Person, 'age', {
|
|
40
56
|
where: {
|
|
41
57
|
age: { $gte: 30 }
|
|
42
58
|
}
|
|
43
59
|
});
|
|
44
|
-
assert(
|
|
60
|
+
assert(filteredAggregate.count === 3);
|
|
61
|
+
assert(filteredAggregate.min === 30);
|
|
62
|
+
assert(filteredAggregate.max === 50);
|
|
63
|
+
assert(filteredAggregate.avg === 40);
|
|
64
|
+
assert(filteredAggregate.sum === 120);
|
|
45
65
|
|
|
46
|
-
const
|
|
47
|
-
where: {
|
|
48
|
-
gender: 'f'
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
assert(filteredAverageResult === 40);
|
|
52
|
-
|
|
53
|
-
// No matches
|
|
54
|
-
const noMatchResult = await service.aggregateFieldByQuery(Person, 'sum', 'age', {
|
|
66
|
+
const emptyAggregate = await service.aggregateFieldByQuery(Person, 'age', {
|
|
55
67
|
where: {
|
|
56
68
|
age: { $gt: 1000 }
|
|
57
69
|
}
|
|
58
70
|
});
|
|
59
|
-
assert(
|
|
71
|
+
assert(emptyAggregate.count === 0);
|
|
72
|
+
assert(emptyAggregate.min === undefined);
|
|
73
|
+
assert(emptyAggregate.max === undefined);
|
|
74
|
+
assert(emptyAggregate.avg === undefined);
|
|
75
|
+
assert(emptyAggregate.sum === undefined);
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
@Test('verify
|
|
63
|
-
async
|
|
64
|
-
const service = await this.service;
|
|
65
|
-
|
|
66
|
-
const firstDate = new Date(2020, 0, 1);
|
|
67
|
-
const secondDate = new Date(2021, 0, 1);
|
|
68
|
-
const thirdDate = new Date(2022, 0, 1);
|
|
69
|
-
|
|
70
|
-
await this.saveAll(Aged, [
|
|
71
|
-
Aged.from({ createdAt: firstDate }),
|
|
72
|
-
Aged.from({ createdAt: secondDate }),
|
|
73
|
-
Aged.from({ createdAt: thirdDate })
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
const minimumDateResult = await service.aggregateFieldByQuery(Aged, 'min', 'createdAt');
|
|
77
|
-
assert(minimumDateResult instanceof Date);
|
|
78
|
-
assert(minimumDateResult.getTime() === firstDate.getTime());
|
|
79
|
-
|
|
80
|
-
const maximumDateResult = await service.aggregateFieldByQuery(Aged, 'max', 'createdAt');
|
|
81
|
-
assert(maximumDateResult instanceof Date);
|
|
82
|
-
assert(maximumDateResult.getTime() === thirdDate.getTime());
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Test('verify bigint aggregations')
|
|
86
|
-
async testBigIntAggregate() {
|
|
78
|
+
@Test('verify bigint field aggregate')
|
|
79
|
+
async testBigIntFieldAggregate() {
|
|
87
80
|
const service = await this.service;
|
|
88
81
|
|
|
89
82
|
await this.saveAll(BigIntModel, [
|
|
@@ -92,16 +85,11 @@ export abstract class ModelQueryAggregateSuite extends BaseModelSuite<ModelQuery
|
|
|
92
85
|
BigIntModel.from({ largeNumber: 300n })
|
|
93
86
|
]);
|
|
94
87
|
|
|
95
|
-
const
|
|
96
|
-
assert(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
assert(
|
|
100
|
-
|
|
101
|
-
const maximumResult = await service.aggregateFieldByQuery(BigIntModel, 'max', 'largeNumber');
|
|
102
|
-
assert(maximumResult === 300n);
|
|
103
|
-
|
|
104
|
-
const averageResult = await service.aggregateFieldByQuery(BigIntModel, 'avg', 'largeNumber');
|
|
105
|
-
assert(averageResult === 200n);
|
|
88
|
+
const aggregate = await service.aggregateFieldByQuery(BigIntModel, 'largeNumber');
|
|
89
|
+
assert(aggregate.count === 3);
|
|
90
|
+
assert(aggregate.min === 100n);
|
|
91
|
+
assert(aggregate.max === 300n);
|
|
92
|
+
assert(aggregate.avg === 200n);
|
|
93
|
+
assert(aggregate.sum === 600n);
|
|
106
94
|
}
|
|
107
95
|
}
|
package/support/test/crud.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
2
|
|
|
3
|
-
import { Model, type ModelCrudSupport, NotFoundError, UniqueError } from '@travetto/model';
|
|
4
|
-
import { castTo } from '@travetto/runtime';
|
|
3
|
+
import { Model, type ModelCrudSupport, type ModelType, NotFoundError, UniqueError } from '@travetto/model';
|
|
4
|
+
import { castTo, type Class } from '@travetto/runtime';
|
|
5
5
|
import { Suite, Test } from '@travetto/test';
|
|
6
6
|
|
|
7
7
|
import { BaseModelSuite } from '@travetto/model/support/test/base.ts';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import type { WhereClause } from '../../__index__.ts';
|
|
10
|
+
import { QueryIndex } from '../../src/model/indexes.ts';
|
|
10
11
|
import type { ModelQueryCrudSupport } from '../../src/types/crud.ts';
|
|
11
12
|
import { Address, BigIntModel, Person, Todo } from './model.ts';
|
|
12
13
|
|
|
@@ -25,6 +26,11 @@ class UniqueUser2 {
|
|
|
25
26
|
export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudSupport & ModelCrudSupport> {
|
|
26
27
|
supportsUniqueIndexes = true;
|
|
27
28
|
|
|
29
|
+
async countByQuery<T extends ModelType>(cls: Class<T>, query: WhereClause<T>): Promise<number> {
|
|
30
|
+
const svc = await this.service;
|
|
31
|
+
return svc.query(cls, { where: query, limit: 10000 }).then(v => v.length);
|
|
32
|
+
}
|
|
33
|
+
|
|
28
34
|
@Test({ skip: self => !castTo<ModelQueryCrudSuite>(self).supportsUniqueIndexes })
|
|
29
35
|
async testUnique() {
|
|
30
36
|
const svc = await this.service;
|
|
@@ -147,13 +153,13 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
147
153
|
|
|
148
154
|
assert(c === 2);
|
|
149
155
|
|
|
150
|
-
assert((await
|
|
156
|
+
assert((await this.countByQuery(Person, {})) === 3);
|
|
151
157
|
|
|
152
158
|
const c2 = await svc.deleteByQuery(Person, { where: { age: { $lte: 3 } } });
|
|
153
159
|
|
|
154
160
|
assert(c2 === 3);
|
|
155
161
|
|
|
156
|
-
assert((await
|
|
162
|
+
assert((await this.countByQuery(Person, {})) === 0);
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
@Test()
|
|
@@ -177,7 +183,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
177
183
|
|
|
178
184
|
assert(count === 5);
|
|
179
185
|
|
|
180
|
-
assert((await
|
|
186
|
+
assert((await this.countByQuery(Person, { gender: 'm' })) === 5);
|
|
181
187
|
|
|
182
188
|
const c = await svc.updatePartialByQuery(Person, { where: { age: { $gt: 3 } } }, { gender: 'f' });
|
|
183
189
|
|
|
@@ -185,14 +191,14 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
185
191
|
|
|
186
192
|
assert(c === 2);
|
|
187
193
|
|
|
188
|
-
assert((await
|
|
194
|
+
assert((await this.countByQuery(Person, { gender: 'm' })) === 3);
|
|
189
195
|
|
|
190
196
|
const c2 = await svc.updatePartialByQuery(Person, { where: { gender: 'm' } }, { gender: 'f' });
|
|
191
197
|
|
|
192
198
|
assert(c2 === 3);
|
|
193
199
|
|
|
194
|
-
assert((await
|
|
195
|
-
assert((await
|
|
200
|
+
assert((await this.countByQuery(Person, { gender: 'f' })) === 5);
|
|
201
|
+
assert((await this.countByQuery(Person, { gender: 'm' })) === 0);
|
|
196
202
|
}
|
|
197
203
|
|
|
198
204
|
@Test()
|
|
@@ -237,7 +243,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
237
243
|
assert(withOptional.every(x => x.optionalBigInt !== undefined));
|
|
238
244
|
|
|
239
245
|
// Test count with bigint condition
|
|
240
|
-
const countResult = await
|
|
246
|
+
const countResult = await this.countByQuery(BigIntModel, { largeNumber: { $gte: 1000n } });
|
|
241
247
|
assert(countResult === 2);
|
|
242
248
|
}
|
|
243
249
|
|
|
@@ -285,7 +291,7 @@ export abstract class ModelQueryCrudSuite extends BaseModelSuite<ModelQueryCrudS
|
|
|
285
291
|
assert(deleted === 2);
|
|
286
292
|
|
|
287
293
|
// Verify deletions
|
|
288
|
-
const remaining = await
|
|
294
|
+
const remaining = await this.countByQuery(BigIntModel, {});
|
|
289
295
|
assert(remaining === 3);
|
|
290
296
|
|
|
291
297
|
const all = await svc.query(BigIntModel, {});
|
|
@@ -36,12 +36,12 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
36
36
|
assert((await svc.query(Doctor, {})).length === 2);
|
|
37
37
|
assert((await svc.query(Engineer, {})).length === 1);
|
|
38
38
|
|
|
39
|
-
assert((await svc.
|
|
40
|
-
assert((await svc.
|
|
41
|
-
assert((await svc.
|
|
39
|
+
assert((await svc.countByQuery(Worker, { where: { name: 'bob' } })) === 1);
|
|
40
|
+
assert((await svc.countByQuery(Doctor, { where: { name: 'bob' } })) === 1);
|
|
41
|
+
assert((await svc.countByQuery(Engineer, { where: { name: 'bob' } })) === 0);
|
|
42
42
|
|
|
43
|
-
assert((await svc.
|
|
44
|
-
await assert.rejects(() => svc.
|
|
43
|
+
assert((await svc.getByQuery(Worker, { where: { name: 'bob' } })) instanceof Doctor);
|
|
44
|
+
await assert.rejects(() => svc.getByQuery(Firefighter, { where: { name: 'bob' } }), NotFoundError);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
@Test({ skip: ModelQueryPolymorphismSuite.ifNot(ModelQueryCrudUtil.isSupported) })
|
|
@@ -60,7 +60,7 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
60
60
|
const c = await svc.updatePartialByQuery(Doctor, { where: { specialty: 'feet' } }, { specialty: 'eyes' });
|
|
61
61
|
assert(c === 1);
|
|
62
62
|
|
|
63
|
-
assert((await svc.
|
|
63
|
+
assert((await svc.countByQuery(Doctor, { where: { specialty: 'eyes' } })) === 2);
|
|
64
64
|
|
|
65
65
|
const removed = await svc.deleteByQuery(Worker, { where: { name: 'rob' } });
|
|
66
66
|
|
|
@@ -125,10 +125,11 @@ export abstract class ModelQueryPolymorphismSuite extends BaseModelSuite<ModelQu
|
|
|
125
125
|
await this.saveAll(Worker, [doctorOne, doctorTwo, firefighterOne, firefighterTwo]);
|
|
126
126
|
assert((await this.getSize(Worker)) === 4);
|
|
127
127
|
|
|
128
|
-
const
|
|
129
|
-
assert(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
assert(
|
|
128
|
+
const aggregate = await service.aggregateFieldByQuery(Firefighter, 'firehouse');
|
|
129
|
+
assert(aggregate.count === 2);
|
|
130
|
+
assert(aggregate.min === 20);
|
|
131
|
+
assert(aggregate.max === 30);
|
|
132
|
+
assert(aggregate.avg === 25);
|
|
133
|
+
assert(aggregate.sum === 50);
|
|
133
134
|
}
|
|
134
135
|
}
|
package/support/test/query.ts
CHANGED
|
@@ -32,13 +32,13 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
32
32
|
|
|
33
33
|
await this.saveAll(Person, people);
|
|
34
34
|
|
|
35
|
-
const one = await svc.
|
|
35
|
+
const one = await svc.getByQuery(Person, { where: { id: people[0].id } });
|
|
36
36
|
assert(one.id === people[0].id);
|
|
37
37
|
|
|
38
|
-
const one2 = await svc.
|
|
38
|
+
const one2 = await svc.getByQuery(Person, { where: { id: { $eq: people[0].id } } });
|
|
39
39
|
assert(one2.id === people[0].id);
|
|
40
40
|
|
|
41
|
-
const none = await svc.
|
|
41
|
+
const none = await svc.countByQuery(Person, { where: { id: { $ne: people[0].id } } });
|
|
42
42
|
assert(none === 3);
|
|
43
43
|
|
|
44
44
|
const noneIds = await svc.query(Person, { where: { id: { $ne: people[0].id } } });
|
|
@@ -91,11 +91,11 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
91
91
|
)
|
|
92
92
|
);
|
|
93
93
|
|
|
94
|
-
await assert.rejects(() => service.
|
|
95
|
-
await assert.rejects(() => service.
|
|
96
|
-
await assert.rejects(() => service.
|
|
97
|
-
await assert.rejects(() => service.
|
|
98
|
-
await assert.rejects(() => service.
|
|
94
|
+
await assert.rejects(() => service.getByQuery(Person, { where: { gender: 'm' } }), /Invalid number of results/);
|
|
95
|
+
await assert.rejects(() => service.getByQuery(Person, { where: { gender: 'z' } }), NotFoundError);
|
|
96
|
+
await assert.rejects(() => service.getByQuery(Person, { where: { gender: 'z' } }), /No results found for query/);
|
|
97
|
+
await assert.rejects(() => service.getByQuery(Person, { where: { id: 'orange' } }), NotFoundError);
|
|
98
|
+
await assert.rejects(() => service.getByQuery(Person, { where: { id: 'orange' } }), /orange/);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
@Test('Verify array $in queries work properly')
|
|
@@ -331,7 +331,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
331
331
|
|
|
332
332
|
await this.saveAll(Location, toAdd);
|
|
333
333
|
|
|
334
|
-
assert((await svc.
|
|
334
|
+
assert((await svc.countByQuery(Location, {})) === 25);
|
|
335
335
|
|
|
336
336
|
const result = await svc.query(Location, {
|
|
337
337
|
limit: 100,
|
|
@@ -382,7 +382,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
382
382
|
})
|
|
383
383
|
);
|
|
384
384
|
|
|
385
|
-
const out = await service.
|
|
385
|
+
const out = await service.countByQuery(Note, {
|
|
386
386
|
where: {
|
|
387
387
|
entities: {
|
|
388
388
|
id
|
|
@@ -413,7 +413,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
413
413
|
)
|
|
414
414
|
);
|
|
415
415
|
|
|
416
|
-
const simple = await service.
|
|
416
|
+
const simple = await service.countByQuery(Aged, {
|
|
417
417
|
where: {
|
|
418
418
|
createdAt: {
|
|
419
419
|
$gt: new Date()
|
|
@@ -422,7 +422,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
422
422
|
});
|
|
423
423
|
assert(simple === 5);
|
|
424
424
|
|
|
425
|
-
const simple2 = await service.
|
|
425
|
+
const simple2 = await service.countByQuery(Aged, {
|
|
426
426
|
where: {
|
|
427
427
|
createdAt: {
|
|
428
428
|
$gt: '-1d'
|
|
@@ -431,7 +431,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
431
431
|
});
|
|
432
432
|
assert(simple2 === 6);
|
|
433
433
|
|
|
434
|
-
const simple3 = await service.
|
|
434
|
+
const simple3 = await service.countByQuery(Aged, {
|
|
435
435
|
where: {
|
|
436
436
|
createdAt: {
|
|
437
437
|
$gt: '-1296m', // -1.9d
|
|
@@ -441,7 +441,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
441
441
|
});
|
|
442
442
|
assert(simple3 === 3);
|
|
443
443
|
|
|
444
|
-
const simple4 = await service.
|
|
444
|
+
const simple4 = await service.countByQuery(Aged, {
|
|
445
445
|
where: {
|
|
446
446
|
createdAt: {
|
|
447
447
|
$gt: TimeUtil.fromNow('-144m'), // -0.1d
|
|
@@ -463,35 +463,35 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
463
463
|
WithNestedLists.from({ names: [], tags: [] })
|
|
464
464
|
]);
|
|
465
465
|
|
|
466
|
-
let total = await service.
|
|
466
|
+
let total = await service.countByQuery(WithNestedLists, {
|
|
467
467
|
where: {
|
|
468
468
|
names: { $exists: true }
|
|
469
469
|
}
|
|
470
470
|
});
|
|
471
471
|
assert(total === 3);
|
|
472
472
|
|
|
473
|
-
total = await service.
|
|
473
|
+
total = await service.countByQuery(WithNestedLists, {
|
|
474
474
|
where: {
|
|
475
475
|
names: { $exists: false }
|
|
476
476
|
}
|
|
477
477
|
});
|
|
478
478
|
assert(total === 2);
|
|
479
479
|
|
|
480
|
-
total = await service.
|
|
480
|
+
total = await service.countByQuery(WithNestedLists, {
|
|
481
481
|
where: {
|
|
482
482
|
tags: { $exists: false }
|
|
483
483
|
}
|
|
484
484
|
});
|
|
485
485
|
assert(total === 3);
|
|
486
486
|
|
|
487
|
-
total = await service.
|
|
487
|
+
total = await service.countByQuery(WithNestedLists, {
|
|
488
488
|
where: {
|
|
489
489
|
tags: { $exists: true }
|
|
490
490
|
}
|
|
491
491
|
});
|
|
492
492
|
assert(total === 2);
|
|
493
493
|
|
|
494
|
-
total = await service.
|
|
494
|
+
total = await service.countByQuery(WithNestedLists, {
|
|
495
495
|
where: {
|
|
496
496
|
tags: { $exists: false },
|
|
497
497
|
names: { $exists: false }
|
|
@@ -499,7 +499,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
499
499
|
});
|
|
500
500
|
assert(total === 1);
|
|
501
501
|
|
|
502
|
-
total = await service.
|
|
502
|
+
total = await service.countByQuery(WithNestedLists, {
|
|
503
503
|
where: {
|
|
504
504
|
tags: { $exists: true },
|
|
505
505
|
names: { $exists: true }
|
|
@@ -520,35 +520,35 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
520
520
|
WithNestedNestedLists.from({ sub: {}, tags: [] })
|
|
521
521
|
]);
|
|
522
522
|
|
|
523
|
-
let total = await service.
|
|
523
|
+
let total = await service.countByQuery(WithNestedNestedLists, {
|
|
524
524
|
where: {
|
|
525
525
|
sub: { names: { $exists: true } }
|
|
526
526
|
}
|
|
527
527
|
});
|
|
528
528
|
assert(total === 3);
|
|
529
529
|
|
|
530
|
-
total = await service.
|
|
530
|
+
total = await service.countByQuery(WithNestedNestedLists, {
|
|
531
531
|
where: {
|
|
532
532
|
sub: { names: { $exists: false } }
|
|
533
533
|
}
|
|
534
534
|
});
|
|
535
535
|
assert(total === 2);
|
|
536
536
|
|
|
537
|
-
total = await service.
|
|
537
|
+
total = await service.countByQuery(WithNestedNestedLists, {
|
|
538
538
|
where: {
|
|
539
539
|
tags: { $exists: false }
|
|
540
540
|
}
|
|
541
541
|
});
|
|
542
542
|
assert(total === 3);
|
|
543
543
|
|
|
544
|
-
total = await service.
|
|
544
|
+
total = await service.countByQuery(WithNestedNestedLists, {
|
|
545
545
|
where: {
|
|
546
546
|
tags: { $exists: true }
|
|
547
547
|
}
|
|
548
548
|
});
|
|
549
549
|
assert(total === 2);
|
|
550
550
|
|
|
551
|
-
total = await service.
|
|
551
|
+
total = await service.countByQuery(WithNestedNestedLists, {
|
|
552
552
|
where: {
|
|
553
553
|
tags: { $exists: false },
|
|
554
554
|
sub: { names: { $exists: false } }
|
|
@@ -556,7 +556,7 @@ export abstract class ModelQuerySuite extends BaseModelSuite<ModelQuerySupport &
|
|
|
556
556
|
});
|
|
557
557
|
assert(total === 1);
|
|
558
558
|
|
|
559
|
-
total = await service.
|
|
559
|
+
total = await service.countByQuery(WithNestedNestedLists, {
|
|
560
560
|
where: {
|
|
561
561
|
tags: { $exists: true },
|
|
562
562
|
sub: { names: { $exists: true } }
|