Skip to content
Snippets Groups Projects
Commit dbf67d72 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Import : le SOURCE_ID peut être fourni dans les données à importer ou en dur...

Import : le SOURCE_ID peut être fourni dans les données à importer ou en dur dans la config via l'attribut 'code' de la 'source' ; les insertions sont désormais réalisées 1 enregistrement à la fois
parent 64ad6e7b
No related branches found
No related tags found
No related merge requests found
Showing
with 229 additions and 123 deletions
......@@ -257,24 +257,17 @@ abstract class CodeGenerator implements CodeGeneratorInterface
/**
* @param string $tableName
* @param array $columnsValues
* @param string $sourceCode
* @param array $row
* @param string|null $sourceCode
* @param string|null|false $idColumnSequence
* @return string
*/
public function generateSQLForInsertIntoTable(
string $tableName,
array $columnsValues,
string $sourceCode,
public function generateSQLForInsertOneRowIntoTable(string $tableName,
array $row,
string $sourceCode = null,
$idColumnSequence = null): string
{
$insertsSQL = '';
foreach ($columnsValues as $row) {
$sql = $this->tableHelper->generateSQLForInsertIntoTable($tableName, $row, $sourceCode, $idColumnSequence);
$insertsSQL .= $sql . PHP_EOL;
}
return $insertsSQL;
return $this->tableHelper->generateSQLForInsertOneRowIntoTable($tableName, $row, $sourceCode, $idColumnSequence);
}
/**
......
......@@ -78,15 +78,14 @@ interface CodeGeneratorInterface
/**
* @param string $tableName
* @param array $columnsValues
* @param string $sourceCode
* @param array $row
* @param string|null $sourceCode
* @param string|null|false $idColumnSequence
* @return string
*/
public function generateSQLForInsertIntoTable(
string $tableName,
array $columnsValues,
string $sourceCode,
public function generateSQLForInsertOneRowIntoTable(string $tableName,
array $row,
string $sourceCode = null,
$idColumnSequence = null): string;
/**
......
......@@ -3,7 +3,6 @@
namespace UnicaenDbImport\CodeGenerator;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use UnicaenApp\Exception\RuntimeException;
use UnicaenDbImport\Config\ConfigAwareTrait;
/**
......@@ -39,29 +38,6 @@ abstract class Helper
throw CodeGeneratorException::methodToImplement(__METHOD__, get_called_class(), $this->platform);
}
/**
* @param $value
* @return string
*/
protected function formatValueForInsert($value): string
{
if ($value === null) {
return 'NULL';
}
if ($value instanceof \stdClass) {
if (isset($value->date)) {
$tz = isset($value->timezone) ? new \DateTimeZone($value->timezone) : NULL;
$d = date_create($value->date, $tz);
$value = $d->format('c');
} else {
throw new RuntimeException("Type de valeur inattendu!");
}
}
return $this->platform->quoteStringLiteral($value);
}
/**
* @param int $length
* @param string $string
......
......@@ -2,6 +2,7 @@
namespace UnicaenDbImport\CodeGenerator\Helper;
use UnicaenApp\Exception\RuntimeException;
use UnicaenDbImport\CodeGenerator\Helper;
use UnicaenDbImport\Domain\Operation;
use UnicaenDbImport\Domain\Synchro;
......@@ -245,20 +246,20 @@ EOT;
/**
* @param string $destinationTable
* @param array $columnsValues
* @param string $sourceCode
* @param array $row ['COLONNE_EN_MAJUSCULE' => value, ...]
* @param string|null $sourceCode
* @param string|null|false $idColumnSequence
* @return string
*/
public function generateSQLForInsertIntoTable(
public function generateSQLForInsertOneRowIntoTable(
string $destinationTable,
array $columnsValues,
string $sourceCode,
array $row,
string $sourceCode = null,
$idColumnSequence = null): string
{
$now = $this->platform->getNowExpression();
$columnsValues = array_map([$this, 'formatValueForInsert'], $columnsValues);
$row = array_map([$this, 'formatValueForInsert'], $row);
$useSequenceForId = true;
if ($idColumnSequence === false) {
......@@ -266,38 +267,51 @@ EOT;
// auto-incrémentable pour que l'INSERT fonctionne.
$useSequenceForId = false;
// les éventuels ids sources sont écartés
unset($columnsValues['id']);
unset($columnsValues['ID']);
unset($row['id']);
unset($row['ID']);
} 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']);
unset($row['id']);
unset($row['ID']);
$idExpr = $this->generateSQLForSequenceNextVal($idColumnSequence);
$columnsValues['id'] = $idExpr;
$row['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,
$destinationColumns = array_keys($row);
$columnsToAdd = [
$createdOnCol,
$createdByCol,
'source_id',
);
// génération de la liste des valeurs à INSERTer.
$selectExpressions = $columnsValues;
array_unshift($selectExpressions,
];
$valuesToAdd = [
$now,
$createdByValue !== null ? $createdByValue : 'NULL',
's.id',
);
];
if ($sourceCode !== null) {
// si le code de la Source est fourni dans la config, on l'utilise
$sourceCode = $this->platform->quoteStringLiteral($sourceCode);
$columnsToAdd[] = 'source_id';
$valuesToAdd[] = 's.id';
} else {
// si la Source n'est pas fournie (par son 'code'), elle est présente dans les données (colonne 'SOURCE_ID')
$sourceCode = $row['SOURCE_ID'];
$row['SOURCE_ID'] = 's.id';
}
// génération des noms de colonnes de la table destination pour l'INSERT.
array_unshift($destinationColumns, ...$columnsToAdd);
// génération de la liste des valeurs à INSERTer.
$selectExpressions = $row;
array_unshift($selectExpressions, ...$valuesToAdd);
$commaSeparatedColumnNames = implode(', ', $destinationColumns);
$commaSeparatedColumnValues = implode(', ', $selectExpressions);
......@@ -306,7 +320,7 @@ EOT;
INSERT INTO $destinationTable ($commaSeparatedColumnNames)
SELECT $commaSeparatedColumnValues
FROM source s
WHERE s.code = '$sourceCode';
WHERE s.code = $sourceCode;
EOT;
}
......@@ -348,7 +362,6 @@ EOT;
$createdOnCol,
$createdByCol,
$sourceCodeColumn,
'source_id',
);
if ($useSequenceForId) {
array_unshift($destinationColumns,
......@@ -364,7 +377,6 @@ EOT;
$now,
$createdByValue !== null ? $createdByValue : 'NULL',
$sourceCodeColumn,
'source_id'
);
if ($useSequenceForId) {
$idExpr = $this->generateSQLForSequenceNextVal($idColumnSequence);
......@@ -443,6 +455,43 @@ EOT;
return $setters;
}
/**
* @param $value
* @return string
*/
protected function formatValueForInsert($value): string
{
if ($value === null) {
return 'NULL';
}
if ($value instanceof \stdClass) {
if (isset($value->date)) {
$tz = isset($value->timezone) ? new \DateTimeZone($value->timezone) : NULL;
$d = date_create($value->date, $tz);
return $this->formatDateTimeForInsert($d);
} else {
throw new RuntimeException("Type de valeur inattendu!");
}
}
return $this->platform->quoteStringLiteral($value);
}
/**
* Formate un DateTime pour pouvoir l'utiliser dans un INSERT.
*
* @param \DateTime $value
* @return string
*/
protected function formatDateTimeForInsert(\DateTime $value): string
{
$value = $value->format('c');
return $this->platform->quoteStringLiteral($value);
}
/**
* @param string $tableName
* @return string
......
......@@ -24,22 +24,6 @@ class CodeGenerator extends \UnicaenDbImport\CodeGenerator\CodeGenerator
$this->platform = new OraclePlatform();
}
/**
* @inheritDoc
*/
public function generateSQLForInsertIntoTable(
string $tableName,
array $columnsValues,
string $sourceCode,
$idColumnSequence = null): string
{
// les ; séparant les INSERTs perturbent le driver Oracle donc on met tout dans un bloc BEGIN END et miracle...
return
'BEGIN ' . PHP_EOL .
parent::generateSQLForInsertIntoTable($tableName, $columnsValues, $sourceCode, $idColumnSequence) .
'END;';
}
/**
* @inheritDoc
*/
......
......@@ -302,6 +302,11 @@ class ConfigFactory
$connectionName = $sourceConfig['connection'];
$sourceConfig['connection'] = $this->getConnectionByName($connectionName);
$columnValueFilter = $sourceConfig['column_value_filter'] ?? null;
if ($columnValueFilter !== null) {
$sourceConfig['column_value_filter'] = $this->getColumnValueFilterInstance($columnValueFilter);
}
$columnNameFilter = $sourceConfig['column_name_filter'] ?? null;
if ($columnNameFilter !== null) {
$sourceConfig['column_name_filter'] = $this->getColumnNameFilterInstance($columnNameFilter);
......
......@@ -109,6 +109,10 @@ class Destination implements DestinationInterface
->notEmpty()
->string();
if ($this->config->offsetExists($key = 'extra')) {
(new AssertionChain($this->config->get($key), "Un tableau est requis pour la clé suivante: $key"))
->isArray();
}
}
public function getName(): string
......@@ -141,6 +145,11 @@ class Destination implements DestinationInterface
return $this->config->get('columns');
}
public function getExtra(): array
{
return $this->config->get('extra', []);
}
public function getColumnValueFilter(): ?ColumnValueFilterInterface
{
return $this->config->get('column_value_filter');
......
......@@ -82,6 +82,12 @@ interface DestinationInterface
*/
public function getColumnValueFilter(): ?ColumnValueFilterInterface;
/**
* Retourne les éventuelles attributs libres complémentaires.
*
* @return array
*/
public function getExtra(): array;
/**
* Retourne le nom éventuel de la séquence permettant de générer les valeurs dans la colonne ID (clé primaire).
......
......@@ -11,6 +11,7 @@ use stdClass;
use UnicaenDbImport\Config\ConfigException;
use UnicaenDbImport\Connection\ApiConnection;
use UnicaenDbImport\Filter\ColumnName\ColumnNameFilterInterface;
use UnicaenDbImport\Filter\ColumnValue\ColumnValueFilterInterface;
class Source implements SourceInterface
{
......@@ -68,7 +69,15 @@ class Source implements SourceInterface
/**
* {@inheritDoc}
*/
public function getCode(): string
public function getName(): string
{
return $this->config->get('name');
}
/**
* {@inheritDoc}
*/
public function getCode(): ?string
{
return $this->config->get('code');
}
......@@ -129,6 +138,22 @@ class Source implements SourceInterface
return $this->config->get('column_name_filter');
}
/**
* {@inheritDoc}
*/
public function getColumnValueFilter() : ?ColumnValueFilterInterface
{
return $this->config->get('column_value_filter');
}
/**
* @inheritDoc
*/
public function getExtra(): array
{
return $this->config->get('extra', []);
}
/**
* {@inheritDoc}
*/
......@@ -167,10 +192,16 @@ class Source implements SourceInterface
*/
protected function validateConfig()
{
(new AssertionChain($this->config->get($key = 'code'), "Une string non vide est requise pour la clé suivante: $key"))
(new AssertionChain($this->config->get($key = 'name'), "Une string non vide est requise pour la clé suivante: $key"))
->notEmpty()
->string();
if ($this->config->offsetExists($key = 'code')) {
(new AssertionChain($this->config->get($key), "Une string non vide est requise pour la clé suivante: $key"))
->notEmpty()
->string();
}
if ($table = $this->config->get($key = 'table'))
(new AssertionChain($table, "Une string non vide est requise pour la clé suivante: $key"))
->notEmpty()
......@@ -204,6 +235,11 @@ class Source implements SourceInterface
(new AssertionChain($this->config->get($key = 'source_code_column'), "Une string non vide est requise pour la clé suivante: $key"))
->notEmpty()
->string();
if ($this->config->offsetExists($key = 'extra')) {
(new AssertionChain($this->config->get($key), "Un tableau est requis pour la clé suivante: $key"))
->isArray();
}
}
/**
......
......@@ -5,6 +5,7 @@ namespace UnicaenDbImport\Domain;
use Doctrine\DBAL\Connection;
use UnicaenDbImport\Connection\ApiConnection;
use UnicaenDbImport\Filter\ColumnName\ColumnNameFilterInterface;
use UnicaenDbImport\Filter\ColumnValue\ColumnValueFilterInterface;
interface SourceInterface
{
......@@ -94,6 +95,21 @@ interface SourceInterface
*/
public function getColumnNameFilter() : ?ColumnNameFilterInterface;
/**
* Retourne l'instance de filtre permettant de transformer une valeur de colonne/attribut source
* en une valeur de colonne destination.
*
* @return ColumnValueFilterInterface|null
*/
public function getColumnValueFilter(): ?ColumnValueFilterInterface;
/**
* Retourne les éventuelles attributs libres complémentaires.
*
* @return array
*/
public function getExtra(): array;
/**
* Retourne les données obtenues par l'application de la requête associée à la source.
*
......
......@@ -23,6 +23,7 @@ class DatabaseService
const SOURCE_TABLE_NAME = 'SOURCE';
const SOURCE_TABLE_CODE_COLUMN = 'CODE';
const DATA_SLICE_SIZE_FOR_INSERT = 1000;
/**
* @var CodeGeneratorPluginManager
......@@ -245,6 +246,9 @@ class DatabaseService
$tableName = self::SOURCE_TABLE_NAME;
$column = self::SOURCE_TABLE_CODE_COLUMN;
$value = $this->source->getCode();
if ($value === null) {
return;
}
// test if value exists in table
$sql = $this->codeGenerator->generateSQLForValueExistenceCheckInTable($column, $value, $tableName);
......@@ -397,25 +401,32 @@ class DatabaseService
$sourceCode = $this->source->getCode();
$data = $this->source->getData();
$rows = [];
foreach ($data as $row) {
$count = 0;
// insert par paquets
$sliceSize = self::DATA_SLICE_SIZE_FOR_INSERT;
for ($offset = 0; $offset < count($data) ; $offset += $sliceSize) {
$slicedData = array_slice($data, $offset, $sliceSize);
foreach ($slicedData as $row) {
$preparedRow = $this->prepareSourceData($row);
$columnsValues = $this->prepareDestinationData($preparedRow);
$rows[] = $columnsValues;
}
$insertsSQL = $this->codeGenerator->generateSQLForInsertIntoTable($tableName, $rows, $sourceCode, $idColumnSequence);
// Exécution de la requête (inserts) dans la table destination
$insertsSQL = $this->codeGenerator->generateSQLForInsertOneRowIntoTable($tableName, $columnsValues, $sourceCode, $idColumnSequence);
try {
$this->queryExecutor->exec($insertsSQL, $this->destination->getConnection());
$count += $this->queryExecutor->exec($insertsSQL, $this->destination->getConnection());
} catch (Exception $e) {
throw DatabaseServiceException::error("Erreur rencontrée lors du remplissage de la table destination '$tableName'", $e);
}
}
}
return count($rows);
return $count;
}
/**
* @throws \UnicaenDbImport\Service\Exception\DatabaseServiceException
*/
private function prepareSourceData(array $row): array
{
// transformations éventuelles de valeurs de colonnes/attributs
......@@ -434,6 +445,19 @@ class DatabaseService
$row = array_combine($filteredKeys, $row);
}
// si la Source n'est pas fournie (par son 'code'), il faut que les données l'inclue (colonne 'SOURCE_ID')
$sourceCode = $this->source->getCode();
if ($sourceCode === null) {
if (array_key_exists('source_id', $row)) {
$row['SOURCE_ID'] = $row['source_id'];
}
elseif (!array_key_exists('SOURCE_ID', $row)) {
throw DatabaseServiceException::error(
"Lorsque la config ne fournie pas le 'code' de la source ({$this->source}), " .
"les données doivent contenir une colonne SOURCE_ID ou source_id");
}
}
return $row;
}
......@@ -443,9 +467,10 @@ class DatabaseService
$sourceCodeColumn = $this->source->getSourceCodeColumn();
$columns = array_merge([$sourceCodeColumn], $columns);
// préparation des données :
// - noms de colonnes en majuscules ;
// - mise à NULL des colonnes pour lesquelles on n'a pas de valeur.
$columnsValues = [];
// mise à NULL des colonnes pour lesquelles on n'a pas de valeur
foreach (array_map('strtoupper', $columns) as $column) {
$columnsValues[$column] = $row[$column] ?? $row[strtolower($column)] ?? null;
}
......
......@@ -224,6 +224,7 @@ abstract class AbstractFacadeService
"Impossible de déterminer la liste des colonnes à partir des données sources car leur type est inconnu (ni array ni stdClass)");
}
return array_diff($columns, ['source_id', 'SOURCE_ID']);
//return array_diff($columns, ['source_id', 'SOURCE_ID']);
return $columns;
}
}
\ No newline at end of file
......@@ -7,7 +7,6 @@ use Psr\Log\LoggerAwareTrait;
use UnicaenApp\Exception\RuntimeException;
use UnicaenDbImport\Config\Config;
use UnicaenDbImport\Domain\Exception\NotFoundException;
use UnicaenDbImport\Domain\ImportInterface;
use UnicaenDbImport\Domain\NameAwareInterface;
use UnicaenDbImport\Domain\SynchroInterface;
use UnicaenDbImport\Domain\SynchroResult;
......
......@@ -9,13 +9,15 @@
<dd class="col-md-7"><?php echo $destination->getName(); ?></dd>
<dt class="col-md-4">Connexion</dt>
<dd class="col-md-7"><?php echo $this->partial('unicaen-db-import/partial/connection-dl', ['connection' => $destination->getConnection()]); ?></dd>
<dt class="col-md-3">Table</dt>
<dt class="col-md-4">Table</dt>
<dd class="col-md-7"><?php echo $destination->getTable(); ?></dd>
<dt class="col-md-3">Where</dt>
<dt class="col-md-4">Where</dt>
<dd class="col-md-7"><?php echo $destination->getWhere() ?: '-'; ?></dd>
<dt class="col-md-3">Colonne discriminante</dt>
<dt class="col-md-4">Colonne discriminante</dt>
<dd class="col-md-7"><?php echo $destination->getSourceCodeColumn(); ?></dd>
<dt class="col-md-3">Table intermédiaire</dt>
<dt class="col-md-4">Column Value Filter</dt>
<dd class="col-md-8"><?php echo $destination->getColumnValueFilter() ?: '-'; ?></dd>
<dt class="col-md-4">Table intermédiaire</dt>
<dd class="col-md-7">
<?php if ($destination->usesIntermediateTable()): ?>
<?php echo $destination->getIntermediateTable() ?: "Aucune"; ?>
......@@ -23,7 +25,7 @@
<abbr title="Non appplicable">NA</abbr>
<?php endif ?>
</dd>
<dt class="col-md-3">Auto drop</dt>
<dt class="col-md-4">Auto drop</dt>
<dd class="col-md-7">
<?php if ($destination->usesIntermediateTable()): ?>
<?php echo $destination->getIntermediateTableAutoDrop() ? 'Oui' : 'Non'; ?>
......@@ -31,8 +33,8 @@
<abbr title="Non appplicable">NA</abbr>
<?php endif ?>
</dd>
<dt class="col-md-3">Séquence utilisée</dt>
<dt class="col-md-4">Séquence utilisée</dt>
<dd class="col-md-7"><?php echo $destination->getIdColumnSequence() ?: '-'; ?></dd>
<dt class="col-md-3">Table de logs</dt>
<dt class="col-md-4">Table de logs</dt>
<dd class="col-md-7"><?php echo $destination->getLogTable(); ?></dd>
</dl>
......@@ -14,10 +14,16 @@ use UnicaenDbImport\Utils;
<dd class="col-md-8"><?php echo $source->getCode() ?: '-'; ?></dd>
<dt class="col-md-4">Connexion</dt>
<dd class="col-md-8"><?php echo $this->partial('unicaen-db-import/partial/connection-dl', ['connection' => $source->getConnection()]); ?></dd>
<dt class="col-md-3">Table/Select</dt>
<dt class="col-md-4">Table/Select</dt>
<dd class="col-md-8"><?php echo $source->getTable() ?: $source->getSelect(); ?></dd>
<dt class="col-md-3">Where</dt>
<dt class="col-md-4">Where</dt>
<dd class="col-md-8"><?php echo $source->getWhere() ?: '-'; ?></dd>
<dt class="col-md-3">Colonne discriminante</dt>
<dt class="col-md-4">Colonne discriminante</dt>
<dd class="col-md-8"><?php echo $source->getSourceCodeColumn(); ?></dd>
<dt class="col-md-4">Column Value Filter</dt>
<dd class="col-md-8"><?php echo $source->getColumnValueFilter() ?: '-'; ?></dd>
<dt class="col-md-4">Column Name Filter</dt>
<dd class="col-md-8"><?php echo $source->getColumnNameFilter() ?: '-'; ?></dd>
<dt class="col-md-4">Extra</dt>
<dd class="col-md-8"><?php echo Utils::associativeArrayToString($source->getExtra()); ?></dd>
</dl>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment