protected function build()
    {
        if (!$this->from) {
            throw new \RuntimeException("No tables provided for FROM clause");
        }

        $this->placeholders = [];
        $this->placeholderCounter = 1;

        $sql = ['SELECT'];

        if ($this->distinct) {
            $sql[] = 'DISTINCT';
        }

        $sql[] = implode(', ', $this->columns);

        $sql[] = 'FROM';
        $sql[] = implode(', ', $this->from);

        if ($this->joins) {
            foreach ($this->joins as $join) {
                $sql[] = $join[0];
                $sql[] = $join[1];
                $sql[] = 'ON';
                $sql[] = is_string($join[2])
                       ? $join[2]
                       : $this->buildConditions($join[2], false);
            }
        }

        if ($this->conditions) {
            $sql[] = 'WHERE';
            $sql[] = $this->buildConditions($this->conditions);
        }

        if ($this->groupBy) {
            $sql[] = 'GROUP BY';
            $sql[] = implode(', ', $this->groupBy);
        }

        if ($this->having) {
            $sql[] = 'HAVING';
            $sql[] = $this->buildConditions($this->having);
        }

        if ($this->orderBy) {
            $sql[] = 'ORDER BY';
            $sql[] = implode(', ', $this->orderBy);
        }

        if ($this->limit || $this->offset) {
            $sql[] = 'LIMIT';

            $parts = [];

            if ($this->offset) {
                if (!$this->limit) {
                    $this->limit = PHP_INT_MAX;
                }
                $parts[] = $this->addPlaceholder($this->offset);
            }

            if ($this->limit) {
                $parts[] = $this->addPlaceholder($this->limit);
            }

            $sql[] = implode(', ', $parts);
        }

        $this->sql = implode(' ', $sql);
        $this->changed = false;
    }