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