Skip to content

Commit 6ab04db

Browse files
committed
feat(search): array search for workscopes etc
1 parent e6730c1 commit 6ab04db

File tree

5 files changed

+367
-140
lines changed

5 files changed

+367
-140
lines changed

schema.graphql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,11 @@ input MetadataWhereInput {
678678
}
679679

680680
input NumberArraySearchOptions {
681+
"""Array of numbers"""
681682
contains: [BigInt!]
683+
684+
"""Array of numbers"""
685+
overlaps: [BigInt!]
682686
}
683687

684688
input NumberSearchOptions {
@@ -844,6 +848,7 @@ enum SortOrder {
844848

845849
input StringArraySearchOptions {
846850
contains: [String!]
851+
overlaps: [String!]
847852
}
848853

849854
input StringSearchOptions {

src/graphql/schemas/inputs/searchOptions.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,22 @@ export class NumberSearchOptions {
6868
export class StringArraySearchOptions {
6969
@Field(() => [String], { nullable: true })
7070
contains?: string[];
71+
72+
@Field(() => [String], { nullable: true })
73+
overlaps?: string[];
7174
}
7275

7376
@InputType()
7477
export class NumberArraySearchOptions {
75-
@Field(() => [GraphQLBigInt], { nullable: true })
76-
contains?: bigint[] | number[];
78+
@Field(() => [GraphQLBigInt], {
79+
nullable: true,
80+
description: "Array of numbers",
81+
})
82+
contains?: bigint[];
83+
84+
@Field(() => [GraphQLBigInt], {
85+
nullable: true,
86+
description: "Array of numbers",
87+
})
88+
overlaps?: bigint[];
7789
}
Lines changed: 280 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1-
type OperandType = string | number | bigint | string[] | bigint[];
2-
type OperatorType =
3-
| "eq"
4-
| "gt"
5-
| "gte"
6-
| "lt"
7-
| "lte"
8-
| "ilike"
9-
| "contains"
10-
| "startsWith"
11-
| "endsWith";
1+
import { sql, SqlBool } from "kysely";
2+
import {
3+
NumberSearchOptions,
4+
StringSearchOptions,
5+
StringArraySearchOptions,
6+
NumberArraySearchOptions,
7+
} from "../inputs/searchOptions.js";
8+
9+
export type OperandType = string | number | bigint | string[] | bigint[];
10+
11+
export type NumericOperatorType = "eq" | "gt" | "gte" | "lt" | "lte";
12+
export type StringOperatorType = "contains" | "startsWith" | "endsWith";
13+
export type ArrayOperatorType = "overlaps" | "contains";
14+
export type OperatorType =
15+
| NumericOperatorType
16+
| StringOperatorType
17+
| ArrayOperatorType;
1218

1319
enum OperatorSymbols {
1420
eq = "=",
1521
gt = ">",
1622
gte = ">=",
1723
lt = "<",
1824
lte = "<=",
19-
ilike = "ilike",
25+
ilike = "~*",
26+
overlaps = "&&",
27+
contains = "@>",
2028
}
2129

2230
export const generateFilterValues = (
@@ -36,12 +44,269 @@ export const generateFilterValues = (
3644
case "lte":
3745
return [column, OperatorSymbols.lte, operand];
3846
case "contains":
39-
return [column, OperatorSymbols.ilike, operand];
47+
return [column, OperatorSymbols.ilike, `%${operand}%`];
4048
case "startsWith":
41-
return [column, OperatorSymbols.ilike, operand];
49+
return [column, OperatorSymbols.ilike, `${operand}%`];
4250
case "endsWith":
43-
return [column, OperatorSymbols.ilike, operand];
51+
return [column, OperatorSymbols.ilike, `%${operand}`];
4452
}
4553

4654
return [];
4755
};
56+
57+
export const getTablePrefix = (column: string): string => {
58+
switch (column) {
59+
case "hypercerts":
60+
return "claims";
61+
case "contract":
62+
return "contracts";
63+
case "fractions":
64+
return "fractions_view";
65+
case "metadata":
66+
return "metadata";
67+
case "attestations":
68+
return "attestations";
69+
default:
70+
return column;
71+
}
72+
};
73+
74+
export const isFilterObject = (obj: never): boolean => {
75+
const filterKeys = [
76+
"eq",
77+
"gt",
78+
"gte",
79+
"lt",
80+
"lte",
81+
"contains",
82+
"startsWith",
83+
"endsWith",
84+
"in",
85+
"overlaps",
86+
"contains",
87+
];
88+
return Object.keys(obj).some((key) => filterKeys.includes(key));
89+
};
90+
91+
export const buildSearchCondition = (
92+
column: string,
93+
value:
94+
| StringSearchOptions
95+
| NumberSearchOptions
96+
| StringArraySearchOptions
97+
| NumberArraySearchOptions,
98+
tableName: string,
99+
): SqlBool => {
100+
const conditions: SqlBool[] = [];
101+
102+
if (value instanceof StringSearchOptions) {
103+
if (value.contains) {
104+
conditions.push(
105+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
106+
${"%" + value.contains + "%"}`,
107+
);
108+
}
109+
if (value.startsWith) {
110+
conditions.push(
111+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
112+
${value.startsWith + "%"}`,
113+
);
114+
}
115+
if (value.endsWith) {
116+
conditions.push(
117+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
118+
${"%" + value.endsWith}`,
119+
);
120+
}
121+
if (value.eq) {
122+
conditions.push(
123+
sql`${sql.raw(`"${tableName}"."${column}"`)} =
124+
${value.eq}`,
125+
);
126+
}
127+
} else if (value instanceof NumberSearchOptions) {
128+
if (value.eq !== undefined) {
129+
conditions.push(
130+
sql`${sql.raw(`"${tableName}"."${column}"`)} =
131+
${value.eq}`,
132+
);
133+
}
134+
if (value.gt !== undefined) {
135+
conditions.push(
136+
sql`${sql.raw(`"${tableName}"."${column}"`)} >
137+
${value.gt}`,
138+
);
139+
}
140+
if (value.gte !== undefined) {
141+
conditions.push(
142+
sql`${sql.raw(`"${tableName}"."${column}"`)} >=
143+
${value.gte}`,
144+
);
145+
}
146+
if (value.lt !== undefined) {
147+
conditions.push(
148+
sql`${sql.raw(`"${tableName}"."${column}"`)} <
149+
${value.lt}`,
150+
);
151+
}
152+
if (value.lte !== undefined) {
153+
conditions.push(
154+
sql`${sql.raw(`"${tableName}"."${column}"`)} <=
155+
${value.lte}`,
156+
);
157+
}
158+
} else if (value instanceof StringArraySearchOptions) {
159+
if (value.contains && value.contains.length > 0) {
160+
conditions.push(
161+
sql`${sql.raw(`"${tableName}"."${column}"`)} @>
162+
${sql.raw(`ARRAY[${value.contains.map((v) => `'${v}'`).join(", ")}]`)}`,
163+
);
164+
}
165+
if (value.overlaps && value.overlaps.length > 0) {
166+
conditions.push(
167+
sql`${sql.raw(`"${tableName}"."${column}"`)} &&
168+
${sql.raw(`ARRAY[${value.overlaps.map((v) => `'${v}'`).join(", ")}]`)}`,
169+
);
170+
}
171+
} else if (value instanceof NumberArraySearchOptions) {
172+
if (value.contains && value.contains.length > 0) {
173+
conditions.push(
174+
sql`${sql.raw(`"${tableName}"."${column}"`)} @>
175+
${sql.raw(`ARRAY[${value.contains.join(", ")}]`)}`,
176+
);
177+
}
178+
if (value.overlaps && value.overlaps.length > 0) {
179+
conditions.push(
180+
sql`${sql.raw(`"${tableName}"."${column}"`)} &&
181+
${sql.raw(`ARRAY[${value.overlaps.join(", ")}]`)}`,
182+
);
183+
}
184+
}
185+
186+
return sql.join(conditions, sql` AND `);
187+
};
188+
export const buildFilterCondition = (
189+
column: string,
190+
filter: never,
191+
tableName: string,
192+
): SqlBool => {
193+
const conditions: SqlBool[] = [];
194+
195+
if ("eq" in filter) {
196+
conditions.push(
197+
sql`${sql.raw(`"${tableName}"."${column}"`)} =
198+
${filter.eq}`,
199+
);
200+
}
201+
if ("gt" in filter) {
202+
conditions.push(
203+
sql`${sql.raw(`"${tableName}"."${column}"`)} >
204+
${filter.gt}`,
205+
);
206+
}
207+
if ("gte" in filter) {
208+
conditions.push(
209+
sql`${sql.raw(`"${tableName}"."${column}"`)} >=
210+
${filter.gte}`,
211+
);
212+
}
213+
if ("lt" in filter) {
214+
conditions.push(
215+
sql`${sql.raw(`"${tableName}"."${column}"`)} <
216+
${filter.lt}`,
217+
);
218+
}
219+
if ("lte" in filter) {
220+
conditions.push(
221+
sql`${sql.raw(`"${tableName}"."${column}"`)} <=
222+
${filter.lte}`,
223+
);
224+
}
225+
if ("contains" in filter && typeof filter.contains === "string") {
226+
conditions.push(
227+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
228+
${"%" + filter.contains + "%"}`,
229+
);
230+
}
231+
if ("startsWith" in filter) {
232+
conditions.push(
233+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
234+
${filter.startsWith + "%"}`,
235+
);
236+
}
237+
if ("endsWith" in filter) {
238+
conditions.push(
239+
sql`${sql.raw(`"${tableName}"."${column}"`)} ILIKE
240+
${"%" + filter.endsWith}`,
241+
);
242+
}
243+
244+
return sql.join(conditions, sql` AND `);
245+
};
246+
247+
export const buildWhereCondition = <T extends string>(
248+
column: string,
249+
value: never,
250+
tableName: T,
251+
eb: never,
252+
): SqlBool | null => {
253+
if (!column || value === undefined) return null;
254+
console.log(
255+
"Building where condition for field",
256+
column,
257+
"in table",
258+
tableName,
259+
);
260+
261+
if (
262+
value instanceof StringSearchOptions ||
263+
value instanceof NumberSearchOptions ||
264+
value instanceof StringArraySearchOptions ||
265+
value instanceof NumberArraySearchOptions
266+
) {
267+
console.log("Found search condition for column: ", column);
268+
return buildSearchCondition(column, value, tableName);
269+
}
270+
271+
if (typeof value === "object" && value !== null) {
272+
if (isFilterObject(value)) {
273+
console.log("Found filter condition for column: ", column);
274+
if (
275+
("contains" in value && Array.isArray(value.contains)) ||
276+
"overlaps" in value
277+
) {
278+
// This is an array operation, use buildSearchCondition
279+
return buildSearchCondition(column, value, tableName);
280+
} else {
281+
// This is a non-array operation, use buildFilterCondition
282+
return buildFilterCondition(column, value, tableName);
283+
}
284+
}
285+
286+
const relatedTable = getTablePrefix(column);
287+
const nestedConditions: SqlBool[] = [];
288+
289+
for (const [nestedColumn, nestedValue] of Object.entries(value)) {
290+
if (!nestedColumn || nestedValue === undefined) continue;
291+
console.log("Nested column", nestedColumn);
292+
console.log("Nested value", nestedValue);
293+
const nestedCondition = buildWhereCondition(
294+
nestedColumn,
295+
nestedValue,
296+
relatedTable,
297+
eb,
298+
);
299+
if (nestedCondition) {
300+
nestedConditions.push(nestedCondition);
301+
}
302+
}
303+
304+
return nestedConditions.length > 0
305+
? sql.join(nestedConditions, sql` AND `)
306+
: null;
307+
}
308+
309+
console.log("Simple equality condition for column: ", column);
310+
return sql`${sql.raw(`"${tableName}"."${column}"`)} =
311+
${value}`;
312+
};

0 commit comments

Comments
 (0)