<?php declare(strict_types=1); /** * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 3.0.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Database\Driver; use Cake\Database\Expression\IdentifierExpression; use Cake\Database\Expression\QueryExpression; use Cake\Database\Expression\TupleComparison; use Cake\Database\Query; use RuntimeException; /** * Provides a translator method for tuple comparisons * * @internal */ trait TupleComparisonTranslatorTrait { /** * Receives a TupleExpression and changes it so that it conforms to this * SQL dialect. * * It transforms expressions looking like '(a, b) IN ((c, d), (e, f))' into an * equivalent expression of the form '((a = c) AND (b = d)) OR ((a = e) AND (b = f))'. * * It can also transform transform expressions where the right hand side is a query * selecting the same amount of columns as the elements in the left hand side of * the expression: * * (a, b) IN (SELECT c, d FROM a_table) is transformed into * * 1 = (SELECT 1 FROM a_table WHERE (a = c) AND (b = d)) * * @param \Cake\Database\Expression\TupleComparison $expression The expression to transform * @param \Cake\Database\Query $query The query to update. * @return void */ protected function _transformTupleComparison(TupleComparison $expression, Query $query): void { $fields = $expression->getField(); if (!is_array($fields)) { return; } $operator = strtoupper($expression->getOperator()); if (!in_array($operator, ['IN', '='])) { throw new RuntimeException( sprintf( 'Tuple comparison transform only supports the `IN` and `=` operators, `%s` given.', $operator ) ); } $value = $expression->getValue(); $true = new QueryExpression('1'); if ($value instanceof Query) { $selected = array_values($value->clause('select')); foreach ($fields as $i => $field) { $value->andWhere([$field => new IdentifierExpression($selected[$i])]); } $value->select($true, true); $expression->setField($true); $expression->setOperator('='); return; } $type = $expression->getType(); if ($type) { /** @var array<string, string> $typeMap */ $typeMap = array_combine($fields, $type) ?: []; } else { $typeMap = []; } $surrogate = $query->getConnection() ->selectQuery($true); if (!is_array(current($value))) { $value = [$value]; } $conditions = ['OR' => []]; foreach ($value as $tuple) { $item = []; foreach (array_values($tuple) as $i => $value2) { $item[] = [$fields[$i] => $value2]; } $conditions['OR'][] = $item; } $surrogate->where($conditions, $typeMap); $expression->setField($true); $expression->setValue($surrogate); $expression->setOperator('='); } }