mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-17 11:27:45 +00:00
197 lines
6.2 KiB
PHP
197 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* Chill is a software for social workers
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace Chill\MainBundle\Service\Import;
|
|
|
|
use Doctrine\DBAL\Connection;
|
|
use Doctrine\DBAL\Statement;
|
|
|
|
/**
|
|
* Optimized way to load postal code into database.
|
|
*/
|
|
class PostalCodeBaseImporter
|
|
{
|
|
private const CREATE_TEMP_TABLE = <<<'SQL'
|
|
CREATE TEMPORARY TABLE chill_main_postal_code_temp (
|
|
countrycode VARCHAR(10),
|
|
label VARCHAR(255),
|
|
code VARCHAR(100),
|
|
refpostalcodeid VARCHAR(255),
|
|
postalcodeSource VARCHAR(255),
|
|
lon FLOAT,
|
|
lat FLOAT,
|
|
srid INT
|
|
)
|
|
SQL;
|
|
|
|
private const INSERT_TEMP = <<<'SQL'
|
|
INSERT INTO chill_main_postal_code_temp
|
|
(countrycode, label, code, refpostalcodeid, postalcodeSource, lon, lat, srid)
|
|
VALUES
|
|
{{ values }}
|
|
SQL;
|
|
|
|
private const UPSERT = <<<'SQL'
|
|
WITH g AS (
|
|
SELECT DISTINCT
|
|
country.id AS country_id,
|
|
temp.*
|
|
FROM chill_main_postal_code_temp temp
|
|
JOIN country ON country.countrycode = temp.countrycode
|
|
)
|
|
INSERT INTO chill_main_postal_code (id, country_id, label, code, origin, refpostalcodeid, postalcodeSource, center, createdAt, updatedAt, deletedAt)
|
|
SELECT
|
|
nextval('chill_main_postal_code_id_seq'),
|
|
g.country_id,
|
|
g.label,
|
|
g.code,
|
|
0,
|
|
g.refpostalcodeid,
|
|
g.postalcodeSource,
|
|
CASE WHEN (g.lon != 0.0 AND g.lat != 0.0) THEN ST_Transform(ST_setSrid(ST_point(g.lon, g.lat), g.srid), 4326) ELSE NULL END,
|
|
NOW(),
|
|
NOW(),
|
|
NULL
|
|
FROM g
|
|
ON CONFLICT (code, refpostalcodeid, postalcodeSource) WHERE refpostalcodeid IS NOT NULL DO UPDATE
|
|
SET label = excluded.label,
|
|
center = excluded.center,
|
|
deletedAt = NULL,
|
|
updatedAt = CASE WHEN NOT st_equals(excluded.center, chill_main_postal_code.center) OR excluded.label != chill_main_postal_code.label OR chill_main_postal_code.deletedAt IS NOT NULL THEN NOW() ELSE chill_main_postal_code.updatedAt END
|
|
SQL;
|
|
|
|
private const DELETE_MISSING = <<<'SQL'
|
|
UPDATE chill_main_postal_code
|
|
SET deletedAt = NOW(), updatedAt = NOW()
|
|
WHERE postalcodeSource = ?
|
|
AND deletedAt IS NULL
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM chill_main_postal_code_temp temp
|
|
WHERE temp.code = chill_main_postal_code.code
|
|
AND temp.refpostalcodeid = chill_main_postal_code.refpostalcodeid
|
|
AND temp.postalcodeSource = chill_main_postal_code.postalcodeSource
|
|
)
|
|
SQL;
|
|
|
|
private const VALUE = '(?, ?, ?, ?, ?, ?, ?, ?)';
|
|
|
|
/**
|
|
* @var array<int, Statement>
|
|
*/
|
|
private array $cachingStatements = [];
|
|
|
|
private array $waitingForInsert = [];
|
|
|
|
private bool $isInitialized = false;
|
|
|
|
private ?string $currentSource = null;
|
|
|
|
public function __construct(private readonly Connection $defaultConnection) {}
|
|
|
|
public function finalize(): void
|
|
{
|
|
$this->doInsertPending();
|
|
|
|
if ($this->isInitialized && null !== $this->currentSource) {
|
|
$this->defaultConnection->transactional(function (Connection $connection): void {
|
|
$connection->executeStatement(self::UPSERT);
|
|
$connection->executeStatement(self::DELETE_MISSING, [$this->currentSource]);
|
|
});
|
|
$this->deleteTemporaryTable();
|
|
}
|
|
|
|
$this->isInitialized = false;
|
|
$this->currentSource = null;
|
|
}
|
|
|
|
public function importCode(
|
|
string $countryCode,
|
|
string $label,
|
|
string $code,
|
|
string $refPostalCodeId,
|
|
string $refPostalCodeSource,
|
|
float $centerLat,
|
|
float $centerLon,
|
|
int $centerSRID,
|
|
): void {
|
|
if (!$this->isInitialized) {
|
|
$this->initialize($refPostalCodeSource);
|
|
}
|
|
|
|
if ($this->currentSource !== $refPostalCodeSource) {
|
|
throw new \LogicException('Cannot store postal codes from different sources during same import. Execute finalize to commit inserts before changing the source');
|
|
}
|
|
|
|
$this->waitingForInsert[] = [
|
|
$countryCode,
|
|
$label,
|
|
$code,
|
|
$refPostalCodeId,
|
|
$refPostalCodeSource,
|
|
$centerLon,
|
|
$centerLat,
|
|
$centerSRID,
|
|
];
|
|
|
|
if (100 <= \count($this->waitingForInsert)) {
|
|
$this->doInsertPending();
|
|
}
|
|
}
|
|
|
|
private function initialize(string $source): void
|
|
{
|
|
$this->currentSource = $source;
|
|
$this->deleteTemporaryTable();
|
|
$this->createTemporaryTable();
|
|
$this->isInitialized = true;
|
|
}
|
|
|
|
private function createTemporaryTable(): void
|
|
{
|
|
$this->defaultConnection->executeStatement(self::CREATE_TEMP_TABLE);
|
|
}
|
|
|
|
private function deleteTemporaryTable(): void
|
|
{
|
|
$this->defaultConnection->executeStatement('DROP TABLE IF EXISTS chill_main_postal_code_temp');
|
|
}
|
|
|
|
private function doInsertPending(): void
|
|
{
|
|
if ([] == $this->waitingForInsert) {
|
|
return;
|
|
}
|
|
|
|
if (!\array_key_exists($forNumber = \count($this->waitingForInsert), $this->cachingStatements)) {
|
|
$sql = strtr(self::INSERT_TEMP, [
|
|
'{{ values }}' => implode(
|
|
', ',
|
|
array_fill(0, $forNumber, self::VALUE)
|
|
),
|
|
]);
|
|
|
|
$this->cachingStatements[$forNumber] = $this->defaultConnection->prepare($sql);
|
|
}
|
|
|
|
$statement = $this->cachingStatements[$forNumber];
|
|
|
|
try {
|
|
$statement->executeStatement(array_merge(...$this->waitingForInsert));
|
|
} catch (\Exception $e) {
|
|
// in some case, we can add debug code here
|
|
// dump($this->waitingForInsert);
|
|
throw $e;
|
|
} finally {
|
|
$this->waitingForInsert = [];
|
|
}
|
|
}
|
|
}
|