Skip to content
Snippets Groups Projects
Select Git revision
  • 3f5b8b64de2c47d5d4cb1fc08daad1e77e4c3967
  • master default protected
  • release_7.2.1
  • release_7.2.0
  • unicaen_authentification
  • release_6.1.2
  • release_6.0.4
  • release_6.1.0
  • release_6.0.3
  • 6.1.0
  • smile
  • 5.x
  • release_5.2.2
  • utilisateur
  • release_5.2.1
  • laminas-bs5-UnicaenPrivilege
  • feature_import_one_row
  • laminas_migration
  • PHP_7-4
  • release_4.0.2
  • release_4.0.0
  • 7.3.2
  • 7.3.1
  • 7.3.0
  • 7.2.1
  • 7.2.0
  • 7.1.0
  • 6.2.0
  • 7.0.0
  • 6.1.4
  • 6.1.3
  • 6.1.2
  • 6.1.1
  • 6.1.0
  • 6.0.3
  • 6.0.2
  • 5.2.3
  • 6.0.1
  • 6.0.0
  • 5.2.2
  • emc2_1.0.0
41 results

TableHelper.php

Blame
  • Bertrand GAUTHIER's avatar
    Bertrand Gauthier authored
    Optimisation : pas besoin d'interroger la vue diff pour générer le SQL ; AMélioration : une erreur lors des inserts d'une synchro par exemple ne stoppe plus les synchros.
    2afc8fed
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    TableHelper.php 18.15 KiB
    <?php
    
    namespace UnicaenDbImport\CodeGenerator\Helper;
    
    use Doctrine\DBAL\DBALException;
    use UnicaenApp\Exception\LogicException;
    use UnicaenApp\Exception\RuntimeException;
    use UnicaenApp\Util;
    use UnicaenDbImport\CodeGenerator\Helper;
    use UnicaenDbImport\Domain\Operation;
    
    /**
     * Génération de code SQL autour des tables.
     *
     * Version commune à toutes les plateformes de bases de données.
     *
     * @author Unicaen
     */
    abstract class TableHelper extends Helper
    {
        const V_DIFF_COLUMN_PREFIX = 'S_';
    
        /**
         * @param string $tableName
         * @return string
         */
        public function generateSQLForSelectFromTable($tableName)
        {
            return "SELECT * FROM " . $tableName;
        }
    
        /**
         * @param string   $destinationTable
         * @param string   $sourceCodeColumn
         * @param string[] $columns
         * @param string   $intermediateTable
         * @return string
         */
        public function generateSQLForIntermediateTableCreation($destinationTable, $sourceCodeColumn, $columns, $intermediateTable)
        {
            $commaSeparatedColumnNames = implode(', ', array_merge([$sourceCodeColumn], $columns));
    
            $sqlTemplate = <<<'EOT'
    create table {intermediateTable} as select {commaSeparatedColumnNames} from {destinationTable};
    delete from {intermediateTable};
    EOT;
            return Util::tokenReplacedString($sqlTemplate, compact('intermediateTable', 'destinationTable', 'commaSeparatedColumnNames'));
        }
    
        /**
         * @param string $tableName
         * @return string
         */
        public function generateSQLForTruncateTable($tableName)
        {
            return $this->platform->getTruncateTableSQL($tableName);
        }
    
        /**
         * @param string $diffSourceTableName
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array  $columns
         * @return string
         * @throws DBALException
         */
        public function generateSQLForDiffViewCreation($diffSourceTableName, $destinationTable, $sourceCodeColumn, array $columns)
        {
            $res = '';
            $res .= $this->generateDiffViewDeletionSQLSnippet($destinationTable) . ';' . PHP_EOL;
            $res .= $this->generateDiffViewCreationSQLSnippet($diffSourceTableName, $destinationTable, $sourceCodeColumn, $columns) . PHP_EOL;
    
            return $res;
        }
    
        /**
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array  $columns
         * @return string
         */
        public function generateSQLForDiffViewSelect($destinationTable, $sourceCodeColumn, array $columns)
        {
            // colonne ID écartée
            $columns = array_diff($columns, ['ID', 'id']);
    
            $name = $this->generateDiffViewName($destinationTable);
    
            $updatedColumsSql = implode(
                array_map(function($col) {
                    return sprintf('U_%s', $col);
                }, $columns),
                ','
            );
    
            $srcColumsSql = implode(
                array_map(function($col) {
                    return sprintf('S_%s', $col);
                }, $columns),
                ',' . PHP_EOL
            );
    
            $destColumsSql = implode(
                array_map(function($col) {
                    return sprintf('D_%s', $col);
                }, $columns),
                ',' . PHP_EOL
            );
    
            $sql = <<<EOT
    SELECT 
        $sourceCodeColumn, 
        operation, 
        $updatedColumsSql, 
        $srcColumsSql, 
        $destColumsSql 
    FROM $name
    EOT;
    
            return $sql;
        }
    
        /**
         * @param string $destinationTable
         * @return string
         */
        protected function generateDiffViewName($destinationTable)
        {
            return 'V_DIFF_' . $destinationTable;
        }
    
        /**
         * @param string $destinationTable
         * @return string
         * @throws DBALException
         */
        protected function generateDiffViewDeletionSQLSnippet($destinationTable)
        {
            $name = $this->generateDiffViewName($destinationTable);
    
            return $this->platform->getDropViewSQL($name);
        }
    
        /**
         * @param string $diffSourceTableName
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array  $columns
         * @return string
         */
        protected function generateDiffViewCreationSQLSnippet($diffSourceTableName, $destinationTable, $sourceCodeColumn, array $columns)
        {
            $name = $this->generateDiffViewName($destinationTable);
    
            $res = '';
            $res .= "CREATE VIEW $name AS " . PHP_EOL;
            $res .= $this->indent(4, $this->generateDiffViewBodySelectSQLSnippet($diffSourceTableName, $destinationTable, $sourceCodeColumn, $columns)) . PHP_EOL;
    
            return $res;
        }
    
        /**
         * @param string $diffSourceTableName
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array  $columns
         * @return string
         */
        protected function generateDiffViewBodySelectSQLSnippet($diffSourceTableName, $destinationTable, $sourceCodeColumn, array $columns)
        {
            // colonne ID écartée
            $columns = array_diff($columns, ['ID', 'id']);
    
            $insert = Operation::OPERATION_INSERT;
            $update = Operation::OPERATION_UPDATE;
            $undelete = Operation::OPERATION_UNDELETE;
            $delete = Operation::OPERATION_DELETE;
    
            $updatedColumsSql = implode(
                array_map(function($col) {
                    $tpl = 'CASE WHEN d.%s <> s.%1$s OR (d.%1$s IS NULL AND s.%1$s IS NOT NULL) OR (d.%1$s IS NOT NULL AND s.%1$s IS NULL) THEN 1 ELSE 0 END as U_%1$s';
                    return sprintf($tpl, $col);
                }, $columns),
                ',' . PHP_EOL
            );
    
            $srcColumsSql = implode(
                array_map(function($col) {
                    return sprintf('s.%1$s AS S_%1$s', $col);
                }, $columns),
                ',' . PHP_EOL
            );
    
            $destColumsSql = implode(
                array_map(function($col) {
                    return sprintf('d.%1$s AS D_%1$s', $col);
                }, $columns),
                ',' . PHP_EOL
            );
    
            $udpatedColumnsAddition = implode(
                array_map(function($col) {
                    return sprintf('U_%s', $col);
                }, $columns),
                '+'
            );
    
            $now = $this->platform->getNowExpression();
    
            $deletedOnColumn = $this->config->getHistoColumnAliasForDeletedOn();
    
            return <<<EOT
    with diff as (
        SELECT
            coalesce(s.$sourceCodeColumn, d.$sourceCodeColumn) $sourceCodeColumn,
            coalesce(s.source_id, d.source_id) source_id,
    
            case 
            when (s.$sourceCodeColumn IS NOT NULL AND d.$sourceCodeColumn IS NULL) then '$insert'
            when (s.$sourceCodeColumn IS NOT NULL AND d.$sourceCodeColumn IS NOT NULL and (d.$deletedOnColumn IS NULL or d.$deletedOnColumn > $now)) then '$update'
            when (s.$sourceCodeColumn IS NOT NULL AND d.$sourceCodeColumn IS NOT NULL and (d.$deletedOnColumn IS NOT NULL and d.$deletedOnColumn <= $now)) then '$undelete'
            when (s.$sourceCodeColumn IS NULL AND d.$sourceCodeColumn IS NOT NULL and (d.$deletedOnColumn IS NULL or d.$deletedOnColumn > $now)) then '$delete' end as operation,
    
            $updatedColumsSql,
    
            $srcColumsSql,
    
            $destColumsSql
    
        FROM $destinationTable d
        JOIN source src ON src.id = d.source_id AND src.importable = 1
        FULL OUTER JOIN $diffSourceTableName s ON s.source_id = d.source_id AND s.$sourceCodeColumn = d.$sourceCodeColumn
    )
    select * from diff
    where operation is not null
    and (operation = '$undelete' or 0 < $udpatedColumnsAddition)
    EOT;
        }
    
        /**
         * @param array $rows
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param string[] $columns
         * @param string|null|false $idColumnSequence
         * @return string[]
         */
        public function generateSQLForOperationInDestinationTableFromDiffViewRequestResult(
            array $rows,
            $destinationTable,
            $sourceCodeColumn,
            $columns,
            $idColumnSequence = null)
        {
            // colonne ID écartée
            $columns = array_diff($columns, ['ID', 'id']);
    
            // réorganisation par type d'opération
            $data = [];
            foreach ($rows as $row) {
                $operation = $row['operation'] ?? $row['OPERATION'];
                if (!isset($data[$operation])) {
                    $data[$operation] = [];
                }
                $data[$operation][] = $row;
            };
    
            $sqls = [];
    
            // une instruction SQL (update ou insert) par type d'opération suffira à mettre à jour la table destination
            $operations = array_keys($data);
            foreach ($operations as $operation) {
                switch ($operation) {
                    case Operation::OPERATION_INSERT:
                        $sqls[] = $this->generateSQLForInsertOperationInDestinationTable(
                            $destinationTable,
                            $sourceCodeColumn,
                            $columns,
                            $idColumnSequence) . PHP_EOL;
                        break;
                    case Operation::OPERATION_UPDATE:
                        $sqls[] = $this->generateSQLForUpdateOperationInDestinationTable(
                            $destinationTable,
                            $sourceCodeColumn,
                            $columns) . PHP_EOL;
                        break;
                    case Operation::OPERATION_UNDELETE:
                        $sqls[] = $this->generateSQLForUndeleteOperationInDestinationTable(
                            $destinationTable,
                            $sourceCodeColumn,
                            $columns) . PHP_EOL;
                        break;
                    case Operation::OPERATION_DELETE:
                        $sqls[] = $this->generateSQLForDeleteOperationInDestinationTable(
                            $destinationTable,
                            $sourceCodeColumn,
                            $columns) . PHP_EOL;
                        break;
                    default:
                        throw new RuntimeException("Opération inconnue rencontrée : " . $operation);
                        break;
                }
            }
    
    //        return empty($sqls) ? '' : implode(PHP_EOL, $sqls);
            return $sqls;
        }
    
        /**
         * @param string $destinationTable
         * @param array $columnsValues
         * @param string $sourceCode
         * @param string|null|false $idColumnSequence
         * @return string
         */
        public function generateSQLForInsertIntoTable(
            string $destinationTable,
            array $columnsValues,
            string $sourceCode,
            $idColumnSequence = null)
        {
            $now = $this->platform->getNowExpression();
    
            $columnsValues = array_map([$this, 'formatValueForInsert'], $columnsValues);
    
            $useSequenceForId = true;
            if ($idColumnSequence === false) {
                // si false est spécifié comme nom de séquence pour la colonne 'id', le type de cette colonne devra être
                // auto-incrémentable pour que l'INSERT fonctionne.
                $useSequenceForId = false;
            } elseif ($idColumnSequence === null) {
                // si aucun nom de séquence n'a été fourni, on tente celui par défaut.
                $idColumnSequence = $this->generateSQLForIdSequenceDefaultName($destinationTable);
            }
            if ($useSequenceForId) {
                // les éventuels id d'origine sont écrasés
                unset($columnsValues['id']);
                unset($columnsValues['ID']);
                $idExpr = $this->generateSQLForSequenceNextVal($idColumnSequence);
                $columnsValues['id'] = $idExpr;
            }
    
            $createdOnCol = $this->config->getHistoColumnAliasForCreatedOn();
            $createdByCol = $this->config->getHistoColumnAliasForCreatedBy();
            $createdByValue = $this->config->getHistoColumnValueForCreatedBy();
    
            // génération des noms de colonnes de la table destination pour l'INSERT.
            $destinationColumns = array_keys($columnsValues);
            array_unshift($destinationColumns,
                $createdOnCol,
                $createdByCol,
                'source_id',
            );
            // génération de la liste des valeurs à INSERTer.
            $selectExpressions = $columnsValues;
            array_unshift($selectExpressions,
                $now,
                $createdByValue !== null ? $createdByValue : 'NULL',
                's.id',
            );
    
            $commaSeparatedColumnNames = implode(', ', $destinationColumns);
            $commaSeparatedColumnValues = implode(', ', $selectExpressions);
    
            return <<<EOT
    INSERT INTO $destinationTable d ($commaSeparatedColumnNames) 
    SELECT $commaSeparatedColumnValues 
    FROM source s
    WHERE s.code = '$sourceCode';
    EOT;
        }
    
        /**
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array $columns
         * @param string|null|false $idColumnSequence
         * @return string
         */
        public function generateSQLForInsertOperationInDestinationTable(
            $destinationTable,
            $sourceCodeColumn,
            array $columns,
            $idColumnSequence = null)
        {
            $diffViewName = $this->generateDiffViewName($destinationTable);
            $now = $this->platform->getNowExpression();
    
            $useSequenceForId = true;
            if ($idColumnSequence === false) {
                // si false est spécifié comme nom de séquence pour la colonne 'id', le type de cette colonne devra être
                // auto-incrémentable pour que l'INSERT fonctionne.
                $useSequenceForId = false;
            } elseif ($idColumnSequence === null) {
                // si aucun nom de séquence n'a été fourni, on tente celui par défaut.
                $idColumnSequence = $this->generateSQLForIdSequenceDefaultName($destinationTable);
            }
    
            $createdOnCol = $this->config->getHistoColumnAliasForCreatedOn();
            $createdByCol = $this->config->getHistoColumnAliasForCreatedBy();
            $createdByValue = $this->config->getHistoColumnValueForCreatedBy();
    
            // génération des noms de colonnes de la table destination pour l'INSERT.
            $destinationColumns = $columns;
            array_unshift($destinationColumns,
                $createdOnCol,
                $createdByCol,
                $sourceCodeColumn,
                'source_id',
            );
            if ($useSequenceForId) {
                array_unshift($destinationColumns,
                    'id',
                );
            }
    
            // génération de la liste des noms de colonnes provenant de la vue diff pour l'INSERT.
            $selectExpressions = array_map(function (string $columnName) {
                return self::V_DIFF_COLUMN_PREFIX . strtoupper($columnName);
            }, $columns);
            array_unshift($selectExpressions,
                $now,
                $createdByValue !== null ? $createdByValue : 'NULL',
                $sourceCodeColumn,
                'source_id'
            );
            if ($useSequenceForId) {
                $idExpr = $this->generateSQLForSequenceNextVal($idColumnSequence);
                array_unshift($selectExpressions,
                    $idExpr,
                );
            }
    
            $operation = Operation::OPERATION_INSERT;
    
            // instruction de mise à jour de la table destination
            $markup = '';
            $markup .= "INSERT INTO $destinationTable (" . implode(', ', $destinationColumns) . ")" . PHP_EOL;
            $markup .= "SELECT " . implode(', ', $selectExpressions) . PHP_EOL;
            $markup .= "FROM $diffViewName WHERE operation = '$operation' ";
    
            return $markup;
        }
    
        /**
         * @param string $operation
         * @param array $columns
         * @return string
         */
        protected function generateSQLUpdateSetters(string $operation, array $columns)
        {
            $now = $this->platform->getNowExpression();
    
            switch ($operation) {
    
                case Operation::OPERATION_UPDATE:
                case Operation::OPERATION_UNDELETE:
                    $colUpdates = array_map(function ($col) {
                        return "$col = diff.S_$col";
                    }, $columns);
    
                    $updatedOnColumn = $this->config->getHistoColumnAliasForUpdatedOn();
                    $updatedByColumn = $this->config->getHistoColumnAliasForUpdatedBy();
                    $updatedByValue = $this->config->getHistoColumnValueForUpdatedBy();
                    $deletedOnColumn = $this->config->getHistoColumnAliasForDeletedOn();
                    $deletedByColumn = $this->config->getHistoColumnAliasForDeletedBy();
                    $updatedByValue = $updatedByValue !== null ? $updatedByValue : 'NULL';
                    array_unshift($colUpdates,
                        "$updatedOnColumn = $now",
                        "$updatedByColumn = $updatedByValue",
                        "$deletedOnColumn = null",
                        "$deletedByColumn = null"
                    );
    
                    $setters = implode(', ', $colUpdates);
                    break;
    
                case Operation::OPERATION_DELETE:
                    $deletedOnColumn = $this->config->getHistoColumnAliasForDeletedOn();
                    $deletedByColumn = $this->config->getHistoColumnAliasForDeletedBy();
                    $deletedByValue = $this->config->getHistoColumnValueForDeletedBy();
                    $deletedByValue = $deletedByValue !== null ? $deletedByValue : 'NULL';
                    $colUpdates = [
                        "$deletedOnColumn = $now",
                        "$deletedByColumn = $deletedByValue"
                    ];
    
                    $setters =  implode(', ', $colUpdates);
                    break;
    
                default:
                    throw new LogicException("Opération spécifiée inattendue: " . $operation);
            }
    
            return $setters;
        }
    
        /**
         * @param string $tableName
         * @return string
         */
        public function generateSQLForIdSequenceDefaultName(string $tableName)
        {
            return $tableName . '_ID_SEQ';
        }
    
        /**
         * @param string $sequenceName
         * @return string
         */
        abstract public function generateSQLForSequenceNextVal(string $sequenceName);
    
        /**
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array $columns
         * @return string
         */
        abstract public function generateSQLForUpdateOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns);
    
        /**
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array $columns
         * @return string
         */
        abstract public function generateSQLForUndeleteOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns);
    
        /**
         * @param string $destinationTable
         * @param string $sourceCodeColumn
         * @param array $columns
         * @return string
         */
        abstract public function generateSQLForDeleteOperationInDestinationTable($destinationTable, $sourceCodeColumn, array $columns);
    }