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.
@@ -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,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=EntityFilter.js.map
@@ -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 { EntityConstructor, FilterOf, OrderOf } from "./EntityManager";
4
- import { EntityMetadata } from "./EntityMetadata";
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 = exports.parseEntityFilter = exports.parseValueFilter = void 0;
4
- const joist_utils_1 = require("joist-utils");
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 aliases = {};
120
- function getAlias(tableName) {
121
- const abbrev = abbreviation(tableName);
122
- const i = aliases[abbrev] || 0;
123
- aliases[abbrev] = i + 1;
124
- return i === 0 ? abbrev : `${abbrev}${i}`;
125
- }
126
- const alias = getAlias(meta.tableName);
127
- let query = knex.select(`${alias}.*`).from(`${meta.tableName} AS ${alias}`);
128
- // Define a function for recursively adding joins & filters
129
- function addClauses(meta, alias, where, orderBy) {
130
- // Combine the where and orderBy keys so that we can add them to aliases as that same time
131
- // Filter out undefined values as they should be ignored (for now?)
132
- const keys = [
133
- ...(where ? Object.keys(where).filter((key) => where[key] !== undefined) : []),
134
- ...(orderBy ? Object.keys(orderBy).filter((key) => orderBy[key] !== undefined) : []),
135
- ];
136
- keys.forEach((key) => {
137
- const field = meta.allFields[key] ?? (0, utils_1.fail)(`${key} not found`);
138
- // We may/may not have a where clause or orderBy for this key, but we should have at least one of them.
139
- const clause = where && where[key];
140
- const hasClause = where && key in where;
141
- const order = orderBy && orderBy[key];
142
- const hasOrder = !!order;
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 = query.orderBy(`${alias}.id`);
225
- query = query.limit(limit || EntityManager_1.entityLimit);
45
+ query.orderBy(`${primary.alias}.id`);
46
+ query.limit(limit || EntityManager_1.entityLimit);
226
47
  if (offset) {
227
- query = query.offset(offset);
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 polyColumnFor(meta, field, value) {
240
- const cstr = typeof value === "function" ? value : (0, index_1.maybeGetConstructorFromReference)(value);
241
- return (field.serde.columns.find((c) => c.otherMetadata().cstr === cstr) ??
242
- (0, utils_1.fail)(`${cstr.name} cannot be used as a filter on ${field.fieldName}`));
243
- }
244
- function addPolyClause(query, alias, field, meta, value, clause) {
245
- clause = clause === undefined ? value : clause;
246
- const column = polyColumnFor(meta, field, value);
247
- const [, result] = addForeignKeyClause(query, alias, column, clause);
248
- return result;
249
- }
250
- function addForeignKeyClause(query, alias, column, clause) {
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 addPrimitiveOperator(query, alias, column, op, value) {
333
- const columnName = `${alias}.${column.columnName}`;
334
- if (value === null || value === undefined) {
335
- if (op === "ne") {
336
- return query.whereNotNull(columnName);
337
- }
338
- else if (op === "eq") {
339
- return query.whereNull(columnName);
340
- }
341
- else {
342
- throw new Error("Only ne is supported when the value is undefined or null");
343
- }
344
- }
345
- else if (op === "in") {
346
- return query.whereIn(columnName, value.map((v) => column.mapToDb(v)));
347
- }
348
- else if (op === "between") {
349
- const values = value.map((v) => column.mapToDb(v));
350
- return query.where(columnName, ">=", values[0]).where(columnName, "<=", values[1]);
351
- }
352
- else {
353
- const fn = opToFn[op] ?? (0, utils_1.fail)(`Invalid operator ${op}`);
354
- return query.where(columnName, fn, column.mapToDb(value));
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