@@ -317,8 +317,10 @@ public function generateRankingQuery(string $innerQuery, bool $withRollup = fals
317317 $ additionalColumnsExpressions = $ this ->generateAdditionalColumnsExpressions ();
318318
319319 if (Schema::getInstance ()->supportsWindowFunctions ()) {
320- $ counterExpressions = $ this ->generateWindowCounterExpressions ($ innerQuery , $ withRollup );
321- $ counterRollupExpressions = $ this ->generateWindowCounterRollupExpressions ($ innerQuery , $ withRollup );
320+ $ windowOrderBy = $ this ->generateWindowOrderByExpression ($ innerQuery , $ withRollup );
321+
322+ $ counterExpressions = $ this ->generateWindowCounterExpressions ($ windowOrderBy , $ withRollup );
323+ $ counterRollupExpressions = $ this ->generateWindowCounterRollupExpressions ($ withRollup , $ windowOrderBy );
322324 $ groupByExpression = $ this ->generateWindowGroupByExpression ($ limit , $ withRollup );
323325 } else {
324326 $ counterExpressions = $ this ->generateVariableCounterExpressions ($ limit , $ withRollup );
@@ -552,18 +554,14 @@ private function generateVariableGroupByExpression(bool $withRollup): string
552554 *
553555 * @return array{counter: string, init: string}
554556 */
555- private function generateWindowCounterExpressions (string $ innerQuery , bool $ withRollup ): array
557+ private function generateWindowCounterExpressions (string $ windowOrderBy , bool $ withRollup ): array
556558 {
557559 $ partitionBy = '' ;
558560
559561 if ($ this ->partitionColumn !== false ) {
560562 $ partitionBy = "PARTITION BY ` {$ this ->partitionColumn }` " ;
561563 }
562564
563- $ orderByColumns = DbHelper::extractOrderByFromQuery ($ innerQuery , true )
564- ?? DbHelper::extractGroupByFromQuery ($ innerQuery , true )
565- ?? $ this ->generateLabelColumnsString ();
566-
567565 $ excludeWhens = [];
568566 $ orderByWhens = [];
569567
@@ -580,15 +578,15 @@ private function generateWindowCounterExpressions(string $innerQuery, bool $with
580578 }
581579
582580 if ([] === $ orderByWhens ) {
583- $ orderBy = $ orderByColumns ;
581+ $ orderBy = $ windowOrderBy ;
584582 } else {
585583 $ orderBy = "
586584 CASE
587585 " . implode ("
588586 " , $ orderByWhens ) . "
589587 ELSE 0
590588 END,
591- $ orderByColumns
589+ $ windowOrderBy
592590 " ;
593591 }
594592
@@ -619,24 +617,20 @@ private function generateWindowCounterExpressions(string $innerQuery, bool $with
619617 *
620618 * @return array{counter: string, init: string}
621619 */
622- private function generateWindowCounterRollupExpressions (string $ innerQuery , bool $ withRollup ): array
620+ private function generateWindowCounterRollupExpressions (bool $ withRollup , string $ withRollupOrderBy ): array
623621 {
624622 $ counter = '' ;
625623
626624 if ($ withRollup ) {
627625 $ rollupColumns = array_keys ($ this ->labelColumns );
628626
629- $ orderByColumns = DbHelper::extractOrderByFromQuery ($ innerQuery , true )
630- ?? DbHelper::extractGroupByFromQuery ($ innerQuery , true )
631- ?? $ this ->generateLabelColumnsString ();
632-
633627 $ orderBy = "
634628 CASE
635629 WHEN ` " . implode ('` IS NULL AND ` ' , $ rollupColumns ) . "` IS NULL THEN 1
636630 WHEN ` " . implode ('` IS NULL OR ` ' , $ rollupColumns ) . "` IS NULL THEN 0
637631 ELSE 1
638632 END,
639- $ orderByColumns
633+ $ withRollupOrderBy
640634 " ;
641635
642636 $ counter = "
@@ -679,6 +673,92 @@ private function generateWindowGroupByExpression(int $limit, bool $withRollup):
679673 return $ groupBy ;
680674 }
681675
676+ private function generateWindowOrderByExpression (string $ innerQuery , bool $ withRollup ): string
677+ {
678+ $ selectColumns = DbHelper::extractSelectFromQuery ($ innerQuery );
679+
680+ if ($ withRollup && '* ' === $ selectColumns ) {
681+ // special case for wrapped ROLLUP query
682+ // we find the real columns one level deeper
683+ $ outerSelectPos = stripos ($ innerQuery , 'SELECT ' );
684+ $ innerSelectPos = stripos ($ innerQuery , 'SELECT ' , $ outerSelectPos + strlen ('SELECT ' ));
685+ $ realInnerQuery = substr ($ innerQuery , $ innerSelectPos );
686+ $ selectColumns = DbHelper::extractSelectFromQuery ($ realInnerQuery );
687+ }
688+
689+ if (null === $ selectColumns || '* ' === $ selectColumns ) {
690+ // order by label columns if we can not find
691+ // the named SELECT columns to order by
692+ return $ this ->generateLabelColumnsString ();
693+ }
694+
695+ $ columns = DbHelper::extractOrderByFromQuery ($ innerQuery , true );
696+ $ columns = $ this ->prepareColumnsForWindowOrderBy ($ columns , $ selectColumns );
697+
698+ if (null === $ columns ) {
699+ $ columns = DbHelper::extractGroupByFromQuery ($ innerQuery , true );
700+
701+ if (null === $ columns && $ withRollup ) {
702+ // a rollup has the GROUP BY inside the wrapper
703+ $ outerSelectPos = stripos ($ innerQuery , 'SELECT ' );
704+ $ innerSelectPos = stripos ($ innerQuery , 'SELECT ' , $ outerSelectPos + strlen ('SELECT ' ));
705+ $ lastClosingParenthesis = strrpos ($ innerQuery , ') ' );
706+
707+ $ realInnerQuery = substr ($ innerQuery , $ innerSelectPos , $ lastClosingParenthesis - $ innerSelectPos );
708+ $ columns = DbHelper::extractGroupByFromQuery ($ realInnerQuery , true );
709+ }
710+
711+ $ columns = $ this ->prepareColumnsForWindowOrderBy ($ columns , $ selectColumns );
712+ }
713+
714+ if (null === $ columns ) {
715+ $ columns = $ this ->generateLabelColumnsString ();
716+ }
717+
718+ return $ columns ;
719+ }
720+
721+ private function prepareColumnsForWindowOrderBy (?string $ expr , string $ selectColumns ): ?string
722+ {
723+ if (null === $ expr ) {
724+ return null ;
725+ }
726+
727+ $ exprColumns = explode (', ' , $ expr );
728+
729+ foreach ($ exprColumns as $ i => &$ exprColumn ) {
730+ $ columnParts = explode (' ' , trim ($ exprColumn ));
731+
732+ if (count ($ columnParts ) > 2 ) {
733+ // the column contains more than just "column ASC"
734+ // remove the column for safety, we don't know what we are really dealing with
735+ unset($ exprColumns [$ i ]);
736+ continue ;
737+ }
738+
739+ $ column = str_replace ('` ' , '' , trim ($ columnParts [0 ]));
740+
741+ if (preg_match ('/`? ' . $ column . '`? AS `?(\w+)`?(?:,|$)/is ' , $ selectColumns , $ matches )) {
742+ // unalias the column to allow usage in window
743+ $ column = trim ($ matches [1 ]);
744+ $ columnParts [0 ] = '` ' . $ column . '` ' ;
745+ $ exprColumn = implode (' ' , $ columnParts );
746+ }
747+
748+ if (!preg_match ('/`? ' . $ column . '`?(?:,|$)/is ' , $ selectColumns )) {
749+ // the column was not found as "column," or "column<end of select>" in the SELECT part
750+ // we remove it from the window because it otherwise break the query
751+ unset($ exprColumns [$ i ]);
752+ }
753+ }
754+
755+ if ([] === $ exprColumns ) {
756+ return null ;
757+ }
758+
759+ return implode (', ' , $ exprColumns );
760+ }
761+
682762 /**
683763 * Prepare the inner query for usage in the ranking query.
684764 */
0 commit comments