joist-orm 1.50.5 → 1.51.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/build/src/EntityFilter.d.ts +45 -0
- package/build/src/EntityFilter.js +3 -0
- package/build/src/EntityFilter.js.map +1 -0
- package/build/src/EntityGraphQLFilter.d.ts +28 -0
- package/build/src/EntityGraphQLFilter.js +15 -0
- package/build/src/EntityGraphQLFilter.js.map +1 -0
- package/build/src/QueryBuilder.d.ts +2 -114
- package/build/src/QueryBuilder.js +77 -323
- package/build/src/QueryBuilder.js.map +1 -1
- package/build/src/QueryParser.d.ts +97 -0
- package/build/src/QueryParser.js +348 -0
- package/build/src/QueryParser.js.map +1 -0
- package/build/src/dataloaders/findDataLoader.d.ts +1 -1
- package/build/src/drivers/InMemoryDriver.d.ts +1 -1
- package/build/src/drivers/InMemoryDriver.js +7 -7
- package/build/src/drivers/InMemoryDriver.js.map +1 -1
- package/build/src/drivers/driver.d.ts +1 -1
- package/build/src/index.d.ts +3 -0
- package/build/src/index.js +3 -0
- package/build/src/index.js.map +1 -1
- package/build/src/serde.d.ts +1 -1
- package/build/src/serde.js +1 -1
- package/build/src/serde.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FilterOf, OrderOf } from "./EntityManager";
|
|
2
|
+
/**
|
|
3
|
+
* Combines a `where` filter with optional `orderBy`, `limit`, and `offset` settings.
|
|
4
|
+
*/
|
|
5
|
+
export type FilterAndSettings<T> = {
|
|
6
|
+
where: FilterOf<T>;
|
|
7
|
+
orderBy?: OrderOf<T>;
|
|
8
|
+
limit?: number;
|
|
9
|
+
offset?: number;
|
|
10
|
+
};
|
|
11
|
+
export type OrderBy = "ASC" | "DESC";
|
|
12
|
+
/**
|
|
13
|
+
* A filter for an entity of type `T`.
|
|
14
|
+
*
|
|
15
|
+
* @typeparam T The entity type, i.e. `Author`
|
|
16
|
+
* @typeparam I The ID type of the entity, i.e. `AuthorId`
|
|
17
|
+
* @typeparam F The filter type for the entity, i.e. `AuthorFilter`
|
|
18
|
+
* @typeparam N Either `null | undefined` if the entity can be null, or `never` if it cannot.
|
|
19
|
+
*/
|
|
20
|
+
export type EntityFilter<T, I, F, N> = T | T[] | I | I[] | F | N | {
|
|
21
|
+
ne: T | I | N;
|
|
22
|
+
};
|
|
23
|
+
export type BooleanFilter<N> = true | false | N;
|
|
24
|
+
export type ValueFilter<V, N> = V | V[] | N | {
|
|
25
|
+
eq: V | N;
|
|
26
|
+
} | {
|
|
27
|
+
in: V[];
|
|
28
|
+
} | {
|
|
29
|
+
gt: V;
|
|
30
|
+
} | {
|
|
31
|
+
gte: V;
|
|
32
|
+
} | {
|
|
33
|
+
ne: V | N;
|
|
34
|
+
} | {
|
|
35
|
+
lt: V;
|
|
36
|
+
} | {
|
|
37
|
+
lte: V;
|
|
38
|
+
} | {
|
|
39
|
+
like: V;
|
|
40
|
+
} | {
|
|
41
|
+
ilike: V;
|
|
42
|
+
} | {
|
|
43
|
+
gte: V;
|
|
44
|
+
lte: V;
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityFilter.js","sourceRoot":"","sources":["../../src/EntityFilter.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This essentially matches the ValueFilter but with looser types to placate GraphQL.
|
|
3
|
+
*/
|
|
4
|
+
export type ValueGraphQLFilter<V> = {
|
|
5
|
+
eq?: V | null;
|
|
6
|
+
in?: V[] | null;
|
|
7
|
+
gt?: V | null;
|
|
8
|
+
gte?: V | null;
|
|
9
|
+
ne?: V | null;
|
|
10
|
+
lt?: V | null;
|
|
11
|
+
lte?: V | null;
|
|
12
|
+
like?: V | null;
|
|
13
|
+
ilike?: V | null;
|
|
14
|
+
between?: V[] | null;
|
|
15
|
+
} | {
|
|
16
|
+
op: Operator;
|
|
17
|
+
value: Primitive;
|
|
18
|
+
} | V | V[] | null;
|
|
19
|
+
export type BooleanGraphQLFilter = true | false | null;
|
|
20
|
+
export type Primitive = string | boolean | Date | number;
|
|
21
|
+
export declare const operators: readonly ["eq", "gt", "gte", "ne", "lt", "lte", "like", "ilike", "in", "between"];
|
|
22
|
+
export type Operator = typeof operators[number];
|
|
23
|
+
export declare const opToFn: Record<Exclude<Operator, "in" | "between">, string>;
|
|
24
|
+
export type EnumGraphQLFilter<V> = V[] | null | undefined;
|
|
25
|
+
/** A GraphQL version of EntityFilter. */
|
|
26
|
+
export type EntityGraphQLFilter<T, I, F, N> = T | I | I[] | F | {
|
|
27
|
+
ne: T | I | N;
|
|
28
|
+
} | null | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.opToFn = exports.operators = void 0;
|
|
4
|
+
exports.operators = ["eq", "gt", "gte", "ne", "lt", "lte", "like", "ilike", "in", "between"];
|
|
5
|
+
exports.opToFn = {
|
|
6
|
+
eq: "=",
|
|
7
|
+
gt: ">",
|
|
8
|
+
gte: ">=",
|
|
9
|
+
ne: "!=",
|
|
10
|
+
lt: "<",
|
|
11
|
+
lte: "<=",
|
|
12
|
+
like: "LIKE",
|
|
13
|
+
ilike: "ILIKE",
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=EntityGraphQLFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityGraphQLFilter.js","sourceRoot":"","sources":["../../src/EntityGraphQLFilter.ts"],"names":[],"mappings":";;;AAyBa,QAAA,SAAS,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAU,CAAC;AAI9F,QAAA,MAAM,GAAwD;IACzE,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,GAAG,EAAE,IAAI;IACT,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,GAAG;IACP,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;CACf,CAAC"}
|
|
@@ -1,118 +1,7 @@
|
|
|
1
1
|
import { Knex } from "knex";
|
|
2
2
|
import { Entity } from "./Entity";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
export type OrderBy = "ASC" | "DESC";
|
|
6
|
-
export type BooleanFilter<N> = true | false | N;
|
|
7
|
-
export type ValueFilter<V, N> = V | V[] | N | {
|
|
8
|
-
eq: V | N;
|
|
9
|
-
} | {
|
|
10
|
-
in: V[];
|
|
11
|
-
} | {
|
|
12
|
-
gt: V;
|
|
13
|
-
} | {
|
|
14
|
-
gte: V;
|
|
15
|
-
} | {
|
|
16
|
-
ne: V | N;
|
|
17
|
-
} | {
|
|
18
|
-
lt: V;
|
|
19
|
-
} | {
|
|
20
|
-
lte: V;
|
|
21
|
-
} | {
|
|
22
|
-
like: V;
|
|
23
|
-
} | {
|
|
24
|
-
ilike: V;
|
|
25
|
-
} | {
|
|
26
|
-
gte: V;
|
|
27
|
-
lte: V;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* An ADT version of `ValueFilter`.
|
|
31
|
-
*
|
|
32
|
-
* The ValueFilter is a
|
|
33
|
-
*/
|
|
34
|
-
export type ParsedValueFilter<V> = {
|
|
35
|
-
kind: "eq";
|
|
36
|
-
value: V | null;
|
|
37
|
-
} | {
|
|
38
|
-
kind: "in";
|
|
39
|
-
value: V[];
|
|
40
|
-
} | {
|
|
41
|
-
kind: "gt";
|
|
42
|
-
value: V;
|
|
43
|
-
} | {
|
|
44
|
-
kind: "gte";
|
|
45
|
-
value: V;
|
|
46
|
-
} | {
|
|
47
|
-
kind: "ne";
|
|
48
|
-
value: V | null;
|
|
49
|
-
} | {
|
|
50
|
-
kind: "lt";
|
|
51
|
-
value: V;
|
|
52
|
-
} | {
|
|
53
|
-
kind: "lte";
|
|
54
|
-
value: V;
|
|
55
|
-
} | {
|
|
56
|
-
kind: "like";
|
|
57
|
-
value: V;
|
|
58
|
-
} | {
|
|
59
|
-
kind: "ilike";
|
|
60
|
-
value: V;
|
|
61
|
-
} | {
|
|
62
|
-
kind: "pass";
|
|
63
|
-
} | {
|
|
64
|
-
kind: "between";
|
|
65
|
-
value: [V, V];
|
|
66
|
-
};
|
|
67
|
-
export declare function parseValueFilter<V>(filter: ValueFilter<V, any>): ParsedValueFilter<V>;
|
|
68
|
-
export type EntityFilter<T, I, F, N> = T | T[] | I | I[] | F | N | {
|
|
69
|
-
ne: T | I | N;
|
|
70
|
-
};
|
|
71
|
-
export type ParsedEntityFilter = {
|
|
72
|
-
kind: "eq";
|
|
73
|
-
id: number | null;
|
|
74
|
-
} | {
|
|
75
|
-
kind: "ne";
|
|
76
|
-
id: number | null;
|
|
77
|
-
} | {
|
|
78
|
-
kind: "in";
|
|
79
|
-
ids: number[];
|
|
80
|
-
} | {
|
|
81
|
-
kind: "join";
|
|
82
|
-
subFilter: any;
|
|
83
|
-
};
|
|
84
|
-
export declare function parseEntityFilter(meta: EntityMetadata<any>, filter: any): ParsedEntityFilter;
|
|
85
|
-
export type BooleanGraphQLFilter = true | false | null;
|
|
86
|
-
export type Primitive = string | boolean | Date | number;
|
|
87
|
-
/** This essentially matches the ValueFilter but with looser types to placate GraphQL. */
|
|
88
|
-
export type ValueGraphQLFilter<V> = {
|
|
89
|
-
eq?: V | null;
|
|
90
|
-
in?: V[] | null;
|
|
91
|
-
gt?: V | null;
|
|
92
|
-
gte?: V | null;
|
|
93
|
-
ne?: V | null;
|
|
94
|
-
lt?: V | null;
|
|
95
|
-
lte?: V | null;
|
|
96
|
-
like?: V | null;
|
|
97
|
-
ilike?: V | null;
|
|
98
|
-
between?: V[] | null;
|
|
99
|
-
} | {
|
|
100
|
-
op: Operator;
|
|
101
|
-
value: Primitive;
|
|
102
|
-
} | V | V[] | null;
|
|
103
|
-
export type EnumGraphQLFilter<V> = V[] | null | undefined;
|
|
104
|
-
/** A GraphQL version of EntityFilter. */
|
|
105
|
-
export type EntityGraphQLFilter<T, I, F, N> = T | I | I[] | F | {
|
|
106
|
-
ne: T | I | N;
|
|
107
|
-
} | null | undefined;
|
|
108
|
-
declare const operators: readonly ["eq", "gt", "gte", "ne", "lt", "lte", "like", "ilike", "in", "between"];
|
|
109
|
-
export type Operator = typeof operators[number];
|
|
110
|
-
export type FilterAndSettings<T> = {
|
|
111
|
-
where: FilterOf<T>;
|
|
112
|
-
orderBy?: OrderOf<T>;
|
|
113
|
-
limit?: number;
|
|
114
|
-
offset?: number;
|
|
115
|
-
};
|
|
3
|
+
import { FilterAndSettings } from "./EntityFilter";
|
|
4
|
+
import { EntityConstructor } from "./EntityManager";
|
|
116
5
|
/**
|
|
117
6
|
* Builds the SQL/knex queries for `EntityManager.find` calls.
|
|
118
7
|
*
|
|
@@ -122,4 +11,3 @@ export type FilterAndSettings<T> = {
|
|
|
122
11
|
*/
|
|
123
12
|
export declare function buildQuery<T extends Entity>(knex: Knex, type: EntityConstructor<T>, filter: FilterAndSettings<T>): Knex.QueryBuilder<{}, unknown[]>;
|
|
124
13
|
export declare function abbreviation(tableName: string): string;
|
|
125
|
-
export {};
|
|
@@ -1,111 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.abbreviation = exports.buildQuery =
|
|
4
|
-
const
|
|
5
|
-
const Entity_1 = require("./Entity");
|
|
3
|
+
exports.abbreviation = exports.buildQuery = void 0;
|
|
4
|
+
const EntityGraphQLFilter_1 = require("./EntityGraphQLFilter");
|
|
6
5
|
const EntityManager_1 = require("./EntityManager");
|
|
7
6
|
const EntityMetadata_1 = require("./EntityMetadata");
|
|
8
7
|
const index_1 = require("./index");
|
|
9
|
-
const keys_1 = require("./keys");
|
|
10
8
|
const utils_1 = require("./utils");
|
|
11
|
-
function parseValueFilter(filter) {
|
|
12
|
-
if (filter === null) {
|
|
13
|
-
return { kind: "eq", value: filter };
|
|
14
|
-
}
|
|
15
|
-
else if (filter === undefined) {
|
|
16
|
-
return { kind: "pass" };
|
|
17
|
-
}
|
|
18
|
-
else if (Array.isArray(filter)) {
|
|
19
|
-
return { kind: "in", value: filter };
|
|
20
|
-
}
|
|
21
|
-
else if (typeof filter === "object") {
|
|
22
|
-
const keys = Object.keys(filter);
|
|
23
|
-
if (keys.length === 0) {
|
|
24
|
-
return { kind: "pass" };
|
|
25
|
-
}
|
|
26
|
-
else if (keys.length === 1) {
|
|
27
|
-
const key = keys[0];
|
|
28
|
-
switch (key) {
|
|
29
|
-
case "eq":
|
|
30
|
-
return { kind: "eq", value: filter[key] ?? null };
|
|
31
|
-
case "ne":
|
|
32
|
-
return { kind: "ne", value: filter[key] ?? null };
|
|
33
|
-
case "in":
|
|
34
|
-
return { kind: "in", value: filter[key] };
|
|
35
|
-
case "gt":
|
|
36
|
-
case "gte":
|
|
37
|
-
case "lt":
|
|
38
|
-
case "lte":
|
|
39
|
-
case "like":
|
|
40
|
-
case "ilike":
|
|
41
|
-
return { kind: key, value: filter[key] };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else if (keys.length === 2 && "op" in filter && "value" in filter) {
|
|
45
|
-
// Probe for `findGql` op & value
|
|
46
|
-
const { op, value } = filter;
|
|
47
|
-
return { kind: op, value: value ?? null };
|
|
48
|
-
}
|
|
49
|
-
else if (keys.length === 2 && "gte" in filter && "lte" in filter) {
|
|
50
|
-
const { gte, lte } = filter;
|
|
51
|
-
return { kind: "between", value: [gte, lte] };
|
|
52
|
-
}
|
|
53
|
-
throw new Error("unsupported value filter");
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// This is a primitive like a string, number
|
|
57
|
-
return { kind: "eq", value: filter ?? null };
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
exports.parseValueFilter = parseValueFilter;
|
|
61
|
-
function parseEntityFilter(meta, filter) {
|
|
62
|
-
if (filter === null || filter === undefined) {
|
|
63
|
-
return { kind: "eq", id: null };
|
|
64
|
-
}
|
|
65
|
-
else if (typeof filter === "string" || typeof filter === "number") {
|
|
66
|
-
return { kind: "eq", id: (0, keys_1.keyToNumber)(meta, filter) };
|
|
67
|
-
}
|
|
68
|
-
else if (Array.isArray(filter)) {
|
|
69
|
-
return { kind: "in", ids: filter.map((id) => (0, keys_1.keyToNumber)(meta, id)) };
|
|
70
|
-
}
|
|
71
|
-
else if ((0, Entity_1.isEntity)(filter)) {
|
|
72
|
-
return { kind: "eq", id: (0, keys_1.keyToNumber)(meta, filter.id || -1) };
|
|
73
|
-
}
|
|
74
|
-
else if (typeof filter === "object") {
|
|
75
|
-
const keys = Object.keys(filter);
|
|
76
|
-
if (keys.length === 1 && keys[0] === "ne") {
|
|
77
|
-
const value = filter["ne"];
|
|
78
|
-
if (value === null || value === undefined) {
|
|
79
|
-
return { kind: "ne", id: null };
|
|
80
|
-
}
|
|
81
|
-
else if (typeof value === "string" || typeof value === "number") {
|
|
82
|
-
return { kind: "ne", id: (0, keys_1.keyToNumber)(meta, value) };
|
|
83
|
-
}
|
|
84
|
-
else if ((0, Entity_1.isEntity)(value)) {
|
|
85
|
-
return { kind: "ne", id: (0, keys_1.keyToNumber)(meta, value.id || -1) };
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
throw new Error(`Unsupported "ne" value ${value}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return { kind: "join", subFilter: filter };
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw new Error(`Unrecognized filter ${filter}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
exports.parseEntityFilter = parseEntityFilter;
|
|
98
|
-
const operators = ["eq", "gt", "gte", "ne", "lt", "lte", "like", "ilike", "in", "between"];
|
|
99
|
-
const opToFn = {
|
|
100
|
-
eq: "=",
|
|
101
|
-
gt: ">",
|
|
102
|
-
gte: ">=",
|
|
103
|
-
ne: "!=",
|
|
104
|
-
lt: "<",
|
|
105
|
-
lte: "<=",
|
|
106
|
-
like: "LIKE",
|
|
107
|
-
ilike: "ILIKE",
|
|
108
|
-
};
|
|
109
9
|
/**
|
|
110
10
|
* Builds the SQL/knex queries for `EntityManager.find` calls.
|
|
111
11
|
*
|
|
@@ -116,115 +16,36 @@ const opToFn = {
|
|
|
116
16
|
function buildQuery(knex, type, filter) {
|
|
117
17
|
const meta = (0, EntityMetadata_1.getMetadata)(type);
|
|
118
18
|
const { where, orderBy, limit, offset } = filter;
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (field.kind === "poly") {
|
|
144
|
-
if (Array.isArray(clause)) {
|
|
145
|
-
const ids = clause.map((e) => (0, index_1.maybeResolveReferenceToId)(e));
|
|
146
|
-
const idsByConstructor = (0, joist_utils_1.groupBy)(ids, (id) => (0, index_1.getConstructorFromTaggedId)(id).name);
|
|
147
|
-
query = query.where((query) => field.serde.columns.reduce((query, { columnName, otherMetadata, mapToDb }) => {
|
|
148
|
-
const ids = idsByConstructor[otherMetadata().cstr.name];
|
|
149
|
-
return ids && ids.length > 0 ? query.orWhereIn(`${alias}.${columnName}`, ids.map(mapToDb)) : query;
|
|
150
|
-
}, query));
|
|
151
|
-
}
|
|
152
|
-
else if ((0, Entity_1.isEntity)(clause) || typeof clause === "string") {
|
|
153
|
-
query = addPolyClause(query, alias, field, meta, clause);
|
|
154
|
-
}
|
|
155
|
-
else if (clause === null) {
|
|
156
|
-
query = field.components.reduce((query, component) => addPolyClause(query, alias, field, meta,
|
|
157
|
-
// Not really sure if this is safe, being lazy for now...
|
|
158
|
-
(0, index_1.asConcreteCstr)(component.otherMetadata().cstr), clause), query);
|
|
159
|
-
}
|
|
160
|
-
else if (typeof clause === "object" && Object.keys(clause).length === 1 && "ne" in clause) {
|
|
161
|
-
const { ne: value } = clause;
|
|
162
|
-
if ((0, Entity_1.isEntity)(value) || typeof value === "string") {
|
|
163
|
-
const column = polyColumnFor(meta, field, value);
|
|
164
|
-
query = query.where((query) => query
|
|
165
|
-
.whereNot(`${alias}.${column.columnName}`, column.mapToDb(value))
|
|
166
|
-
// for some reason whereNot excludes null values, so explicitly include them here
|
|
167
|
-
.orWhereNull(`${alias}.${column.columnName}`));
|
|
168
|
-
}
|
|
169
|
-
else if (value === null) {
|
|
170
|
-
query = query.where((b) => field.components.reduce((b, { columnName }) => b.orWhereNotNull(`${alias}.${columnName}`), b));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
else if (field.kind === "o2o") {
|
|
175
|
-
// Add `otherTable.column = ...` clause, unless `key` is not in `where`, i.e. there is only an orderBy for this fk
|
|
176
|
-
const otherMeta = field.otherMetadata();
|
|
177
|
-
const otherAlias = getAlias(otherMeta.tableName);
|
|
178
|
-
const otherColumn = otherMeta.fields[field.otherFieldName];
|
|
179
|
-
query = query.leftJoin(`${otherMeta.tableName} AS ${otherAlias}`, `${otherAlias}.${otherColumn.serde.columns[0].columnName}`, `${alias}.id`);
|
|
180
|
-
const [shouldAddClauses, _query] = hasClause
|
|
181
|
-
? addForeignKeyClause(query, otherAlias, otherMeta.fields["id"].serde.columns[0], clause)
|
|
182
|
-
: [false, query];
|
|
183
|
-
query = _query;
|
|
184
|
-
if (shouldAddClauses || hasOrder) {
|
|
185
|
-
addClauses(otherMeta, otherAlias, shouldAddClauses ? clause : undefined, hasOrder ? order : undefined);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
else if (field.kind === "m2o") {
|
|
189
|
-
const serde = (meta.fields[key] ?? (0, utils_1.fail)(`${key} not found`)).serde;
|
|
190
|
-
// TODO Currently hardcoded to single-column support; poly is handled above this
|
|
191
|
-
const column = serde.columns[0];
|
|
192
|
-
// Add `otherTable.column = ...` clause, unless `key` is not in `where`, i.e. there is only an orderBy for this fk
|
|
193
|
-
const [whereNeedsJoin, _query] = hasClause ? addForeignKeyClause(query, alias, column, clause) : [false, query];
|
|
194
|
-
query = _query;
|
|
195
|
-
if (whereNeedsJoin || hasOrder) {
|
|
196
|
-
// Add a join for this column
|
|
197
|
-
const otherMeta = field.otherMetadata();
|
|
198
|
-
const otherAlias = getAlias(otherMeta.tableName);
|
|
199
|
-
query = query.innerJoin(`${otherMeta.tableName} AS ${otherAlias}`, `${alias}.${column.columnName}`, `${otherAlias}.id`);
|
|
200
|
-
// Then recurse to add its conditions to the query
|
|
201
|
-
addClauses(otherMeta, otherAlias, whereNeedsJoin ? clause : undefined, hasOrder ? order : undefined);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const field = meta.allFields[key] ?? (0, utils_1.fail)(`${key} not found`);
|
|
206
|
-
const serde = field.serde;
|
|
207
|
-
// TODO Currently hardcoded to single-column support; poly is handled above this
|
|
208
|
-
const column = serde.columns[0];
|
|
209
|
-
// TODO Currently we only support base-type WHEREs if the sub-type is the main `em.find`
|
|
210
|
-
// const maybeBaseAlias = field.alias;
|
|
211
|
-
query = hasClause ? addPrimitiveClause(query, alias, column, clause) : query;
|
|
212
|
-
// This is not a foreign key column, so it'll have the primitive filters/order bys
|
|
213
|
-
if (order) {
|
|
214
|
-
query = query.orderBy(`${alias}.${column.columnName}`, order);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
19
|
+
const parsed = (0, index_1.parseFindQuery)(meta, filter.where, filter.orderBy);
|
|
20
|
+
const primary = parsed.tables.find((t) => t.join === "primary");
|
|
21
|
+
let query = knex.from(`${primary.table} AS ${primary.alias}`);
|
|
22
|
+
parsed.selects.forEach((s) => {
|
|
23
|
+
query.select(knex.raw(s));
|
|
24
|
+
});
|
|
25
|
+
parsed.tables.forEach((t) => {
|
|
26
|
+
if (t.join === "left") {
|
|
27
|
+
query.leftOuterJoin(`${t.table} AS ${t.alias}`, t.col1, t.col2);
|
|
28
|
+
}
|
|
29
|
+
else if (t.join !== "primary") {
|
|
30
|
+
query.join(`${t.table} AS ${t.alias}`, t.col1, t.col2);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
parsed.conditions.forEach((c) => {
|
|
34
|
+
addColumnCondition(query, c);
|
|
35
|
+
});
|
|
36
|
+
parsed.complexConditions &&
|
|
37
|
+
parsed.complexConditions.forEach((c) => {
|
|
38
|
+
addComplexCondition(query, c);
|
|
39
|
+
});
|
|
40
|
+
parsed.orderBys &&
|
|
41
|
+
parsed.orderBys.forEach(({ alias, column, order }) => {
|
|
42
|
+
query.orderBy(`${alias}.${column}`, order);
|
|
217
43
|
});
|
|
218
|
-
}
|
|
219
|
-
addClauses(meta, alias, where, orderBy);
|
|
220
|
-
if ((0, index_1.needsClassPerTableJoins)(meta)) {
|
|
221
|
-
(0, index_1.addTablePerClassJoinsAndClassTag)(knex, meta, query, alias);
|
|
222
|
-
}
|
|
223
44
|
// Even if they already added orders, add id as the last one to get deterministic output
|
|
224
|
-
query
|
|
225
|
-
query
|
|
45
|
+
query.orderBy(`${primary.alias}.id`);
|
|
46
|
+
query.limit(limit || EntityManager_1.entityLimit);
|
|
226
47
|
if (offset) {
|
|
227
|
-
query
|
|
48
|
+
query.offset(offset);
|
|
228
49
|
}
|
|
229
50
|
return query;
|
|
230
51
|
}
|
|
@@ -236,122 +57,55 @@ function abbreviation(tableName) {
|
|
|
236
57
|
.join("");
|
|
237
58
|
}
|
|
238
59
|
exports.abbreviation = abbreviation;
|
|
239
|
-
function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// I.e. this could be { authorFk: authorEntity | null | id | { ...recurse... } }
|
|
252
|
-
const clauseKeys = typeof clause === "object" && clause !== null
|
|
253
|
-
? Object.keys(clause).filter((key) => clause[key] !== undefined)
|
|
254
|
-
: [];
|
|
255
|
-
if ((0, Entity_1.isEntity)(clause) || typeof clause === "string" || Array.isArray(clause)) {
|
|
256
|
-
// I.e. { authorFk: authorEntity | id | id[] }
|
|
257
|
-
if ((0, Entity_1.isEntity)(clause) && clause.id === undefined) {
|
|
258
|
-
// The user is filtering on an unsaved entity, which will just never have any rows, so throw in -1
|
|
259
|
-
return [false, query.where(`${alias}.${column.columnName}`, -1)];
|
|
260
|
-
}
|
|
261
|
-
else if (Array.isArray(clause)) {
|
|
262
|
-
return [
|
|
263
|
-
false,
|
|
264
|
-
query.whereIn(`${alias}.${column.columnName}`, clause.map((id) => column.mapToDb(id))),
|
|
265
|
-
];
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
return [false, query.where(`${alias}.${column.columnName}`, column.mapToDb(clause))];
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
else if (clause === null) {
|
|
272
|
-
// I.e. { authorFk: null | undefined }
|
|
273
|
-
return [false, query.whereNull(`${alias}.${column.columnName}`)];
|
|
274
|
-
}
|
|
275
|
-
else if (clauseKeys.length === 1 && clauseKeys[0] === "id") {
|
|
276
|
-
// I.e. { authorFk: { id: string } } || { authorFk: { id: string[] } }
|
|
277
|
-
// If only querying on the id, we can skip the join
|
|
278
|
-
return [false, addPrimitiveClause(query, alias, column, clause["id"])];
|
|
279
|
-
}
|
|
280
|
-
else if (clauseKeys.length === 1 && clauseKeys[0] === "ne") {
|
|
281
|
-
// I.e. { authorFk: { ne: string | null | undefined } }
|
|
282
|
-
const value = clause["ne"];
|
|
283
|
-
if (value === null || value === undefined) {
|
|
284
|
-
return [false, query.whereNotNull(`${alias}.${column.columnName}`)];
|
|
285
|
-
}
|
|
286
|
-
else if (typeof value === "string") {
|
|
287
|
-
return [false, query.whereNot(`${alias}.${column.columnName}`, column.mapToDb(value))];
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
throw new Error("Not implemented");
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
// I.e. { authorFk: { ...authorFilter... } }
|
|
295
|
-
return [clause !== undefined, query];
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function addPrimitiveClause(query, alias, column, clause) {
|
|
299
|
-
if (clause && typeof clause === "object" && operators.find((op) => Object.keys(clause).includes(op))) {
|
|
300
|
-
// I.e. `{ primitiveField: { gt: value } }`
|
|
301
|
-
return Object.entries(clause).reduce((query, [op, value]) => addPrimitiveOperator(query, alias, column, op, value), query);
|
|
302
|
-
}
|
|
303
|
-
else if (clause && typeof clause === "object" && "op" in clause) {
|
|
304
|
-
// I.e. { primitiveField: { op: "gt", value: 1 } }`
|
|
305
|
-
return addPrimitiveOperator(query, alias, column, clause.op, clause.value);
|
|
306
|
-
}
|
|
307
|
-
else if (Array.isArray(clause)) {
|
|
308
|
-
// I.e. `{ primitiveField: value[] }`
|
|
309
|
-
if (column.isArray) {
|
|
310
|
-
return query.where(`${alias}.${column.columnName}`, "@>", column.mapToDb(clause));
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
return query.whereIn(`${alias}.${column.columnName}`, clause.map((v) => column.mapToDb(v)));
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
else if (clause === null) {
|
|
317
|
-
// I.e. `{ primitiveField: null }`
|
|
318
|
-
return query.whereNull(`${alias}.${column.columnName}`);
|
|
319
|
-
}
|
|
320
|
-
else if (clause === undefined) {
|
|
321
|
-
// I.e. `{ primitiveField: undefined }`
|
|
322
|
-
// Currently we treat this like a partial filter, i.e. don't include it. Seems odd
|
|
323
|
-
// unless this is opt-in, i.e. maybe only do this for `findGql`?
|
|
324
|
-
return query;
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
// I.e. `{ primitiveField: value }`
|
|
328
|
-
// TODO In theory could add a addToQuery method to Serde to generalize this to multi-columns fields.
|
|
329
|
-
return query.where(`${alias}.${column.columnName}`, column.mapToDb(clause));
|
|
330
|
-
}
|
|
60
|
+
function addComplexCondition(query, complex) {
|
|
61
|
+
query.where((q) => {
|
|
62
|
+
const op = complex.op === "and" ? "andWhere" : "orWhere";
|
|
63
|
+
complex.conditions.forEach((c) => {
|
|
64
|
+
if ("op" in c) {
|
|
65
|
+
throw new Error("Not implemented");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
q[op]((q) => addColumnCondition(q, c));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
331
72
|
}
|
|
332
|
-
function
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
73
|
+
function addColumnCondition(query, cc) {
|
|
74
|
+
const { alias, column, cond } = cc;
|
|
75
|
+
const columnName = `${alias}.${column}`;
|
|
76
|
+
switch (cond.kind) {
|
|
77
|
+
case "eq":
|
|
78
|
+
case "ne":
|
|
79
|
+
case "gte":
|
|
80
|
+
case "gt":
|
|
81
|
+
case "lte":
|
|
82
|
+
case "lt":
|
|
83
|
+
case "like":
|
|
84
|
+
case "ilike":
|
|
85
|
+
const fn = EntityGraphQLFilter_1.opToFn[cond.kind] ?? (0, utils_1.fail)(`Invalid operator ${cond.kind}`);
|
|
86
|
+
query.where(columnName, fn, cond.value);
|
|
87
|
+
break;
|
|
88
|
+
case "is-null":
|
|
89
|
+
query.whereNull(columnName);
|
|
90
|
+
break;
|
|
91
|
+
case "not-null":
|
|
92
|
+
query.whereNotNull(columnName);
|
|
93
|
+
break;
|
|
94
|
+
case "in":
|
|
95
|
+
query.whereIn(columnName, cond.value);
|
|
96
|
+
break;
|
|
97
|
+
case "@>":
|
|
98
|
+
query.where(columnName, "@>", cond.value);
|
|
99
|
+
break;
|
|
100
|
+
case "between":
|
|
101
|
+
const [min, max] = cond.value;
|
|
102
|
+
query.where(columnName, ">=", min);
|
|
103
|
+
query.where(columnName, "<=", max);
|
|
104
|
+
break;
|
|
105
|
+
case "pass":
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
(0, utils_1.assertNever)(cond);
|
|
355
109
|
}
|
|
356
110
|
}
|
|
357
111
|
//# sourceMappingURL=QueryBuilder.js.map
|