@@ -149,7 +149,9 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
149149 return dayjs . unix ( + value ) . toISOString ( ) ;
150150 } else if ( field . _underlineType . startsWith ( 'DateTime' )
151151 || field . _underlineType . startsWith ( 'String' )
152- || field . _underlineType . startsWith ( 'FixedString' ) ) {
152+ || field . _underlineType . startsWith ( 'FixedString' )
153+ || field . _underlineType . startsWith ( 'Nullable(String)' )
154+ || field . _underlineType . startsWith ( 'Nullable(FixedString)' ) ) {
153155 const v = dayjs ( value ) . toISOString ( ) ;
154156 return v ;
155157 } else {
@@ -163,7 +165,10 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
163165 } else if ( field . type == AdminForthDataTypes . BOOLEAN ) {
164166 return value === null ? null : ! ! value ;
165167 } else if ( field . type == AdminForthDataTypes . JSON ) {
166- if ( field . _underlineType . startsWith ( 'String' ) || field . _underlineType . startsWith ( 'FixedString' ) ) {
168+ if ( field . _underlineType . startsWith ( 'String' )
169+ || field . _underlineType . startsWith ( 'FixedString' )
170+ || field . _underlineType . startsWith ( 'Nullable(String)' )
171+ || field . _underlineType . startsWith ( 'Nullable(FixedString)' ) ) {
167172 try {
168173 return JSON . parse ( value ) ;
169174 } catch ( e ) {
@@ -186,7 +191,9 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
186191 return dayjs ( value ) . unix ( ) ;
187192 } else if ( field . _underlineType . startsWith ( 'DateTime' )
188193 || field . _underlineType . startsWith ( 'String' )
189- || field . _underlineType . startsWith ( 'FixedString' ) ) {
194+ || field . _underlineType . startsWith ( 'FixedString' )
195+ || field . _underlineType . startsWith ( 'Nullable(String)' )
196+ || field . _underlineType . startsWith ( 'Nullable(FixedString)' ) ) {
190197 // value is iso string now, convert to unix timestamp
191198 const iso = dayjs ( value ) . format ( 'YYYY-MM-DDTHH:mm:ss' ) ;
192199 return iso ;
@@ -195,7 +202,10 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
195202 return value === null ? null : ( value ? 1 : 0 ) ;
196203 } else if ( field . type == AdminForthDataTypes . JSON ) {
197204 // check underline type is text or string
198- if ( field . _underlineType . startsWith ( 'String' ) || field . _underlineType . startsWith ( 'FixedString' ) ) {
205+ if ( field . _underlineType . startsWith ( 'String' )
206+ || field . _underlineType . startsWith ( 'FixedString' )
207+ || field . _underlineType . startsWith ( 'Nullable(String)' )
208+ || field . _underlineType . startsWith ( 'Nullable(FixedString)' ) ) {
199209 return JSON . stringify ( value ) ;
200210 } else {
201211 afLogger . warn ( `AdminForth: JSON field is not a string/text but ${ field . _underlineType } , this is not supported yet` ) ;
@@ -226,6 +236,21 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
226236 [ AdminForthSortDirections . asc ] : 'ASC' ,
227237 [ AdminForthSortDirections . desc ] : 'DESC' ,
228238 } ;
239+
240+ isArrayType ( underlineType : string ) : boolean {
241+ return underlineType . startsWith ( 'Array(' ) || underlineType . startsWith ( 'Nullable(Array(' ) ;
242+ }
243+
244+ isNullableType ( underlineType : string ) : boolean {
245+ return underlineType . startsWith ( 'Nullable(' ) ;
246+ }
247+
248+ isStringLikeType ( underlineType : string ) : boolean {
249+ return underlineType . startsWith ( 'String' )
250+ || underlineType . startsWith ( 'FixedString' )
251+ || underlineType . startsWith ( 'Nullable(String)' )
252+ || underlineType . startsWith ( 'Nullable(FixedString)' ) ;
253+ }
229254
230255 getFilterString ( resource : AdminForthResource , filter : IAdminForthSingleFilter | IAdminForthAndOrFilter ) : string {
231256 if ( ( filter as IAdminForthSingleFilter ) . field ) {
@@ -247,6 +272,49 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
247272 return `${ field } ${ operator } ` ;
248273 }
249274
275+ if ( ( filter . operator == AdminForthFilterOperators . LIKE || filter . operator == AdminForthFilterOperators . ILIKE )
276+ && column . isArray ?. enabled ) {
277+ placeholder = '{f$?:String}' ;
278+
279+ if ( this . isArrayType ( column . _underlineType ) ) {
280+ const arrayField = this . isNullableType ( column . _underlineType ) ? `assumeNotNull(${ field } )` : field ;
281+ const arrayMatch = `arrayExists(item -> toString(item) ${ operator } ${ placeholder } , ${ arrayField } )` ;
282+ return this . isNullableType ( column . _underlineType )
283+ ? `${ field } IS NOT NULL AND ${ arrayMatch } `
284+ : arrayMatch ;
285+ }
286+
287+ if ( this . isStringLikeType ( column . _underlineType ) ) {
288+ return `${ field } ${ operator } ${ placeholder } ` ;
289+ }
290+ }
291+
292+ if ( ( filter . operator == AdminForthFilterOperators . IN || filter . operator == AdminForthFilterOperators . NIN )
293+ && column . isArray ?. enabled
294+ && this . isArrayType ( column . _underlineType ) ) {
295+ const itemType = column . _underlineType
296+ . replace ( / ^ N u l l a b l e \( / , '' )
297+ . match ( / ^ A r r a y \( ( .* ) \) $ / ) ?. [ 1 ] ;
298+
299+ if ( ! itemType ) {
300+ throw new Error ( `Unable to determine item type for array field '${ column . name } ' with type '${ column . _underlineType } '` ) ;
301+ }
302+
303+ placeholder = `{f$?:Array(${ itemType } )}` ;
304+ const arrayField = this . isNullableType ( column . _underlineType ) ? `assumeNotNull(${ field } )` : field ;
305+ const hasAnyExpression = `hasAny(${ arrayField } , ${ placeholder } )` ;
306+
307+ if ( filter . operator == AdminForthFilterOperators . NIN ) {
308+ return this . isNullableType ( column . _underlineType )
309+ ? `(${ field } IS NULL OR NOT ${ hasAnyExpression } )`
310+ : `NOT ${ hasAnyExpression } ` ;
311+ }
312+
313+ return this . isNullableType ( column . _underlineType )
314+ ? `${ field } IS NOT NULL AND ${ hasAnyExpression } `
315+ : hasAnyExpression ;
316+ }
317+
250318 if ( column . _underlineType . startsWith ( 'Decimal' ) ) {
251319 field = `toDecimal64(${ field } , 8)` ;
252320 placeholder = `toDecimal64({f$?:String}, 8)` ;
@@ -293,20 +361,24 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
293361 } ) . join ( ` ${ this . OperatorsMap [ filter . operator ] } ` ) ;
294362 }
295363
296- getFilterParams ( filter : IAdminForthSingleFilter | IAdminForthAndOrFilter ) : any [ ] {
364+ getFilterParams ( resource : AdminForthResource , filter : IAdminForthSingleFilter | IAdminForthAndOrFilter ) : any [ ] {
297365 if ( ( filter as IAdminForthSingleFilter ) . field ) {
298366 if ( ( filter as IAdminForthSingleFilter ) . rightField ) {
299367 // No params for field-to-field comparisons
300368 return [ ] ;
301369 }
302370 // filter is a Single filter
371+ const column = resource . dataSourceColumns . find ( ( col ) => col . name == ( filter as IAdminForthSingleFilter ) . field ) ;
303372
304373 // Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
305374 if ( filter . operator == AdminForthFilterOperators . IS_EMPTY || filter . operator == AdminForthFilterOperators . IS_NOT_EMPTY ) {
306375 return [ ] ;
307376 } else if ( filter . operator == AdminForthFilterOperators . LIKE || filter . operator == AdminForthFilterOperators . ILIKE ) {
308377 return [ { 'f' : `%${ filter . value } %` } ] ;
309378 } else if ( filter . operator == AdminForthFilterOperators . IN || filter . operator == AdminForthFilterOperators . NIN ) {
379+ if ( column ?. isArray ?. enabled && this . isArrayType ( column . _underlineType ) ) {
380+ return [ { 'f' : filter . value } ] ;
381+ }
310382 return [ { 'p' : filter . value } ] ;
311383 } else if ( filter . operator == AdminForthFilterOperators . EQ && filter . value === null ) {
312384 // there is no param for IS NULL filter
@@ -326,15 +398,15 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
326398
327399 // filter is a AndOrFilter
328400 return ( filter as IAdminForthAndOrFilter ) . subFilters . reduce ( ( params : any [ ] , f : IAdminForthSingleFilter | IAdminForthAndOrFilter ) => {
329- return params . concat ( this . getFilterParams ( f ) ) ;
401+ return params . concat ( this . getFilterParams ( resource , f ) ) ;
330402 } , [ ] ) ;
331403 }
332404
333- whereParams ( filters : IAdminForthAndOrFilter ) : any {
405+ whereParams ( resource : AdminForthResource , filters : IAdminForthAndOrFilter ) : any {
334406 if ( filters . subFilters . length === 0 ) {
335407 return { } ;
336408 }
337- const paramsArray = this . getFilterParams ( filters ) ;
409+ const paramsArray = this . getFilterParams ( resource , filters ) ;
338410 const params = paramsArray . reduce ( ( acc , param , paramIndex ) => {
339411 if ( param . f !== undefined ) {
340412 acc [ `f${ paramIndex } ` ] = param . f ;
@@ -362,7 +434,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
362434 params : { } ,
363435 }
364436 }
365- const params = this . whereParams ( filters ) ;
437+ const params = this . whereParams ( resource , filters ) ;
366438 const where = Object . keys ( params ) . reduce ( ( w , paramKey ) => {
367439 // remove first char of string (will be "f" or "p") to leave only index
368440 const keyIndex = paramKey . substring ( 1 ) ;
@@ -388,6 +460,7 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
388460 } ) . join ( ', ' ) ;
389461 const tableName = resource . table ;
390462
463+ console . log ( 'getDataWithOriginalTypes called with filters' , JSON . stringify ( filters ) , 'and sort' , JSON . stringify ( sort ) ) ;
391464 const { where, params } = this . whereClause ( resource , filters ) ;
392465
393466 const orderBy = sort . length ? `ORDER BY ${ sort . map ( ( s ) => `${ s . field } ${ this . SortDirectionsMap [ s . direction ] } ` ) . join ( ', ' ) } ` : '' ;
0 commit comments