@@ -10,6 +10,7 @@ import { knex, Knex } from 'knex';
1010import nullthrows from 'nullthrows' ;
1111import { setTimeout } from 'timers/promises' ;
1212
13+ import { SearchStrategy } from '../AuthorizationResultBasedKnexEntityLoader' ;
1314import { OrderByOrdering } from '../BasePostgresEntityDatabaseAdapter' ;
1415import { raw , sql , SQLFragment , SQLFragmentHelpers } from '../SQLOperator' ;
1516import { PostgresTestEntity } from '../__testfixtures__/PostgresTestEntity' ;
@@ -1862,5 +1863,175 @@ describe('postgres entity integration', () => {
18621863 expect ( pageDefaultNoTotal . edges ) . toHaveLength ( 3 ) ;
18631864 expect ( pageDefaultNoTotal . totalCount ) . toBeUndefined ( ) ;
18641865 } ) ;
1866+
1867+ it ( 'supports search with ILIKE strategy' , async ( ) => {
1868+ const vc = new ViewerContext ( createKnexIntegrationTestEntityCompanionProvider ( knexInstance ) ) ;
1869+
1870+ await PostgresTestEntity . dropPostgresTableAsync ( knexInstance ) ;
1871+ await PostgresTestEntity . createOrTruncatePostgresTableAsync ( knexInstance ) ;
1872+
1873+ // Create test data with searchable names
1874+ const names = [ 'Alice Johnson' , 'Bob Smith' , 'Charlie Brown' , 'David Smith' , 'Eve Johnson' , 'Frank Miller' ] ;
1875+ for ( let i = 0 ; i < names . length ; i ++ ) {
1876+ await PostgresTestEntity . creator ( vc )
1877+ . setField ( 'name' , names [ i ] ! )
1878+ . setField ( 'hasACat' , i % 2 === 0 )
1879+ . createAsync ( ) ;
1880+ }
1881+
1882+ // Search for names containing "Johnson"
1883+ const searchResults = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1884+ first : 10 ,
1885+ search : {
1886+ strategy : SearchStrategy . ILIKE ,
1887+ term : 'Johnson' ,
1888+ fields : [ 'name' ] ,
1889+ } ,
1890+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1891+ } ) ;
1892+
1893+ expect ( searchResults . edges ) . toHaveLength ( 2 ) ;
1894+ expect ( searchResults . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'Alice Johnson' ) ;
1895+ expect ( searchResults . edges [ 1 ] ?. node . getField ( 'name' ) ) . toBe ( 'Eve Johnson' ) ;
1896+
1897+ // Search for names containing "Smith" with pagination
1898+ const smithPage1 = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1899+ first : 1 ,
1900+ search : {
1901+ strategy : SearchStrategy . ILIKE ,
1902+ term : 'Smith' ,
1903+ fields : [ 'name' ] ,
1904+ } ,
1905+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1906+ } ) ;
1907+
1908+ expect ( smithPage1 . edges ) . toHaveLength ( 1 ) ;
1909+ expect ( smithPage1 . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'Bob Smith' ) ;
1910+ expect ( smithPage1 . pageInfo . hasNextPage ) . toBe ( true ) ;
1911+
1912+ // Get next page
1913+ const smithPage2 = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1914+ first : 1 ,
1915+ after : smithPage1 . pageInfo . endCursor ! ,
1916+ search : {
1917+ strategy : SearchStrategy . ILIKE ,
1918+ term : 'Smith' ,
1919+ fields : [ 'name' ] ,
1920+ } ,
1921+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1922+ } ) ;
1923+
1924+ expect ( smithPage2 . edges ) . toHaveLength ( 1 ) ;
1925+ expect ( smithPage2 . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'David Smith' ) ;
1926+ expect ( smithPage2 . pageInfo . hasNextPage ) . toBe ( false ) ;
1927+
1928+ // Test partial match (case insensitive)
1929+ const partialMatch = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1930+ first : 10 ,
1931+ search : {
1932+ strategy : SearchStrategy . ILIKE ,
1933+ term : 'john' ,
1934+ fields : [ 'name' ] ,
1935+ } ,
1936+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1937+ } ) ;
1938+
1939+ expect ( partialMatch . edges ) . toHaveLength ( 2 ) ;
1940+ expect ( partialMatch . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'Alice Johnson' ) ;
1941+ expect ( partialMatch . edges [ 1 ] ?. node . getField ( 'name' ) ) . toBe ( 'Eve Johnson' ) ;
1942+
1943+ // Test search with WHERE clause
1944+ const combinedFilter = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1945+ first : 10 ,
1946+ where : sql `has_a_cat = ${ true } ` ,
1947+ search : {
1948+ strategy : SearchStrategy . ILIKE ,
1949+ term : 'Johnson' ,
1950+ fields : [ 'name' ] ,
1951+ } ,
1952+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1953+ } ) ;
1954+
1955+ // Both Alice Johnson (index 0) and Eve Johnson (index 4) have cats
1956+ expect ( combinedFilter . edges ) . toHaveLength ( 2 ) ;
1957+ expect ( combinedFilter . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'Alice Johnson' ) ;
1958+ expect ( combinedFilter . edges [ 0 ] ?. node . getField ( 'hasACat' ) ) . toBe ( true ) ;
1959+ expect ( combinedFilter . edges [ 1 ] ?. node . getField ( 'name' ) ) . toBe ( 'Eve Johnson' ) ;
1960+ expect ( combinedFilter . edges [ 1 ] ?. node . getField ( 'hasACat' ) ) . toBe ( true ) ;
1961+
1962+ // Test search with includeTotal
1963+ const withTotal = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1964+ first : 1 ,
1965+ search : {
1966+ strategy : SearchStrategy . ILIKE ,
1967+ term : 'Smith' ,
1968+ fields : [ 'name' ] ,
1969+ } ,
1970+ orderBy : [ { fieldName : 'name' , order : OrderByOrdering . ASCENDING } ] ,
1971+ includeTotal : true ,
1972+ } ) ;
1973+
1974+ expect ( withTotal . edges ) . toHaveLength ( 1 ) ;
1975+ expect ( withTotal . totalCount ) . toBe ( 2 ) ; // Bob Smith and David Smith
1976+ } ) ;
1977+
1978+ it ( 'supports trigram similarity search' , async ( ) => {
1979+ const vc = new ViewerContext ( createKnexIntegrationTestEntityCompanionProvider ( knexInstance ) ) ;
1980+
1981+ await PostgresTestEntity . dropPostgresTableAsync ( knexInstance ) ;
1982+ await PostgresTestEntity . createOrTruncatePostgresTableAsync ( knexInstance ) ;
1983+
1984+ // Enable pg_trgm extension for trigram similarity
1985+ await knexInstance . raw ( 'CREATE EXTENSION IF NOT EXISTS pg_trgm' ) ;
1986+
1987+ // Create test data with similar names
1988+ const names = [ 'Johnson' , 'Jonson' , 'Johnsen' , 'Smith' , 'Smyth' , 'Schmidt' ] ;
1989+ for ( let i = 0 ; i < names . length ; i ++ ) {
1990+ await PostgresTestEntity . creator ( vc )
1991+ . setField ( 'name' , names [ i ] ! )
1992+ . setField ( 'hasACat' , i < 3 ) // First 3 have cats
1993+ . createAsync ( ) ;
1994+ }
1995+
1996+ // Search for similar names to "Johnson" using trigram
1997+ const trigramSearch = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
1998+ first : 10 ,
1999+ search : {
2000+ strategy : SearchStrategy . TRIGRAM ,
2001+ term : 'Johnson' ,
2002+ fields : [ 'name' ] ,
2003+ threshold : 0.3 , // Similarity threshold
2004+ } ,
2005+ } ) ;
2006+
2007+ // Should find exact match and similar names, ordered by relevance
2008+ expect ( trigramSearch . edges . length ) . toBeGreaterThan ( 0 ) ;
2009+ // Exact match should come first due to ILIKE matching
2010+ expect ( trigramSearch . edges [ 0 ] ?. node . getField ( 'name' ) ) . toBe ( 'Johnson' ) ;
2011+
2012+ // The similar names (Jonson, Johnsen) should also be included
2013+ const foundNames = trigramSearch . edges . map ( e => e . node . getField ( 'name' ) ) ;
2014+ expect ( foundNames ) . toContain ( 'Jonson' ) ;
2015+ expect ( foundNames ) . toContain ( 'Johnsen' ) ;
2016+
2017+ // Test combining with WHERE clause
2018+ const filteredTrigram = await PostgresTestEntity . knexLoader ( vc ) . loadPageBySQLAsync ( {
2019+ first : 10 ,
2020+ where : sql `has_a_cat = ${ true } ` ,
2021+ search : {
2022+ strategy : SearchStrategy . TRIGRAM ,
2023+ term : 'Johnson' ,
2024+ fields : [ 'name' ] ,
2025+ threshold : 0.3 ,
2026+ } ,
2027+ } ) ;
2028+
2029+ // Only the Johnson-like names with cats
2030+ expect ( filteredTrigram . edges . length ) . toBeGreaterThan ( 0 ) ;
2031+ expect ( filteredTrigram . edges . length ) . toBeLessThanOrEqual ( 3 ) ;
2032+ filteredTrigram . edges . forEach ( edge => {
2033+ expect ( edge . node . getField ( 'hasACat' ) ) . toBe ( true ) ;
2034+ } ) ;
2035+ } ) ;
18652036 } ) ;
18662037} ) ;
0 commit comments