system/Database/BaseBuilder.php
protected function compileWhereHaving(string $qb_key): string
	{
		if (! empty($this->$qb_key))
		{
			for ($i = 0, $c = count($this->$qb_key); $i < $c; $i ++)
			{
				// Is this condition already compiled?
				if (is_string($this->{$qb_key}[$i]))
				{
					continue;
				}
				elseif ($this->{$qb_key}[$i]['escape'] === false)
				{
					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
					continue;
				}

				// Split multiple conditions
				$conditions = preg_split(
						'/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', $this->{$qb_key}[$i]['condition'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
				);

				for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci ++)
				{
					if (($op = $this->getOperator($conditions[$ci])) === false
							|| ! preg_match('/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(?<!\)))?(\)?)$/i', $conditions[$ci], $matches)
					)
					{
						continue;
					}

					// $matches = array(
					//	0 => '(test <= foo)',	/* the whole thing */
					//	1 => '(',		/* optional */
					//	2 => 'test',		/* the field name */
					//	3 => ' <= ',		/* $op */
					//	4 => 'foo',		/* optional, if $op is e.g. 'IS NULL' */
					//	5 => ')'		/* optional */
					// );

					if (! empty($matches[4]))
					{
						$matches[4] = ' ' . $matches[4];
					}

					$conditions[$ci] = $matches[1] . $this->db->protectIdentifiers(trim($matches[2]))
							. ' ' . trim($matches[3]) . $matches[4] . $matches[5];
				}

				$this->{$qb_key}[$i] = implode('', $conditions);
			}

			return ($qb_key === 'QBHaving' ? "\nHAVING " : "\nWHERE ")
					. implode("\n", $this->$qb_key);
		}

		return '';
	}
protected function compileWhereHaving(string $qb_key): string
	{
		if (! empty($this->$qb_key))
		{
			for ($i = 0, $c = count($this->$qb_key); $i < $c; $i ++)
			{
				// Is this condition already compiled?
				if (is_string($this->{$qb_key}[$i]))
				{
					continue;
				}
				elseif ($this->{$qb_key}[$i]['escape'] === false)
				{
					$this->{$qb_key}[$i] = $this->{$qb_key}[$i]['condition'];
					continue;
				}

				// Split multiple conditions
				$conditions = preg_split(
						'/((?:^|\s+)AND\s+|(?:^|\s+)OR\s+)/i', $this->{$qb_key}[$i]['condition'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
				);

				for ($ci = 0, $cc = count($conditions); $ci < $cc; $ci ++)
				{
					if (($op = $this->getOperator($conditions[$ci])) === false
							|| ! preg_match('/^(\(?)(.*)(' . preg_quote($op, '/') . ')\s*(.*(?<!\)))?(\)?)$/i', $conditions[$ci], $matches)
					)
					{
						continue;
					}

					// $matches = array(
					//	0 => '(test <= foo)',	/* the whole thing */
					//	1 => '(',		/* optional */
					//	2 => 'test',		/* the field name */
					//	3 => ' <= ',		/* $op */
					//	4 => 'foo',		/* optional, if $op is e.g. 'IS NULL' */
					//	5 => ')'		/* optional */
					// );

					if (! empty($matches[4]))
					{
						$matches[4] = ' ' . $matches[4];
					}

					$conditions[$ci] = $matches[1] . $this->db->protectIdentifiers(trim($matches[2]))
							. ' ' . trim($matches[3]) . $matches[4] . $matches[5];
				}

				$this->{$qb_key}[$i] = implode('', $conditions);
			}

			return ($qb_key === 'QBHaving' ? "\nHAVING " : "\nWHERE ")
					. implode("\n", $this->$qb_key);
		}

		return '';
	}
Compile WHERE, HAVING statements

Escapes identifiers in WHERE and HAVING statements at execution time.

Required so that aliases are tracked properly, regardless of whether
where(), orWhere(), having(), orHaving are called prior to from(),
join() and prefixTable is added only if needed.

@param string $qb_key 'QBWhere' or 'QBHaving'

@return string    SQL statement