public function compileSelect(
        array $fromTables,
        $distinct,
        array $columns,
        array $joinTokens = [],
        array $whereTokens = [],
        array $havingTokens = [],
        array $grouping = [],
        array $ordering = [],
        int $limit = 0,
        int $offset = 0,
        array $unionTokens = []
    ): string {
        //This statement parts should be processed first to define set of table and column aliases
        $fromTables = $this->compileTables($fromTables);

        $joinsStatement = $this->optional(' ', $this->compileJoins($joinTokens), ' ');

        //Distinct flag (if any)
        $distinct = $this->optional(' ', $this->compileDistinct($distinct));

        //Columns are compiled after table names and joins to ensure aliases and prefixes
        $columns = $this->prepareColumns($columns);

        //A lot of constrain and other statements
        $whereStatement = $this->optional("\nWHERE", $this->compileWhere($whereTokens));
        $havingStatement = $this->optional("\nHAVING", $this->compileWhere($havingTokens));
        $groupingStatement = $this->optional("\nGROUP BY", $this->compileGrouping($grouping), ' ');

        //Union statement has new line at beginning of every union
        $unionsStatement = $this->optional("\n", $this->compileUnions($unionTokens));
        $orderingStatement = $this->optional("\nORDER BY", $this->compileOrdering($ordering));

        $limingStatement = $this->optional("\n", $this->compileLimit($limit, $offset));

        //Initial statement have predictable order
        $statement = "SELECT{$distinct}\n{$columns}\nFROM {$fromTables}";
        $statement .= "{$joinsStatement}{$whereStatement}{$groupingStatement}{$havingStatement}";
        $statement .= "{$unionsStatement}{$orderingStatement}{$limingStatement}";

        return rtrim($statement);
    }