final protected function build_query(){
        $select = empty($this->_select) ? $this->default_select() : implode(', ', $this->_select);
        $calc = $this->_calc_rows ? ' SQL_CALC_FOUND_ROWS' : '';
        $distinct = $this->_distinct ? sprintf('DISTINCT(%s)', $this->escape_column_name($this->_distinct)) : '';
        $from = empty($this->_from) ? $this->table : $this->_from;
        // Build query
        $sql = <<<SQL
          SELECT{$calc} {$distinct} {$select} FROM {$from}
SQL;
        // Add join
        $join = [];
        if( !empty($this->_join) ){
            $join = $this->_join;
        }else{
            $default_join = $this->default_join();
            if( !empty($default_join) ){
                $join = $default_join;
            }
        }
        foreach($join as list($table, $on, $method)){
            $method = 'INNER' === strtoupper($method) ? 'INNER' : 'LEFT';
            $sql .= <<<SQL
            {$method} JOIN {$table}
            ON {$on}
SQL;
        }
        // Add where
        if( !empty($this->_wheres) ){
            $where_clause = $this->build_where($this->_wheres);
            $sql .= <<<SQL
            WHERE {$where_clause}
SQL;
        }
        // Add group by
        if( !empty($this->_group_by) ){
            $group_by = implode(', ', $this->_group_by);
            $sql .= <<<SQL
            GROUP BY {$group_by}
SQL;
        }
        // Add order by
        if( !empty($this->_order_by) ){
            $order_by = implode(', ', $this->_order_by);
            $sql .= <<<SQL
            ORDER BY {$order_by}
SQL;
        }
        // Add limit
        if( !empty($this->_limit) ){
            list($per_page, $offset) = $this->_limit;
            $limit = <<<SQL
            LIMIT %d, %d
SQL;
            $sql .= sprintf($limit, $offset, $per_page);
        }
        return $sql;
    }