protected function buildSelect($table, array $where, array $options = []) {
        $options += ['limit' => 0];

        $sql = '';

        // Build the select clause.
        if (!empty($options['columns'])) {
            $columns = array();
            foreach ($options['columns'] as $value) {
                $columns[] = $this->escape($value);
            }
            $sql .= 'select '.implode(', ', $columns);
        } else {
            $sql .= "select *";
        }

        // Build the from clause.
        if ($table instanceof Literal) {
            $table = $table->getValue($this);
        } else {
            $table = $this->prefixTable($table);
        }
        $sql .= "\nfrom $table";

        // Build the where clause.
        $whereString = $this->buildWhere($where, Db::OP_AND);
        if ($whereString) {
            $sql .= "\nwhere ".$whereString;
        }

        // Build the order.
        if (!empty($options['order'])) {
            $orders = [];
            foreach ($options['order'] as $column) {
                if ($column[0] === '-') {
                    $order = $this->escape(substr($column, 1)).' desc';
                } else {
                    $order = $this->escape($column);
                }
                $orders[] = $order;
            }
            $sql .= "\norder by ".implode(', ', $orders);
        }

        // Build the limit, offset.
        if (!empty($options['limit'])) {
            $limit = (int)$options['limit'];
            $sql .= "\nlimit $limit";
        }

        if (!empty($options['offset'])) {
            $sql .= ' offset '.((int)$options['offset']);
        } elseif (isset($options['page'])) {
            $offset = $options['limit'] * ($options['page'] - 1);
            $sql .= ' offset '.$offset;
        }

        return $sql;
    }