@@ -483,6 +483,220 @@ describe('search_database_objects tool', () => {
483483 } ) ;
484484 } ) ;
485485
486+ describe ( 'table filter' , ( ) => {
487+ describe ( 'for columns' , ( ) => {
488+ beforeEach ( ( ) => {
489+ vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
490+ } ) ;
491+
492+ it ( 'should filter columns by table when table parameter is provided' , async ( ) => {
493+ const usersColumns : TableColumn [ ] = [
494+ { column_name : 'id' , data_type : 'INTEGER' , is_nullable : 'NO' , column_default : null } ,
495+ { column_name : 'name' , data_type : 'TEXT' , is_nullable : 'YES' , column_default : null } ,
496+ ] ;
497+
498+ const ordersColumns : TableColumn [ ] = [
499+ { column_name : 'id' , data_type : 'INTEGER' , is_nullable : 'NO' , column_default : null } ,
500+ { column_name : 'user_id' , data_type : 'INTEGER' , is_nullable : 'NO' , column_default : null } ,
501+ ] ;
502+
503+ vi . mocked ( mockConnector . getTableSchema ) . mockImplementation ( async ( table ) => {
504+ if ( table === 'users' ) return usersColumns ;
505+ if ( table === 'orders' ) return ordersColumns ;
506+ return [ ] ;
507+ } ) ;
508+
509+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
510+ const result = await handler (
511+ {
512+ object_type : 'column' ,
513+ pattern : '%' ,
514+ schema : 'public' ,
515+ table : 'users' ,
516+ detail_level : 'names' ,
517+ } ,
518+ null
519+ ) ;
520+
521+ const parsed = parseToolResponse ( result ) ;
522+ expect ( parsed . success ) . toBe ( true ) ;
523+ expect ( parsed . data . count ) . toBe ( 2 ) ;
524+ expect ( parsed . data . results ) . toEqual ( [
525+ { name : 'id' , table : 'users' , schema : 'public' } ,
526+ { name : 'name' , table : 'users' , schema : 'public' } ,
527+ ] ) ;
528+ // Verify only users table was queried
529+ expect ( mockConnector . getTableSchema ) . toHaveBeenCalledWith ( 'users' , 'public' ) ;
530+ expect ( mockConnector . getTableSchema ) . not . toHaveBeenCalledWith ( 'orders' , 'public' ) ;
531+ } ) ;
532+
533+ it ( 'should require schema when table is specified' , async ( ) => {
534+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
535+ const result = await handler (
536+ {
537+ object_type : 'column' ,
538+ pattern : '%' ,
539+ table : 'users' ,
540+ detail_level : 'names' ,
541+ } ,
542+ null
543+ ) ;
544+
545+ expect ( result . isError ) . toBe ( true ) ;
546+ const parsed = parseToolResponse ( result ) ;
547+ expect ( parsed . code ) . toBe ( 'SCHEMA_REQUIRED' ) ;
548+ expect ( parsed . error ) . toContain ( "'table' parameter requires 'schema'" ) ;
549+ } ) ;
550+
551+ it ( 'should work with column pattern when table filter is applied' , async ( ) => {
552+ const usersColumns : TableColumn [ ] = [
553+ { column_name : 'id' , data_type : 'INTEGER' , is_nullable : 'NO' , column_default : null } ,
554+ { column_name : 'name' , data_type : 'TEXT' , is_nullable : 'YES' , column_default : null } ,
555+ { column_name : 'email' , data_type : 'TEXT' , is_nullable : 'YES' , column_default : null } ,
556+ ] ;
557+
558+ vi . mocked ( mockConnector . getTableSchema ) . mockResolvedValue ( usersColumns ) ;
559+
560+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
561+ const result = await handler (
562+ {
563+ object_type : 'column' ,
564+ pattern : '%e%' ,
565+ schema : 'public' ,
566+ table : 'users' ,
567+ detail_level : 'names' ,
568+ } ,
569+ null
570+ ) ;
571+
572+ const parsed = parseToolResponse ( result ) ;
573+ expect ( parsed . data . count ) . toBe ( 2 ) ;
574+ expect ( parsed . data . results . map ( ( r : any ) => r . name ) ) . toEqual ( [ 'name' , 'email' ] ) ;
575+ } ) ;
576+ } ) ;
577+
578+ describe ( 'for indexes' , ( ) => {
579+ beforeEach ( ( ) => {
580+ vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
581+ } ) ;
582+
583+ it ( 'should filter indexes by table when table parameter is provided' , async ( ) => {
584+ const usersIndexes : TableIndex [ ] = [
585+ {
586+ index_name : 'users_pkey' ,
587+ column_names : [ 'id' ] ,
588+ is_unique : true ,
589+ is_primary : true ,
590+ } ,
591+ {
592+ index_name : 'users_email_idx' ,
593+ column_names : [ 'email' ] ,
594+ is_unique : true ,
595+ is_primary : false ,
596+ } ,
597+ ] ;
598+
599+ const ordersIndexes : TableIndex [ ] = [
600+ {
601+ index_name : 'orders_pkey' ,
602+ column_names : [ 'id' ] ,
603+ is_unique : true ,
604+ is_primary : true ,
605+ } ,
606+ ] ;
607+
608+ vi . mocked ( mockConnector . getTableIndexes ) . mockImplementation ( async ( table ) => {
609+ if ( table === 'users' ) return usersIndexes ;
610+ if ( table === 'orders' ) return ordersIndexes ;
611+ return [ ] ;
612+ } ) ;
613+
614+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
615+ const result = await handler (
616+ {
617+ object_type : 'index' ,
618+ pattern : '%' ,
619+ schema : 'public' ,
620+ table : 'users' ,
621+ detail_level : 'names' ,
622+ } ,
623+ null
624+ ) ;
625+
626+ const parsed = parseToolResponse ( result ) ;
627+ expect ( parsed . success ) . toBe ( true ) ;
628+ expect ( parsed . data . count ) . toBe ( 2 ) ;
629+ expect ( parsed . data . results . map ( ( r : any ) => r . name ) ) . toEqual ( [ 'users_pkey' , 'users_email_idx' ] ) ;
630+ // Verify only users table was queried
631+ expect ( mockConnector . getTableIndexes ) . toHaveBeenCalledWith ( 'users' , 'public' ) ;
632+ expect ( mockConnector . getTableIndexes ) . not . toHaveBeenCalledWith ( 'orders' , 'public' ) ;
633+ } ) ;
634+ } ) ;
635+
636+ describe ( 'validation' , ( ) => {
637+ it ( 'should reject table parameter for schema object type' , async ( ) => {
638+ vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
639+
640+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
641+ const result = await handler (
642+ {
643+ object_type : 'schema' ,
644+ pattern : '%' ,
645+ schema : 'public' ,
646+ table : 'users' ,
647+ detail_level : 'names' ,
648+ } ,
649+ null
650+ ) ;
651+
652+ expect ( result . isError ) . toBe ( true ) ;
653+ const parsed = parseToolResponse ( result ) ;
654+ expect ( parsed . code ) . toBe ( 'INVALID_TABLE_FILTER' ) ;
655+ expect ( parsed . error ) . toContain ( "only applies to object_type 'column' or 'index'" ) ;
656+ } ) ;
657+
658+ it ( 'should reject table parameter for table object type' , async ( ) => {
659+ vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
660+
661+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
662+ const result = await handler (
663+ {
664+ object_type : 'table' ,
665+ pattern : '%' ,
666+ schema : 'public' ,
667+ table : 'users' ,
668+ detail_level : 'names' ,
669+ } ,
670+ null
671+ ) ;
672+
673+ expect ( result . isError ) . toBe ( true ) ;
674+ const parsed = parseToolResponse ( result ) ;
675+ expect ( parsed . code ) . toBe ( 'INVALID_TABLE_FILTER' ) ;
676+ } ) ;
677+
678+ it ( 'should reject table parameter for procedure object type' , async ( ) => {
679+ vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
680+
681+ const handler = createSearchDatabaseObjectsToolHandler ( ) ;
682+ const result = await handler (
683+ {
684+ object_type : 'procedure' ,
685+ pattern : '%' ,
686+ schema : 'public' ,
687+ table : 'users' ,
688+ detail_level : 'names' ,
689+ } ,
690+ null
691+ ) ;
692+
693+ expect ( result . isError ) . toBe ( true ) ;
694+ const parsed = parseToolResponse ( result ) ;
695+ expect ( parsed . code ) . toBe ( 'INVALID_TABLE_FILTER' ) ;
696+ } ) ;
697+ } ) ;
698+ } ) ;
699+
486700 describe ( 'error handling' , ( ) => {
487701 it ( 'should validate schema exists' , async ( ) => {
488702 vi . mocked ( mockConnector . getSchemas ) . mockResolvedValue ( [ 'public' ] ) ;
0 commit comments