@@ -528,61 +528,69 @@ impl<T: Model> FullTextSearchBuilder<T> {
528528
529529 fn build_postgres_sql ( & self ) -> Result < ( String , Vec < Value > ) > {
530530 let table = quote_ident ( DatabaseType :: Postgres , T :: table_name ( ) ) ;
531- let language = self . config . language . as_deref ( ) . unwrap_or ( "english" ) ;
532531 let mut params = Vec :: new ( ) ;
532+ let language_placeholder = self . push_param (
533+ DatabaseType :: Postgres ,
534+ & mut params,
535+ Value :: String ( Some (
536+ self . config
537+ . language
538+ . clone ( )
539+ . unwrap_or_else ( || "english" . to_string ( ) ) ,
540+ ) ) ,
541+ ) ;
533542
534543 // Build tsvector expression for columns
535- let tsvector_expr = self . build_pg_tsvector_expr ( language ) ;
544+ let tsvector_expr = self . build_pg_tsvector_expr ( & language_placeholder ) ;
536545
537546 // Build tsquery based on search mode
538- let tsquery_expr = self . build_pg_tsquery_expr ( language , & mut params) ;
547+ let tsquery_expr = self . build_pg_tsquery_expr ( & language_placeholder , & mut params) ;
539548
540549 let mut sql = format ! (
541550 "SELECT * FROM {} WHERE {} @@ {}" ,
542551 table, tsvector_expr, tsquery_expr
543552 ) ;
544553
545554 if self . with_ranking {
546- let weights = self
547- . config
548- . weights
549- . as_ref ( )
550- . map ( |w| w. to_pg_array ( ) )
551- . unwrap_or_else ( || "'{0.1,0.2,0.4,1.0}'" . to_string ( ) ) ;
555+ let weights_placeholder = self . pg_weights_placeholder ( & mut params) ;
552556 sql = format ! (
553- "SELECT *, ts_rank_cd({}, {}, {}) AS _fts_rank FROM {} WHERE {} @@ {} ORDER BY _fts_rank DESC" ,
554- weights, tsvector_expr, tsquery_expr, table, tsvector_expr, tsquery_expr
557+ "SELECT *, ts_rank_cd(CAST({} AS real[]), {}, {}) AS _fts_rank FROM {} WHERE {} @@ {} ORDER BY _fts_rank DESC" ,
558+ weights_placeholder,
559+ tsvector_expr,
560+ tsquery_expr,
561+ table,
562+ tsvector_expr,
563+ tsquery_expr
555564 ) ;
556565 }
557566
558- if let Some ( limit) = self . limit {
559- sql. push_str ( & format ! ( " LIMIT {}" , limit) ) ;
560- }
561- if let Some ( offset) = self . offset {
562- sql. push_str ( & format ! ( " OFFSET {}" , offset) ) ;
563- }
567+ self . append_limit_offset ( DatabaseType :: Postgres , & mut sql, & mut params) ?;
564568
565569 Ok ( ( sql, params) )
566570 }
567571
568572 fn build_postgres_ranked_sql ( & self ) -> Result < ( String , Vec < Value > ) > {
569573 let table = quote_ident ( DatabaseType :: Postgres , T :: table_name ( ) ) ;
570- let language = self . config . language . as_deref ( ) . unwrap_or ( "english" ) ;
571574 let mut params = Vec :: new ( ) ;
575+ let language_placeholder = self . push_param (
576+ DatabaseType :: Postgres ,
577+ & mut params,
578+ Value :: String ( Some (
579+ self . config
580+ . language
581+ . clone ( )
582+ . unwrap_or_else ( || "english" . to_string ( ) ) ,
583+ ) ) ,
584+ ) ;
572585
573- let tsvector_expr = self . build_pg_tsvector_expr ( language ) ;
574- let tsquery_expr = self . build_pg_tsquery_expr ( language , & mut params) ;
586+ let tsvector_expr = self . build_pg_tsvector_expr ( & language_placeholder ) ;
587+ let tsquery_expr = self . build_pg_tsquery_expr ( & language_placeholder , & mut params) ;
575588
576- let weights = self
577- . config
578- . weights
579- . as_ref ( )
580- . map ( |w| w. to_pg_array ( ) )
581- . unwrap_or_else ( || "'{0.1,0.2,0.4,1.0}'" . to_string ( ) ) ;
589+ let weights_placeholder = self . pg_weights_placeholder ( & mut params) ;
582590
583591 let mut sql = format ! (
584- "SELECT *, ts_rank_cd({} , {}, {}) AS _fts_rank FROM {} WHERE {} @@ {}" ,
585- weights , tsvector_expr, tsquery_expr, table, tsvector_expr, tsquery_expr
592+ "SELECT *, ts_rank_cd(CAST({} AS real[]) , {}, {}) AS _fts_rank FROM {} WHERE {} @@ {}" ,
593+ weights_placeholder , tsvector_expr, tsquery_expr, table, tsvector_expr, tsquery_expr
586594 ) ;
587595
588596 if let Some ( min_rank) = self . min_rank {
@@ -592,30 +600,34 @@ impl<T: Model> FullTextSearchBuilder<T> {
592600 Value :: Double ( Some ( min_rank) ) ,
593601 ) ;
594602 sql. push_str ( & format ! (
595- " AND ts_rank_cd({} , {}, {}) >= {}" ,
596- weights , tsvector_expr, tsquery_expr, min_rank_placeholder
603+ " AND ts_rank_cd(CAST({} AS real[]) , {}, {}) >= {}" ,
604+ weights_placeholder , tsvector_expr, tsquery_expr, min_rank_placeholder
597605 ) ) ;
598606 }
599607
600608 sql. push_str ( " ORDER BY _fts_rank DESC" ) ;
601609
602- if let Some ( limit) = self . limit {
603- sql. push_str ( & format ! ( " LIMIT {}" , limit) ) ;
604- }
605- if let Some ( offset) = self . offset {
606- sql. push_str ( & format ! ( " OFFSET {}" , offset) ) ;
607- }
610+ self . append_limit_offset ( DatabaseType :: Postgres , & mut sql, & mut params) ?;
608611
609612 Ok ( ( sql, params) )
610613 }
611614
612615 fn build_postgres_count_sql ( & self ) -> Result < ( String , Vec < Value > ) > {
613616 let table = quote_ident ( DatabaseType :: Postgres , T :: table_name ( ) ) ;
614- let language = self . config . language . as_deref ( ) . unwrap_or ( "english" ) ;
615617 let mut params = Vec :: new ( ) ;
618+ let language_placeholder = self . push_param (
619+ DatabaseType :: Postgres ,
620+ & mut params,
621+ Value :: String ( Some (
622+ self . config
623+ . language
624+ . clone ( )
625+ . unwrap_or_else ( || "english" . to_string ( ) ) ,
626+ ) ) ,
627+ ) ;
616628
617- let tsvector_expr = self . build_pg_tsvector_expr ( language ) ;
618- let tsquery_expr = self . build_pg_tsquery_expr ( language , & mut params) ;
629+ let tsvector_expr = self . build_pg_tsvector_expr ( & language_placeholder ) ;
630+ let tsquery_expr = self . build_pg_tsquery_expr ( & language_placeholder , & mut params) ;
619631
620632 Ok ( (
621633 format ! (
@@ -626,12 +638,11 @@ impl<T: Model> FullTextSearchBuilder<T> {
626638 ) )
627639 }
628640
629- fn build_pg_tsvector_expr ( & self , language : & str ) -> String {
630- let language = escape_string ( language) ;
641+ fn build_pg_tsvector_expr ( & self , language_placeholder : & str ) -> String {
631642 if self . columns . len ( ) == 1 {
632643 format ! (
633- "to_tsvector('{}' , COALESCE({}, ''))" ,
634- language ,
644+ "to_tsvector(CAST({} AS regconfig) , COALESCE({}, ''))" ,
645+ language_placeholder ,
635646 quote_ident( DatabaseType :: Postgres , & self . columns[ 0 ] )
636647 )
637648 } else {
@@ -640,36 +651,48 @@ impl<T: Model> FullTextSearchBuilder<T> {
640651 . iter ( )
641652 . map ( |c| format ! ( "COALESCE({}, '')" , quote_ident( DatabaseType :: Postgres , c) ) )
642653 . collect ( ) ;
643- format ! ( "to_tsvector('{}', {})" , language, cols. join( " || ' ' || " ) )
654+ format ! (
655+ "to_tsvector(CAST({} AS regconfig), {})" ,
656+ language_placeholder,
657+ cols. join( " || ' ' || " )
658+ )
644659 }
645660 }
646661
647- fn build_pg_tsquery_expr ( & self , language : & str , params : & mut Vec < Value > ) -> String {
648- let language = escape_string ( language) ;
662+ fn build_pg_tsquery_expr ( & self , language_placeholder : & str , params : & mut Vec < Value > ) -> String {
649663 match self . config . mode {
650664 SearchMode :: Natural => {
651665 let placeholder = self . push_param (
652666 DatabaseType :: Postgres ,
653667 params,
654668 Value :: String ( Some ( self . query . clone ( ) ) ) ,
655669 ) ;
656- format ! ( "plainto_tsquery('{}', {})" , language, placeholder)
670+ format ! (
671+ "plainto_tsquery(CAST({} AS regconfig), {})" ,
672+ language_placeholder, placeholder
673+ )
657674 }
658675 SearchMode :: Boolean => {
659676 let placeholder = self . push_param (
660677 DatabaseType :: Postgres ,
661678 params,
662679 Value :: String ( Some ( self . query . clone ( ) ) ) ,
663680 ) ;
664- format ! ( "to_tsquery('{}', {})" , language, placeholder)
681+ format ! (
682+ "to_tsquery(CAST({} AS regconfig), {})" ,
683+ language_placeholder, placeholder
684+ )
665685 }
666686 SearchMode :: Phrase => {
667687 let placeholder = self . push_param (
668688 DatabaseType :: Postgres ,
669689 params,
670690 Value :: String ( Some ( self . query . clone ( ) ) ) ,
671691 ) ;
672- format ! ( "phraseto_tsquery('{}', {})" , language, placeholder)
692+ format ! (
693+ "phraseto_tsquery(CAST({} AS regconfig), {})" ,
694+ language_placeholder, placeholder
695+ )
673696 }
674697 SearchMode :: Prefix => {
675698 let words: Vec < & str > = self . query . split_whitespace ( ) . collect ( ) ;
@@ -679,15 +702,21 @@ impl<T: Model> FullTextSearchBuilder<T> {
679702 params,
680703 Value :: String ( Some ( prefixed. join ( " & " ) ) ) ,
681704 ) ;
682- format ! ( "to_tsquery('{}', {})" , language, placeholder)
705+ format ! (
706+ "to_tsquery(CAST({} AS regconfig), {})" ,
707+ language_placeholder, placeholder
708+ )
683709 }
684710 SearchMode :: Fuzzy => {
685711 let placeholder = self . push_param (
686712 DatabaseType :: Postgres ,
687713 params,
688714 Value :: String ( Some ( self . query . clone ( ) ) ) ,
689715 ) ;
690- format ! ( "plainto_tsquery('{}', {})" , language, placeholder)
716+ format ! (
717+ "plainto_tsquery(CAST({} AS regconfig), {})" ,
718+ language_placeholder, placeholder
719+ )
691720 }
692721 SearchMode :: Proximity ( distance) => {
693722 let words: Vec < & str > = self . query . split_whitespace ( ) . collect ( ) ;
@@ -697,7 +726,10 @@ impl<T: Model> FullTextSearchBuilder<T> {
697726 params,
698727 Value :: String ( Some ( proximity. join ( & format ! ( " <{}> " , distance) ) ) ) ,
699728 ) ;
700- format ! ( "to_tsquery('{}', {})" , language, placeholder)
729+ format ! (
730+ "to_tsquery(CAST({} AS regconfig), {})" ,
731+ language_placeholder, placeholder
732+ )
701733 }
702734 }
703735 }
@@ -734,12 +766,7 @@ impl<T: Model> FullTextSearchBuilder<T> {
734766 table, columns_str, query_placeholder, mode_modifier
735767 ) ;
736768
737- if let Some ( limit) = self . limit {
738- sql. push_str ( & format ! ( "LIMIT {} " , limit) ) ;
739- }
740- if let Some ( offset) = self . offset {
741- sql. push_str ( & format ! ( "OFFSET {} " , offset) ) ;
742- }
769+ self . append_limit_offset ( DatabaseType :: MySQL , & mut sql, & mut params) ?;
743770
744771 Ok ( ( sql, params) )
745772 }
@@ -803,12 +830,7 @@ impl<T: Model> FullTextSearchBuilder<T> {
803830
804831 sql. push_str ( "ORDER BY _fts_rank DESC " ) ;
805832
806- if let Some ( limit) = self . limit {
807- sql. push_str ( & format ! ( "LIMIT {} " , limit) ) ;
808- }
809- if let Some ( offset) = self . offset {
810- sql. push_str ( & format ! ( "OFFSET {} " , offset) ) ;
811- }
833+ self . append_limit_offset ( DatabaseType :: MySQL , & mut sql, & mut params) ?;
812834
813835 Ok ( ( sql, params) )
814836 }
@@ -869,12 +891,7 @@ impl<T: Model> FullTextSearchBuilder<T> {
869891 table, fts_table, fts_table, query_placeholder
870892 ) ;
871893
872- if let Some ( limit) = self . limit {
873- sql. push_str ( & format ! ( "LIMIT {} " , limit) ) ;
874- }
875- if let Some ( offset) = self . offset {
876- sql. push_str ( & format ! ( "OFFSET {} " , offset) ) ;
877- }
894+ self . append_limit_offset ( DatabaseType :: SQLite , & mut sql, & mut params) ?;
878895
879896 Ok ( ( sql, params) )
880897 }
@@ -914,12 +931,7 @@ impl<T: Model> FullTextSearchBuilder<T> {
914931 // BM25 returns negative values, so ORDER BY ASC for best matches
915932 sql. push_str ( & format ! ( "ORDER BY bm25({}) " , fts_table) ) ;
916933
917- if let Some ( limit) = self . limit {
918- sql. push_str ( & format ! ( "LIMIT {} " , limit) ) ;
919- }
920- if let Some ( offset) = self . offset {
921- sql. push_str ( & format ! ( "OFFSET {} " , offset) ) ;
922- }
934+ self . append_limit_offset ( DatabaseType :: SQLite , & mut sql, & mut params) ?;
923935
924936 Ok ( ( sql, params) )
925937 }
@@ -955,6 +967,38 @@ impl<T: Model> FullTextSearchBuilder<T> {
955967 params. push ( value) ;
956968 placeholder
957969 }
970+
971+ fn pg_weights_placeholder ( & self , params : & mut Vec < Value > ) -> String {
972+ let weights = self
973+ . config
974+ . weights
975+ . as_ref ( )
976+ . map ( |w| w. to_pg_array ( ) . trim_matches ( '\'' ) . to_string ( ) )
977+ . unwrap_or_else ( || "{0.1,0.2,0.4,1.0}" . to_string ( ) ) ;
978+
979+ self . push_param ( DatabaseType :: Postgres , params, Value :: String ( Some ( weights) ) )
980+ }
981+
982+ fn append_limit_offset (
983+ & self ,
984+ db_type : DatabaseType ,
985+ sql : & mut String ,
986+ params : & mut Vec < Value > ,
987+ ) -> Result < ( ) > {
988+ if let Some ( limit) = self . limit {
989+ let limit_value = i64:: try_from ( limit)
990+ . map_err ( |_| Error :: query ( "Full-text search limit exceeds i64 range" ) ) ?;
991+ let placeholder = self . push_param ( db_type, params, Value :: BigInt ( Some ( limit_value) ) ) ;
992+ sql. push_str ( & format ! ( " LIMIT {}" , placeholder) ) ;
993+ }
994+ if let Some ( offset) = self . offset {
995+ let offset_value = i64:: try_from ( offset)
996+ . map_err ( |_| Error :: query ( "Full-text search offset exceeds i64 range" ) ) ?;
997+ let placeholder = self . push_param ( db_type, params, Value :: BigInt ( Some ( offset_value) ) ) ;
998+ sql. push_str ( & format ! ( " OFFSET {}" , placeholder) ) ;
999+ }
1000+ Ok ( ( ) )
1001+ }
9581002}
9591003
9601004// =============================================================================
0 commit comments