. * * 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 Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero 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 string $expression; public function __construct(string $expression) { if ($expression === '') { throw new RuntimeException("Expression can't be empty."); } if (str_ends_with($expression, ':')) { 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 . ':'; } public function getRawValue(): mixed { return null; } /** * Get a string expression. */ 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 $value A scalar or NULL. */ public static function value(string|float|int|bool|null $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); } /** * Create an alias reference expression. * * @param string $expression Examples: `someAlias`, `subQueryAlias.someAlias`. * @since 8.1.0 */ public static function alias(string $expression): self { if ($expression === '') { throw new RuntimeException("Empty alias."); } if (!preg_match('/^[a-zA-Z\d.]+$/', $expression)) { throw new RuntimeException("Bad alias expression. Must be of letters, digits. Can have a dot."); } if (str_contains($expression, '.')) { [$left, $right] = explode('.', $expression, 2); return self::create($left . '.#' . $right); } 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 A condition. * @param Expression|string|int|float|bool|null $then Then. * @param Expression|string|int|float|bool|null $else Else. */ public static function if( Expression $condition, Expression|string|int|float|bool|null $then, Expression|string|int|float|bool|null $else ): self { return self::composeFunction('IF', $condition, $then, $else); } /** * 'CASE' expression. Even arguments define 'WHEN' conditions, following odd arguments * define 'THEN' values. The last unmatched argument defines the 'ELSE' value. * * @param Expression|scalar|null ...$arguments Arguments. */ public static function switch(Expression|string|int|float|bool|null ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('SWITCH', ...$arguments); } /** * 'CASE' expression that maps keys to values. The first argument is the value to map. * Odd arguments define keys, the following even arguments define mapped values. * The last unmatched argument defines the 'ELSE' value. * * @param Expression|scalar|null ...$arguments Arguments. */ public static function map(Expression|string|int|float|bool|null ...$arguments): self { if (count($arguments) < 3) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('MAP', ...$arguments); } /** * 'IFNULL' function. If the first argument is not NULL, returns it, * otherwise returns the second argument. * * @param Expression $value A value. * @param Expression|string|int|float|bool $fallbackValue A fallback value. */ public static function ifNull(Expression $value, Expression|string|int|float|bool $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 $argument1 * @param Expression|string|int|float|bool $argument2 */ public static function nullIf( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $argument2 ): self { return self::composeFunction('NULLIF', $argument1, $argument2); } /** * 'LIKE' operator. * * Example: `like(Expression:column('test'), 'test%'`. * * @param Expression $subject A subject. * @param Expression|string $pattern A pattern. */ public static function like(Expression $subject, Expression|string $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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( Expression|string|int|float|bool $argument1, Expression|string|int|float|bool $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[] $values */ public static function in(Expression $expression, array $values): self { return self::composeFunction('IN', $expression, ...$values); } /** * 'NOT IN' operator. Check whether a value is not within a set of values. * * @param Expression $expression * @param Expression[]|string[]|int[]|float[]|bool[] $values */ public static function notIn(Expression $expression, array $values): self { return self::composeFunction('NOT_IN', $expression, ...$values); } /** * 'COALESCE' function. Returns the first non-NULL value in the list. */ public static function coalesce(Expression ...$expressions): self { return self::composeFunction('COALESCE', ...$expressions); } /** * '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 $fiscalYearStart A month number of a fiscal year start. 1..12. */ public static function yearFiscal(Expression $date, int $fiscalYearStart = 1): self { if ($fiscalYearStart < 1 || $fiscalYearStart > 12) { throw new RuntimeException("Bad fiscal year start."); } return self::composeFunction('YEAR_' . strval($fiscalYearStart), $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); } /** * 'UNIX_TIMESTAMP' function. Seconds. * * @param Expression $dateTime * @since 9.0.0 */ public static function unixTimestamp(Expression $dateTime): self { return self::composeFunction('UNIX_TIMESTAMP', $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 ...$strings Strings. */ public static function concat(Expression|string ...$strings): self { return self::composeFunction('CONCAT', ...$strings); } /** * '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, Expression|string $needle, Expression|string $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 ...$arguments */ public static function add(Expression|int|float ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('ADD', ...$arguments); } /** * 'SUB' function. Subtraction. * * @param Expression|int|float ...$arguments */ public static function subtract(Expression|int|float ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('SUB', ...$arguments); } /** * 'MUL' function. Multiplication. * * @param Expression|int|float ...$arguments */ public static function multiply(Expression|int|float ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('MUL', ...$arguments); } /** * 'DIV' function. Division. * * @param Expression|int|float ...$arguments */ public static function divide(Expression|int|float ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('DIV', ...$arguments); } /** * 'MOD' function. Returns a remainder of a number divided by another number. * * @param Expression|int|float ...$arguments */ public static function modulo(Expression|int|float ...$arguments): self { if (count($arguments) < 2) { throw new RuntimeException("Too few arguments."); } return self::composeFunction('MOD', ...$arguments); } /** * '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); } /** * 'GREATEST' function. A max value from a list of expressions. */ public static function greatest(Expression ...$arguments): self { return self::composeFunction('GREATEST', ...$arguments); } /** * 'LEAST' function. A min value from a list of expressions. */ public static function least(Expression ...$arguments): self { return self::composeFunction('LEAST', ...$arguments); } /** * 'ANY_VALUE' function. * * @since 9.1.6 */ public function anyValue(Expression $expression): self { return self::composeFunction('ANY_VALUE', $expression); } /** * 'AND' operator. Returns TRUE if all arguments are TRUE. */ public static function and(Expression ...$arguments): self { return self::composeFunction('AND', ...$arguments); } /** * 'OR' operator. Returns TRUE if at least one argument is TRUE. */ public static function or(Expression ...$arguments): self { return self::composeFunction('OR', ...$arguments); } /** * 'NOT' operator. Negates an expression. */ public static function not(Expression $argument): self { return self::composeFunction('NOT', $argument); } /** * 'ROW' constructor. */ public static function row(Expression ...$arguments): self { return self::composeFunction('ROW', ...$arguments); } private static function composeFunction( string $function, Expression|bool|int|float|string|null ...$arguments ): self { return Util::composeFunction($function, ...$arguments); } private static function stringifyArgument(Expression|bool|int|float|string|null $argument): string { return Util::stringifyArgument($argument); } }