@@ -23,6 +23,7 @@ use sqlx::Executor;
2323use std:: any:: type_name;
2424use std:: fmt:: Display ;
2525use std:: time:: Duration ;
26+ use test_log:: test;
2627use tower:: timeout:: Timeout ;
2728use tower:: { Service , ServiceExt } ;
2829use tower_lsp:: LspService ;
@@ -558,6 +559,208 @@ async fn test_completions() -> Result<()> {
558559 Ok ( ( ) )
559560}
560561
562+ #[ tokio:: test]
563+ async fn test_issue_271 ( ) -> Result < ( ) > {
564+ let factory = ServerFactory :: default ( ) ;
565+ let mut fs = MemoryFileSystem :: default ( ) ;
566+ let test_db = get_new_test_db ( ) . await ;
567+
568+ let setup = r#"
569+ create table public.users (
570+ id serial primary key,
571+ name varchar(255) not null
572+ );
573+ "# ;
574+
575+ test_db
576+ . execute ( setup)
577+ . await
578+ . expect ( "Failed to setup test database" ) ;
579+
580+ let mut conf = PartialConfiguration :: init ( ) ;
581+ conf. merge_with ( PartialConfiguration {
582+ db : Some ( PartialDatabaseConfiguration {
583+ database : Some (
584+ test_db
585+ . connect_options ( )
586+ . get_database ( )
587+ . unwrap ( )
588+ . to_string ( ) ,
589+ ) ,
590+ ..Default :: default ( )
591+ } ) ,
592+ ..Default :: default ( )
593+ } ) ;
594+ fs. insert (
595+ url ! ( "postgrestools.jsonc" ) . to_file_path ( ) . unwrap ( ) ,
596+ serde_json:: to_string_pretty ( & conf) . unwrap ( ) ,
597+ ) ;
598+
599+ let ( service, client) = factory
600+ . create_with_fs ( None , DynRef :: Owned ( Box :: new ( fs) ) )
601+ . into_inner ( ) ;
602+
603+ let ( stream, sink) = client. split ( ) ;
604+ let mut server = Server :: new ( service) ;
605+
606+ let ( sender, _) = channel ( CHANNEL_BUFFER_SIZE ) ;
607+ let reader = tokio:: spawn ( client_handler ( stream, sink, sender) ) ;
608+
609+ server. initialize ( ) . await ?;
610+ server. initialized ( ) . await ?;
611+
612+ server. load_configuration ( ) . await ?;
613+
614+ server
615+ . open_document ( "CREATE COLLATION ignore_accent_case (provider = icu, deterministic = false, locale = 'und-u-ks-level1');\n \n -- CREATE OR REPLACE FUNCTION\n -- add_one(integer)\n -- RETURNS\n -- integer\n -- AS\n -- 'add_one.so', 'add_one'\n -- LANGUAGE\n -- C \n -- STRICT;\n \n \n SELECT pwhash, FROM users;" )
616+ . await ?;
617+
618+ server
619+ . change_document (
620+ 3 ,
621+ vec ! [ TextDocumentContentChangeEvent {
622+ range: Some ( Range {
623+ start: Position {
624+ line: 13 ,
625+ character: 13 ,
626+ } ,
627+ end: Position {
628+ line: 13 ,
629+ character: 14 ,
630+ } ,
631+ } ) ,
632+ range_length: Some ( 0 ) ,
633+ text: "" . to_string( ) ,
634+ } ] ,
635+ )
636+ . await ?;
637+
638+ server
639+ . change_document (
640+ 1 ,
641+ vec ! [ TextDocumentContentChangeEvent {
642+ range: Some ( Range {
643+ start: Position {
644+ line: 13 ,
645+ character: 13 ,
646+ } ,
647+ end: Position {
648+ line: 13 ,
649+ character: 13 ,
650+ } ,
651+ } ) ,
652+ range_length: Some ( 0 ) ,
653+ text: "," . to_string( ) ,
654+ } ] ,
655+ )
656+ . await ?;
657+
658+ server
659+ . change_document (
660+ 2 ,
661+ vec ! [ TextDocumentContentChangeEvent {
662+ range: Some ( Range {
663+ start: Position {
664+ line: 13 ,
665+ character: 14 ,
666+ } ,
667+ end: Position {
668+ line: 13 ,
669+ character: 14 ,
670+ } ,
671+ } ) ,
672+ range_length: Some ( 0 ) ,
673+ text: " " . to_string( ) ,
674+ } ] ,
675+ )
676+ . await ?;
677+
678+ server
679+ . change_document (
680+ 3 ,
681+ vec ! [ TextDocumentContentChangeEvent {
682+ range: Some ( Range {
683+ start: Position {
684+ line: 13 ,
685+ character: 15 ,
686+ } ,
687+ end: Position {
688+ line: 13 ,
689+ character: 15 ,
690+ } ,
691+ } ) ,
692+ range_length: Some ( 0 ) ,
693+ text: "county_name" . to_string( ) ,
694+ } ] ,
695+ )
696+ . await ?;
697+
698+ server
699+ . change_document (
700+ 4 ,
701+ vec ! [ TextDocumentContentChangeEvent {
702+ range: Some ( Range {
703+ start: Position {
704+ line: 13 ,
705+ character: 13 ,
706+ } ,
707+ end: Position {
708+ line: 13 ,
709+ character: 26 ,
710+ } ,
711+ } ) ,
712+ range_length: Some ( 13 ) ,
713+ text: "" . to_string( ) ,
714+ } ] ,
715+ )
716+ . await ?;
717+
718+ server
719+ . change_document (
720+ 5 ,
721+ vec ! [ TextDocumentContentChangeEvent {
722+ range: Some ( Range {
723+ start: Position {
724+ line: 13 ,
725+ character: 13 ,
726+ } ,
727+ end: Position {
728+ line: 13 ,
729+ character: 13 ,
730+ } ,
731+ } ) ,
732+ range_length: Some ( 0 ) ,
733+ text: "," . to_string( ) ,
734+ } ] ,
735+ )
736+ . await ?;
737+
738+ // crashes with range end index 37 out of range for slice of length 26
739+ let res = server
740+ . get_completion ( CompletionParams {
741+ work_done_progress_params : WorkDoneProgressParams :: default ( ) ,
742+ partial_result_params : PartialResultParams :: default ( ) ,
743+ context : None ,
744+ text_document_position : TextDocumentPositionParams {
745+ text_document : TextDocumentIdentifier {
746+ uri : url ! ( "document.sql" ) ,
747+ } ,
748+ position : Position {
749+ line : 13 ,
750+ character : 14 ,
751+ } ,
752+ } ,
753+ } )
754+ . await ?;
755+
756+ assert ! ( res. is_some( ) ) ;
757+
758+ server. shutdown ( ) . await ?;
759+ reader. abort ( ) ;
760+
761+ Ok ( ( ) )
762+ }
763+
561764#[ tokio:: test]
562765async fn test_execute_statement ( ) -> Result < ( ) > {
563766 let factory = ServerFactory :: default ( ) ;
0 commit comments