protected function compileWhere(array $tokens): string
    {
        if (empty($tokens)) {
            return '';
        }

        $statement = '';

        $activeGroup = true;
        foreach ($tokens as $condition) {
            //OR/AND keyword
            $boolean = $condition[0];

            //See AbstractWhere
            $context = $condition[1];

            //First condition in group/query, no any AND, OR required
            if ($activeGroup) {
                //Kill AND, OR and etc.
                $boolean = '';

                //Next conditions require AND or OR
                $activeGroup = false;
            }

            /*
             * When context is string it usually represent control keyword/syntax such as opening
             * or closing braces.
             */
            if (is_string($context)) {
                if ($context == '(') {
                    //New where group.
                    $activeGroup = true;
                }

                $statement .= ltrim("{$boolean} {$context} ");

                if ($context == ')') {
                    //We don't need trailing space
                    $statement = rtrim($statement);
                }

                continue;
            }

            if ($context instanceof FragmentInterface) {
                //Fragments has to be compiled separately
                $statement .= "{$boolean} {$this->prepareFragment($context)} ";
                continue;
            }

            //Now we are operating with "class" where function, where we need 3 variables where(id, =, 1)
            if (!is_array($context)) {
                throw new CompilerException('Invalid where token, context expected to be an array');
            }

            /*
             * This is "normal" where token which includes identifier, operator and value.
             */
            list($identifier, $operator, $value) = $context;

            //Identifier can be column name, expression or even query builder
            $identifier = $this->quote($identifier);

            //Value has to be prepared as well
            $placeholder = $this->prepareValue($value);

            if ($operator == 'BETWEEN' || $operator == 'NOT BETWEEN') {
                //Between statement has additional parameter
                $right = $this->prepareValue($context[3]);

                $statement .= "{$boolean} {$identifier} {$operator} {$placeholder} AND {$right} ";
                continue;
            }

            //Compiler can switch equal to IN if value points to array (do we need it?)
            $operator = $this->prepareOperator($value, $operator);

            $statement .= "{$boolean} {$identifier} {$operator} {$placeholder} ";
        }

        if ($activeGroup) {
            throw new CompilerException('Unable to build where statement, unclosed where group');
        }

        return trim($statement);
    }