@@ -916,7 +916,7 @@ async fn test_execute_statement(test_db: PgPool) -> Result<()> {
916916 . find_map ( |action_or_cmd| match action_or_cmd {
917917 lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
918918 let command = code_action. command . as_ref ( ) ;
919- if command. is_some_and ( |cmd| & cmd. command == "pgt .executeStatement" ) {
919+ if command. is_some_and ( |cmd| & cmd. command == "pgls .executeStatement" ) {
920920 let command = command. unwrap ( ) ;
921921 let arguments = command. arguments . as_ref ( ) . unwrap ( ) . clone ( ) ;
922922 Some ( ( command. command . clone ( ) , arguments) )
@@ -952,6 +952,164 @@ async fn test_execute_statement(test_db: PgPool) -> Result<()> {
952952 Ok ( ( ) )
953953}
954954
955+ #[ sqlx:: test( migrator = "pgls_test_utils::MIGRATIONS" ) ]
956+ async fn test_invalidate_schema_cache ( test_db : PgPool ) -> Result < ( ) > {
957+ let factory = ServerFactory :: default ( ) ;
958+ let mut fs = MemoryFileSystem :: default ( ) ;
959+
960+ let database = test_db
961+ . connect_options ( )
962+ . get_database ( )
963+ . unwrap ( )
964+ . to_string ( ) ;
965+ let host = test_db. connect_options ( ) . get_host ( ) . to_string ( ) ;
966+
967+ // Setup: Create a table with only id column (no name column yet)
968+ let setup = r#"
969+ create table public.users (
970+ id serial primary key
971+ );
972+ "# ;
973+
974+ test_db
975+ . execute ( setup)
976+ . await
977+ . expect ( "Failed to setup test database" ) ;
978+
979+ let mut conf = PartialConfiguration :: init ( ) ;
980+ conf. merge_with ( PartialConfiguration {
981+ db : Some ( PartialDatabaseConfiguration {
982+ database : Some ( database) ,
983+ host : Some ( host) ,
984+ ..Default :: default ( )
985+ } ) ,
986+ ..Default :: default ( )
987+ } ) ;
988+
989+ fs. insert (
990+ url ! ( "postgres-language-server.jsonc" )
991+ . to_file_path ( )
992+ . unwrap ( ) ,
993+ serde_json:: to_string_pretty ( & conf) . unwrap ( ) ,
994+ ) ;
995+
996+ let ( service, client) = factory
997+ . create_with_fs ( None , DynRef :: Owned ( Box :: new ( fs) ) )
998+ . into_inner ( ) ;
999+
1000+ let ( stream, sink) = client. split ( ) ;
1001+ let mut server = Server :: new ( service) ;
1002+
1003+ let ( sender, _receiver) = channel ( CHANNEL_BUFFER_SIZE ) ;
1004+ let reader = tokio:: spawn ( client_handler ( stream, sink, sender) ) ;
1005+
1006+ server. initialize ( ) . await ?;
1007+ server. initialized ( ) . await ?;
1008+
1009+ server. load_configuration ( ) . await ?;
1010+
1011+ // Open a document to get completions from
1012+ let doc_content = "select from public.users;" ;
1013+ server. open_document ( doc_content) . await ?;
1014+
1015+ // Get completions before adding the column - 'name' should NOT be present
1016+ let completions_before = server
1017+ . get_completion ( CompletionParams {
1018+ work_done_progress_params : WorkDoneProgressParams :: default ( ) ,
1019+ partial_result_params : PartialResultParams :: default ( ) ,
1020+ context : None ,
1021+ text_document_position : TextDocumentPositionParams {
1022+ text_document : TextDocumentIdentifier {
1023+ uri : url ! ( "document.sql" ) ,
1024+ } ,
1025+ position : Position {
1026+ line : 0 ,
1027+ character : 7 ,
1028+ } ,
1029+ } ,
1030+ } )
1031+ . await ?
1032+ . unwrap ( ) ;
1033+
1034+ let items_before = match completions_before {
1035+ CompletionResponse :: Array ( ref a) => a,
1036+ CompletionResponse :: List ( ref l) => & l. items ,
1037+ } ;
1038+
1039+ let has_name_before = items_before. iter ( ) . any ( |item| {
1040+ item. label == "name"
1041+ && item. label_details . as_ref ( ) . is_some_and ( |d| {
1042+ d. description
1043+ . as_ref ( )
1044+ . is_some_and ( |desc| desc. contains ( "public.users" ) )
1045+ } )
1046+ } ) ;
1047+
1048+ assert ! (
1049+ !has_name_before,
1050+ "Column 'name' should not be in completions before it's added to the table"
1051+ ) ;
1052+
1053+ // Add the missing column to the database
1054+ let alter_table = r#"
1055+ alter table public.users
1056+ add column name text;
1057+ "# ;
1058+
1059+ test_db
1060+ . execute ( alter_table)
1061+ . await
1062+ . expect ( "Failed to add column to table" ) ;
1063+
1064+ // Invalidate the schema cache (all = false for current connection only)
1065+ server
1066+ . request :: < bool , ( ) > ( "pgt/invalidate_schema_cache" , "_invalidate_cache" , false )
1067+ . await ?;
1068+
1069+ // Get completions after invalidating cache - 'name' should NOW be present
1070+ let completions_after = server
1071+ . get_completion ( CompletionParams {
1072+ work_done_progress_params : WorkDoneProgressParams :: default ( ) ,
1073+ partial_result_params : PartialResultParams :: default ( ) ,
1074+ context : None ,
1075+ text_document_position : TextDocumentPositionParams {
1076+ text_document : TextDocumentIdentifier {
1077+ uri : url ! ( "document.sql" ) ,
1078+ } ,
1079+ position : Position {
1080+ line : 0 ,
1081+ character : 7 ,
1082+ } ,
1083+ } ,
1084+ } )
1085+ . await ?
1086+ . unwrap ( ) ;
1087+
1088+ let items_after = match completions_after {
1089+ CompletionResponse :: Array ( ref a) => a,
1090+ CompletionResponse :: List ( ref l) => & l. items ,
1091+ } ;
1092+
1093+ let has_name_after = items_after. iter ( ) . any ( |item| {
1094+ item. label == "name"
1095+ && item. label_details . as_ref ( ) . is_some_and ( |d| {
1096+ d. description
1097+ . as_ref ( )
1098+ . is_some_and ( |desc| desc. contains ( "public.users" ) )
1099+ } )
1100+ } ) ;
1101+
1102+ assert ! (
1103+ has_name_after,
1104+ "Column 'name' should be in completions after schema cache invalidation"
1105+ ) ;
1106+
1107+ server. shutdown ( ) . await ?;
1108+ reader. abort ( ) ;
1109+
1110+ Ok ( ( ) )
1111+ }
1112+
9551113#[ sqlx:: test( migrator = "pgls_test_utils::MIGRATIONS" ) ]
9561114async fn test_issue_281 ( test_db : PgPool ) -> Result < ( ) > {
9571115 let factory = ServerFactory :: default ( ) ;
0 commit comments