@@ -642,15 +642,7 @@ public function fromSubquery(BaseBuilder $from, string $alias): self
642642 */
643643 public function join (string $ table , $ cond , string $ type = '' , ?bool $ escape = null )
644644 {
645- if ($ type !== '' ) {
646- $ type = strtoupper (trim ($ type ));
647-
648- if (! in_array ($ type , $ this ->joinTypes , true )) {
649- $ type = '' ;
650- } else {
651- $ type .= ' ' ;
652- }
653- }
645+ $ type = $ this ->compileJoinType ($ type );
654646
655647 // Extract any aliases that might exist. We use this information
656648 // in the protectIdentifiers to know whether to add a table prefix
@@ -660,62 +652,96 @@ public function join(string $table, $cond, string $type = '', ?bool $escape = nu
660652 $ escape = $ this ->db ->protectIdentifiers ;
661653 }
662654
663- // Do we want to escape the table name?
664- if ($ escape === true ) {
665- $ table = $ this ->db ->protectIdentifiers ($ table , true , null , false );
655+ $ table = $ this ->compileJoinTable ($ table , $ escape );
656+ $ cond = $ this ->compileJoinCondition ($ cond , $ escape );
657+
658+ // Assemble the JOIN statement
659+ $ this ->QBJoin [] = $ type . 'JOIN ' . $ table . $ cond ;
660+
661+ return $ this ;
662+ }
663+
664+ /**
665+ * Compiles the JOIN table name.
666+ */
667+ protected function compileJoinTable (string $ table , bool $ escape ): string
668+ {
669+ if ($ escape ) {
670+ return $ this ->db ->protectIdentifiers ($ table , true , null , false );
666671 }
667672
668- if ( $ cond instanceof RawSql) {
669- $ this -> QBJoin [] = $ type . ' JOIN ' . $ table . ' ON ' . $ cond ;
673+ return $ table ;
674+ }
670675
671- return $ this ;
676+ private function compileJoinType (string $ type ): string
677+ {
678+ if ($ type === '' ) {
679+ return '' ;
672680 }
673681
674- if (! $ this ->hasOperator ($ cond )) {
675- $ cond = ' USING ( ' . ($ escape ? $ this ->db ->escapeIdentifiers ($ cond ) : $ cond ) . ') ' ;
676- } elseif ($ escape === false ) {
677- $ cond = ' ON ' . $ cond ;
678- } else {
679- // Split multiple conditions
680- // @TODO This does not parse `BETWEEN a AND b` correctly.
681- if (preg_match_all ('/\sAND\s|\sOR\s/i ' , $ cond , $ joints , PREG_OFFSET_CAPTURE ) >= 1 ) {
682- $ conditions = [];
683- $ joints = $ joints [0 ];
684- array_unshift ($ joints , ['' , 0 ]);
685-
686- for ($ i = count ($ joints ) - 1 , $ pos = strlen ($ cond ); $ i >= 0 ; $ i --) {
687- $ joints [$ i ][1 ] += strlen ($ joints [$ i ][0 ]); // offset
688- $ conditions [$ i ] = substr ($ cond , $ joints [$ i ][1 ], $ pos - $ joints [$ i ][1 ]);
689- $ pos = $ joints [$ i ][1 ] - strlen ($ joints [$ i ][0 ]);
690- $ joints [$ i ] = $ joints [$ i ][0 ];
691- }
692- ksort ($ conditions );
693- } else {
694- $ conditions = [$ cond ];
695- $ joints = ['' ];
696- }
682+ $ type = strtoupper (trim ($ type ));
697683
698- $ cond = ' ON ' ;
684+ return in_array ($ type , $ this ->joinTypes , true ) ? $ type . ' ' : '' ;
685+ }
699686
700- foreach ($ conditions as $ i => $ condition ) {
701- $ operator = $ this ->getOperator ($ condition );
687+ /**
688+ * Compiles the JOIN ON or USING condition.
689+ */
690+ private function compileJoinCondition (RawSql |string $ condition , bool $ escape ): string
691+ {
692+ if ($ condition instanceof RawSql) {
693+ return ' ON ' . $ condition ;
694+ }
702695
703- // Workaround for BETWEEN
704- if ($ operator === false ) {
705- $ cond .= $ joints [ $ i ] . $ condition ;
696+ if (! $ this -> hasOperator ( $ condition )) {
697+ return ' USING ( ' . ($ escape ? $ this -> db -> escapeIdentifiers ( $ condition ) : $ condition ) . ' ) ' ;
698+ }
706699
707- continue ;
708- }
700+ if ($ escape === false ) {
701+ return ' ON ' . $ condition ;
702+ }
703+
704+ return $ this ->compileProtectedJoinCondition ($ condition );
705+ }
709706
710- $ cond .= $ joints [$ i ];
711- $ cond .= preg_match ('/(\(*)?([\[\]\w\. \'-]+) ' . preg_quote ($ operator , '/ ' ) . '(.*)/i ' , $ condition , $ match ) ? $ match [1 ] . $ this ->db ->protectIdentifiers ($ match [2 ]) . $ operator . $ this ->db ->protectIdentifiers ($ match [3 ]) : $ condition ;
707+ private function compileProtectedJoinCondition (string $ condition ): string
708+ {
709+ // Split multiple conditions
710+ // @TODO This does not parse `BETWEEN a AND b` correctly.
711+ if (preg_match_all ('/\sAND\s|\sOR\s/i ' , $ condition , $ joints , PREG_OFFSET_CAPTURE ) >= 1 ) {
712+ $ conditions = [];
713+ $ joints = $ joints [0 ];
714+ array_unshift ($ joints , ['' , 0 ]);
715+
716+ for ($ i = count ($ joints ) - 1 , $ pos = strlen ($ condition ); $ i >= 0 ; $ i --) {
717+ $ joints [$ i ][1 ] += strlen ($ joints [$ i ][0 ]); // offset
718+ $ conditions [$ i ] = substr ($ condition , $ joints [$ i ][1 ], $ pos - $ joints [$ i ][1 ]);
719+ $ pos = $ joints [$ i ][1 ] - strlen ($ joints [$ i ][0 ]);
720+ $ joints [$ i ] = $ joints [$ i ][0 ];
712721 }
722+ ksort ($ conditions );
723+ } else {
724+ $ conditions = [$ condition ];
725+ $ joints = ['' ];
713726 }
714727
715- // Assemble the JOIN statement
716- $ this ->QBJoin [] = $ type . 'JOIN ' . $ table . $ cond ;
728+ $ condition = ' ON ' ;
717729
718- return $ this ;
730+ foreach ($ conditions as $ i => $ conditionPart ) {
731+ $ operator = $ this ->getOperator ($ conditionPart );
732+
733+ // Workaround for BETWEEN
734+ if ($ operator === false ) {
735+ $ condition .= $ joints [$ i ] . $ conditionPart ;
736+
737+ continue ;
738+ }
739+
740+ $ condition .= $ joints [$ i ];
741+ $ condition .= preg_match ('/(\(*)?([\[\]\w\. \'-]+) ' . preg_quote ($ operator , '/ ' ) . '(.*)/i ' , $ conditionPart , $ match ) ? $ match [1 ] . $ this ->db ->protectIdentifiers ($ match [2 ]) . $ operator . $ this ->db ->protectIdentifiers ($ match [3 ]) : $ conditionPart ;
742+ }
743+
744+ return $ condition ;
719745 }
720746
721747 /**
0 commit comments