Files
espocrm/application/Espo/ORM/Query/Part/Expression.php
2021-07-27 11:26:18 +03:00

688 lines
19 KiB
PHP

<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\ORM\Query\Part;
use Espo\ORM\Query\Part\Expression\Util;
use RuntimeException;
/**
* A complex expression. Can be a function or a simple column reference. Immutable.
*/
class Expression implements WhereItem
{
private $expression;
public function __construct(string $expression)
{
if ($expression === '') {
throw new RuntimeException("Expression can't be empty.");
}
if (substr($expression, -1) === ':') {
throw new RuntimeException("Expression should not end with `:`.");
}
$this->expression = $expression;
}
public function getRaw(): array
{
return [$this->getRawKey() => null];
}
public function getRawKey(): string
{
return $this->expression . ':';
}
/**
* @return mixed
*/
public function getRawValue()
{
return null;
}
public function getValue(): string
{
return $this->expression;
}
/**
* Create an expression from a string.
*/
public static function create(string $expression): self
{
return new self($expression);
}
/**
* Create an expression from a scalar value or NULL.
*
* @param string|float|int|bool|null A scalar or NULL.
*/
public static function value($value): self
{
return self::create(self::stringifyArgument($value));
}
/**
* Create a column reference expression.
*
* @param string $expression Examples: `columnName`, `alias.columnName`.
*/
public static function column(string $expression): self
{
$string = $expression;
if (strlen($string) && $string[0] === '@') {
$string = substr($string, 1);
}
if ($string === '') {
throw new RuntimeException("Empty column.");
}
if (!preg_match('/^[a-zA-Z\d\.]+$/', $string)) {
throw new RuntimeException("Bad column. Must be of letters, digits. Can have a dot.");
}
return self::create($expression);
}
/**
* 'COUNT' function.
*
* @param Expression $expression
*/
public static function count(Expression $expression): self
{
return self::composeFunction('COUNT', $expression);
}
/**
* 'MIN' function.
*
* @param Expression $expression
*/
public static function min(Expression $expression): self
{
return self::composeFunction('MIN', $expression);
}
/**
* 'MAX' function.
*
* @param Expression $expression
*/
public static function max(Expression $expression): self
{
return self::composeFunction('MAX', $expression);
}
/**
* 'SUM' function.
*
* @param Expression $expression
*/
public static function sum(Expression $expression): self
{
return self::composeFunction('SUM', $expression);
}
/**
* 'AVG' function.
*
* @param Expression $expression
*/
public static function average(Expression $expression): self
{
return self::composeFunction('AVG', $expression);
}
/**
* 'IF' function. Return $then if a condition is true, $else otherwise.
*
* @param Expression $condition
* @param Expression|string|int|float|bool|null $then
* @param Expression|string|int|float|bool|null $else
*/
public static function if(Expression $condition, $then, $else): self
{
return self::composeFunction('IF', $condition, $then, $else);
}
/**
* 'IFNULL' function. If the first argument is not NULL, returns it,
* otherwise returns the second argument.
*
* @param Expression $value
* @param Expression|string|int|float|bool $fallbackValue
*/
public static function ifNull(Expression $value, $fallbackValue): self
{
return self::composeFunction('IFNULL', $value, $fallbackValue);
}
/**
* 'NULLIF' function. If $arg1 = $arg2, returns NULL,
* otherwise returns the first argument.
*
* @param Expression|string|int|float|bool $arg1
* @param Expression|string|int|float|bool $arg2
*/
public static function nullIf($arg1, $arg2): self
{
return self::composeFunction('NULLIF', $arg1, $arg2);
}
/**
* 'LIKE' operator.
*
* Example: `like(Expression:column('test'), 'test%'`.
*
* @param Expression $subject
* @param Expression|string $pattern
*/
public static function like(Expression $subject, $pattern): self
{
return self::composeFunction('LIKE', $subject, $pattern);
}
/**
* '=' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function equal($argument1, $argument2): self
{
return self::composeFunction('EQUAL', $argument1, $argument2);
}
/**
* '<>' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function notEqual($argument1, $argument2): self
{
return self::composeFunction('NOT_EQUAL', $argument1, $argument2);
}
/**
* '>' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function greater($argument1, $argument2): self
{
return self::composeFunction('GREATER_THAN', $argument1, $argument2);
}
/**
* '<' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function less($argument1, $argument2): self
{
return self::composeFunction('LESS_THAN', $argument1, $argument2);
}
/**
* '>=' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function greaterOrEqual($argument1, $argument2): self
{
return self::composeFunction('GREATER_THAN_OR_EQUAL', $argument1, $argument2);
}
/**
* '<=' operator.
*
* @param Expression|string|int|float|bool $argument1
* @param Expression|string|int|float|bool $argument2
*/
public static function lessOrEqual($argument1, $argument2): self
{
return self::composeFunction('LESS_THAN_OR_EQUAL', $argument1, $argument2);
}
/**
* 'IS NULL' operator.
*
* @param Expression $expression
*/
public static function isNull(Expression $expression): self
{
return self::composeFunction('IS_NULL', $expression);
}
/**
* 'IS NOT NULL' operator.
*
* @param Expression $expression
*/
public static function isNotNull(Expression $expression): self
{
return self::composeFunction('IS_NOT_NULL', $expression);
}
/**
* 'IN' operator. Check whether a value is within a set of values.
*
* @param Expression $expression
* @param Expression[]|string[]|int[]|float[]|bool[] $valueList
*/
public static function in(Expression $expression, array $valueList): self
{
return self::composeFunction('IN', $expression, ...$valueList);
}
/**
* 'NOT IN' operator. Check whether a value is not within a set of values.
*
* @param Expression $expression
* @param Expression[]|string[]|int[]|float[]|bool[] $valueList
*/
public static function notIn(Expression $expression, array $valueList): self
{
return self::composeFunction('NOT_IN', $expression, ...$valueList);
}
/**
* 'COALESCE' function. Returns the first non-NULL value in the list.
*/
public static function coalesce(Expression ...$expressionList): self
{
return self::composeFunction('COALESCE', ...$expressionList);
}
/**
* 'MONTH' function. Returns a month number of a passed date or date-time.
*
* @param Expression $date
*/
public static function month(Expression $date): self
{
return self::composeFunction('MONTH_NUMBER', $date);
}
/**
* 'WEEK' function. Returns a week number of a passed date or date-time.
*
* @param Expression $date
* @param int $weekStart A week start. `0` for Sunday, `1` for Monday.
*/
public static function week(Expression $date, int $weekStart = 0): self
{
if ($weekStart !== 0 && $weekStart !== 1) {
throw new RuntimeException("Week start can be only 0 or 1.");
}
if ($weekStart === 1) {
return self::composeFunction('WEEK_NUMBER_1', $date);
}
return self::composeFunction('WEEK_NUMBER', $date);
}
/**
* 'DAYOFWEEK' function. A day of week of a passed date or date-time. 1..7.
*
* @param Expression $date
*/
public static function dayOfWeek(Expression $date): self
{
return self::composeFunction('DAYOFWEEK', $date);
}
/**
* 'DAYOFMONTH' function. A day of month of a passed date or date-time. 1..31.
*
* @param Expression $date
*/
public static function dayOfMonth(Expression $date): self
{
return self::composeFunction('DAYOFMONTH', $date);
}
/**
* 'YEAR' function. A year number of a passed date or date-time.
*
* @param Expression $date
*/
public static function year(Expression $date): self
{
return self::composeFunction('YEAR', $date);
}
/**
* 'YEAR' function taking into account a fiscal year start.
*
* @param Expression $date
* @param int $firscalYearStart A month number of a fiscal year start. 1..12.
*/
public static function yearFiscal(Expression $date, int $firscalYearStart = 1): self
{
if ($firscalYearStart < 1 || $firscalYearStart > 12) {
throw new RuntimeException("Bad fiscal year start.");
}
return self::composeFunction('YEAR_' . strval($firscalYearStart), $date);
}
/**
* 'QUARTER' function. A quarter number of a passed date or date-time. 1..4.
*
* @param Expression $date
*/
public static function quarter(Expression $date): self
{
return self::composeFunction('QUARTER_NUMBER', $date);
}
/**
* 'HOUR' function. A hour number of a passed date-time. 0..23.
*
* @param Expression $dateTime
*/
public static function hour(Expression $dateTime): self
{
return self::composeFunction('HOUR', $dateTime);
}
/**
* 'MINUTE' function. A minute number of a passed date-time. 0..59.
*
* @param Expression $dateTime
*/
public static function minute(Expression $dateTime): self
{
return self::composeFunction('MINUTE', $dateTime);
}
/**
* 'SECOND' function. A second number of a passed date-time. 0..59.
*
* @param Expression $dateTime
*/
public static function second(Expression $dateTime): self
{
return self::composeFunction('SECOND', $dateTime);
}
/**
* 'NOW' function. A current date and time.
*/
public static function now(): self
{
return self::composeFunction('NOW');
}
/**
* 'DATE' function. Returns a date part of a date-time.
*
* @param Expression $dateTime
*/
public static function date(Expression $dateTime): self
{
return self::composeFunction('DATE', $dateTime);
}
/**
* Time zone conversion function. Converts a passed data-time applying a hour offset.
*
* @param Expression $date
*/
public static function convertTimezone(Expression $date, float $offset): self
{
return self::composeFunction('TZ', $date, $offset);
}
/**
* 'CONCAT' function. Concatenates multiple strings.
*
* @param Expression|string ...$stringList
*/
public static function concat(...$stringList): self
{
return self::composeFunction('CONCAT', ...$stringList);
}
/**
* 'LEFT' function. Returns a specified number of characters from the left of a string.
*/
public static function left(Expression $string, int $offset): self
{
return self::composeFunction('LEFT', $string, $offset);
}
/**
* 'LOWER' function. Converts a string to a lower case.
*/
public static function lowerCase(Expression $string): self
{
return self::composeFunction('LOWER', $string);
}
/**
* 'UPPER' function. Converts a string to an upper case.
*/
public static function upperCase(Expression $string): self
{
return self::composeFunction('UPPER', $string);
}
/**
* 'TRIM' function. Removes leading and trailing spaces.
*/
public static function trim(Expression $string): self
{
return self::composeFunction('TRIM', $string);
}
/**
* 'BINARY' function. Converts a string value to a binary string.
*/
public static function binary(Expression $string): self
{
return self::composeFunction('BINARY', $string);
}
/**
* 'CHAR_LENGTH' function. A number of characters in a string.
*/
public static function charLength(Expression $string): self
{
return self::composeFunction('CHAR_LENGTH', $string);
}
/**
* 'REPLACE' function. Replaces all the occurrences of a sub-string within a string.
*
* @param Expression $haystack A subject.
* @param Expression|string $needle A string to be replaced.
* @param Expression|string $replaceWith A string to replace with.
*/
public static function replace(Expression $haystack, $needle, $replaceWith): self
{
return self::composeFunction('REPLACE', $haystack, $needle, $replaceWith);
}
/**
* 'FIELD' operator (in MySQL). Returns an index (position) of an expression
* in a list. Returns `0` if not found. The first index is `1`.
*
* @param Expression $expression
* @param Expression[]|string[]|int[]|float[] $list
*/
public static function positionInList(Expression $expression, array $list): self
{
return self::composeFunction('POSITION_IN_LIST', $expression, ...$list);
}
/**
* 'ADD' function. Adds two or more numbers.
*
* @param Expression|int|float ...$argumentList
*/
public static function add(...$argumentList): self
{
if (count($argumentList) < 2) {
throw new RuntimeException("Too few arguments");
}
return self::composeFunction('ADD', ...$argumentList);
}
/**
* 'SUB' function. Subtraction.
*
* @param Expression|int|float ...$argumentList
*/
public static function subtract(...$argumentList): self
{
if (count($argumentList) < 2) {
throw new RuntimeException("Too few arguments");
}
return self::composeFunction('SUB', ...$argumentList);
}
/**
* 'MUL' function. Multiplication.
*
* @param Expression|int|float ...$argumentList
*/
public static function multiply(...$argumentList): self
{
if (count($argumentList) < 2) {
throw new RuntimeException("Too few arguments");
}
return self::composeFunction('MUL', ...$argumentList);
}
/**
* 'DIV' function. Division.
*
* @param Expression|int|float ...$argumentList
*/
public static function divide(...$argumentList): self
{
if (count($argumentList) < 2) {
throw new RuntimeException("Too few arguments");
}
return self::composeFunction('DIV', ...$argumentList);
}
/**
* 'MOD' function. Returns a remainder of a number divided by another number.
*
* @param Expression|int|float ...$argumentList
*/
public static function modulo(...$argumentList): self
{
if (count($argumentList) < 2) {
throw new RuntimeException("Too few arguments");
}
return self::composeFunction('MOD', ...$argumentList);
}
/**
* 'FLOOR' function. The largest integer value not greater than the argument.
*/
public static function floor(Expression $number): self
{
return self::composeFunction('FLOOR', $number);
}
/**
* 'CEIL' function. The largest integer value not greater than the argument.
*/
public static function ceil(Expression $number): self
{
return self::composeFunction('CEIL', $number);
}
/**
* 'ROUND' function. Rounds a number to a specified number of decimal places.
*/
public static function round(Expression $number, int $precision = 0): self
{
return self::composeFunction('ROUND', $number, $precision);
}
/**
* 'AND' operator. Returns TRUE if all arguments are TRUE.
*/
public static function and(Expression ...$argumentList): self
{
return self::composeFunction('AND', ...$argumentList);
}
/**
* 'OR' operator. Returns TRUE if at least one arguments is TRUE.
*/
public static function or(Expression ...$argumentList): self
{
return self::composeFunction('OR', ...$argumentList);
}
/**
* 'NOT' operator. Negates an expression.
*/
public static function not(Expression $argument): self
{
return self::composeFunction('NOT', $argument);
}
private static function composeFunction(string $function, ...$argumentList): self
{
return Util::composeFunction($function, ...$argumentList);
}
private static function stringifyArgument($arg): string
{
return Util::stringifyArgument($arg);
}
}