<?php

/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <http://www.doctrine-project.org>.
 */
namespace MailPoetVendor\Doctrine\ORM\Query\Exec;

if (!defined('ABSPATH')) exit;


use MailPoetVendor\Doctrine\DBAL\Connection;
use MailPoetVendor\Doctrine\DBAL\Types\Type;
use MailPoetVendor\Doctrine\ORM\Query\ParameterTypeInferer;
use MailPoetVendor\Doctrine\ORM\Query\AST;
/**
 * Executes the SQL statements for bulk DQL UPDATE statements on classes in
 * Class Table Inheritance (JOINED).
 *
 * @author Roman Borschel <roman@code-factory.org>
 * @since 2.0
 */
class MultiTableUpdateExecutor extends \MailPoetVendor\Doctrine\ORM\Query\Exec\AbstractSqlExecutor
{
    /**
     * @var string
     */
    private $_createTempTableSql;
    /**
     * @var string
     */
    private $_dropTempTableSql;
    /**
     * @var string
     */
    private $_insertSql;
    /**
     * @var array
     */
    private $_sqlParameters = array();
    /**
     * @var int
     */
    private $_numParametersInUpdateClause = 0;
    /**
     * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
     *
     * Internal note: Any SQL construction and preparation takes place in the constructor for
     *                best performance. With a query cache the executor will be cached.
     *
     * @param \MailPoetVendor\Doctrine\ORM\Query\AST\Node  $AST The root AST node of the DQL query.
     * @param \MailPoetVendor\Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST.
     */
    public function __construct(\MailPoetVendor\Doctrine\ORM\Query\AST\Node $AST, $sqlWalker)
    {
        $em = $sqlWalker->getEntityManager();
        $conn = $em->getConnection();
        $platform = $conn->getDatabasePlatform();
        $quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
        $updateClause = $AST->updateClause;
        $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
        $rootClass = $em->getClassMetadata($primaryClass->rootEntityName);
        $updateItems = $updateClause->updateItems;
        $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
        $idColumnNames = $rootClass->getIdentifierColumnNames();
        $idColumnList = \implode(', ', $idColumnNames);
        // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
        $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
        $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')' . ' SELECT t0.' . \implode(', t0.', $idColumnNames);
        $rangeDecl = new \MailPoetVendor\Doctrine\ORM\Query\AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
        $fromClause = new \MailPoetVendor\Doctrine\ORM\Query\AST\FromClause(array(new \MailPoetVendor\Doctrine\ORM\Query\AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
        $this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
        // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
        $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
        // 3. Create and store UPDATE statements
        $classNames = \array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses);
        $i = -1;
        foreach (\array_reverse($classNames) as $className) {
            $affected = \false;
            $class = $em->getClassMetadata($className);
            $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET ';
            foreach ($updateItems as $updateItem) {
                $field = $updateItem->pathExpression->field;
                if (isset($class->fieldMappings[$field]) && !isset($class->fieldMappings[$field]['inherited']) || isset($class->associationMappings[$field]) && !isset($class->associationMappings[$field]['inherited'])) {
                    $newValue = $updateItem->newValue;
                    if (!$affected) {
                        $affected = \true;
                        ++$i;
                    } else {
                        $updateSql .= ', ';
                    }
                    $updateSql .= $sqlWalker->walkUpdateItem($updateItem);
                    if ($newValue instanceof \MailPoetVendor\Doctrine\ORM\Query\AST\InputParameter) {
                        $this->_sqlParameters[$i][] = $newValue->name;
                        ++$this->_numParametersInUpdateClause;
                    }
                }
            }
            if ($affected) {
                $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
            }
        }
        // Append WHERE clause to insertSql, if there is one.
        if ($AST->whereClause) {
            $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
        }
        // 4. Store DDL for temporary identifier table.
        $columnDefinitions = array();
        foreach ($idColumnNames as $idColumnName) {
            $columnDefinitions[$idColumnName] = array('notnull' => \true, 'type' => \MailPoetVendor\Doctrine\DBAL\Types\Type::getType($rootClass->getTypeOfColumn($idColumnName)));
        }
        $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
        $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
    }
    /**
     * {@inheritDoc}
     */
    public function execute(\MailPoetVendor\Doctrine\DBAL\Connection $conn, array $params, array $types)
    {
        $numUpdated = 0;
        // Create temporary id table
        $conn->executeUpdate($this->_createTempTableSql);
        try {
            // Insert identifiers. Parameters from the update clause are cut off.
            $numUpdated = $conn->executeUpdate($this->_insertSql, \array_slice($params, $this->_numParametersInUpdateClause), \array_slice($types, $this->_numParametersInUpdateClause));
            // Execute UPDATE statements
            foreach ($this->_sqlStatements as $key => $statement) {
                $paramValues = array();
                $paramTypes = array();
                if (isset($this->_sqlParameters[$key])) {
                    foreach ($this->_sqlParameters[$key] as $parameterKey => $parameterName) {
                        $paramValues[] = $params[$parameterKey];
                        $paramTypes[] = isset($types[$parameterKey]) ? $types[$parameterKey] : \MailPoetVendor\Doctrine\ORM\Query\ParameterTypeInferer::inferType($params[$parameterKey]);
                    }
                }
                $conn->executeUpdate($statement, $paramValues, $paramTypes);
            }
        } catch (\Exception $exception) {
            // FAILURE! Drop temporary table to avoid possible collisions
            $conn->executeUpdate($this->_dropTempTableSql);
            // Re-throw exception
            throw $exception;
        }
        // Drop temporary table
        $conn->executeUpdate($this->_dropTempTableSql);
        return $numUpdated;
    }
}
