Guides
Architecture & Déploiement
Transformation PostgreSQL

Transformation CSWeb PostgreSQL

Travail Technique par Assietou DIAGNE

ANSD (Agence Nationale de la Statistique et de la Démographie, Sénégal)

Ce guide documente le travail technique complet réalisé par Assietou DIAGNE pour transformer CSWeb vanilla (MySQL uniquement) en une version supportant PostgreSQL de manière native.

Contact Assietou DIAGNE :


Table des Matières

  1. Introduction
  2. Contexte et Problématique
  3. Vue d'Ensemble des Modifications
  4. Étape 1: Intégration du Driver pdo_pgsql
  5. Étape 2: Mise à Jour de la Base de Données
  6. Étape 3: Transformation des Scripts
  7. Étape 4: Génération des Schémas PostgreSQL
  8. Reproduire sur CSWeb Vanilla
  9. Références Techniques

Introduction

Objectif Transformer CSWeb vanilla (MySQL only) en une version supportant PostgreSQL pour permettre :

Breakout vers PostgreSQL (meilleur pour analytics) Support multi-SGBD (PostgreSQL, MySQL, SQL Server) Architecture flexible (local/remote) Performance améliorée (indexation PostgreSQL)

Auteur Ce travail technique a été réalisé par Assietou DIAGNE (ANSD, Sénégal) dans le cadre du projet RGPH5 (Recensement Général de la Population et de l'Habitat du Sénégal).


Contexte et Problématique

CSWeb Vanilla (Avant)

Limites :

Support MySQL uniquement pour le breakout Pas de driver PostgreSQL intégré Code hardcodé pour MySQL (MySQLDictionarySchemaGenerator) Fonctions SQL spécifiques MySQL (incompatibles PostgreSQL)

Architecture :


 CSWeb Vanilla (Setup.php) 

 Base Métadonnées: MySQL (SEUL) 
 Base Breakout: MySQL (SEUL) 

CSWeb Community v2.0 (Après Transformation)

Améliorations :

Support PostgreSQL natif Support MySQL (rétrocompatible) Support SQL Server (Microsoft) Architecture flexible (local/remote) Breakout sélectif par dictionnaire

Architecture :


 CSWeb Community v2.0 (Transformé) 

 Base Métadonnées: MySQL (local) 
 Base Breakout: PostgreSQL/MySQL/SQLServer 
 (local OU remote) 

Vue d'Ensemble des Modifications

Fichiers PHP Modifiés (Core)

FichierModificationsImpact
DictionarySchemaHelper.phpAjout support pdo_pgsqlDriver PostgreSQL disponible
DataSettings.phpDétection type DB breakoutConfiguration multi-SGBD
MapDataRepository.phpGestion connexion PostgreSQLRepository flexible
MySQLDictionarySchemaGenerator.phpTransformation complèteGénération schéma PostgreSQL

Transformations Clés

  1. cleanDictionarySchema() - Nettoyage schéma avant breakout
  2. createDictionarySchema() - Création schéma PostgreSQL
  3. generateDictionary() - Génération structure dictionnaire
  4. createDefaultTables() - Création tables cases, notes, jobs, cspro_meta

Étape 1: Intégration du Driver pdo_pgsql

A. Modification de DictionarySchemaHelper.php

Objectif : Ajouter le driver PostgreSQL aux drivers disponibles.

Code Avant (MySQL uniquement)

class DictionarySchemaHelper
{
 // Uniquement MySQL supporté
 private $mysqlConnection;
 
 public function __construct()
 {
 // Hardcoded MySQL
 $this->mysqlConnection = new PDO('mysql:host=...');
 }
}

Code Après (Multi-SGBD)

class DictionarySchemaHelper
{
 private $connection;
 private $dbType; // 'mysql', 'pgsql', 'sqlsrv'
 
 public function __construct($dbType = 'mysql')
 {
 $this->dbType = $dbType;
 
 switch ($dbType) {
 case 'pgsql':
 $this->connection = new PDO('pgsql:host=...'); // PostgreSQL break;
 case 'mysql':
 $this->connection = new PDO('mysql:host=...');
 break;
 case 'sqlsrv':
 $this->connection = new PDO('sqlsrv:Server=...');
 break;
 }
 }
}

B. Modification de DataSettings.php

Objectif : Lire la configuration du SGBD de breakout depuis .env.

Ajout Variables d'Environnement

// Dans DataSettings.php
public function getBreakoutDbType(): string
{
 return $_ENV['BREAKOUT_DB_TYPE'] ?? 'mysql'; // Par défaut: mysql
}
 
public function getBreakoutDbHost(): string
{
 return $_ENV['BREAKOUT_DB_HOST'] ?? 'localhost';
}
 
public function getBreakoutDbPort(): int
{
 $type = $this->getBreakoutDbType();
 
 // Ports par défaut selon SGBD
 $defaultPorts = [
 'mysql' => 3306,
 'pgsql' => 5432,
 'sqlsrv' => 1433,
 ];
 
 return $_ENV['BREAKOUT_DB_PORT'] ?? $defaultPorts[$type];
}

Fichier .env Correspondant

# Configuration Breakout Database
BREAKOUT_DB_TYPE=pgsql # mysql|pgsql|sqlsrv
BREAKOUT_DB_HOST=csweb_postgres
BREAKOUT_DB_PORT=5432
BREAKOUT_DB_NAME=csweb_analytics
BREAKOUT_DB_USER=csweb_user
BREAKOUT_DB_PASSWORD=VOTRE_PASSWORD
BREAKOUT_DB_SCHEMA=public # Schéma PostgreSQL (optionnel)

C. Modification de MapDataRepository.php

Objectif : Utiliser la connexion PostgreSQL au lieu de MySQL pour le repository.

Code Transformé

class MapDataRepository
{
 private $connection;
 private $dbType;
 
 public function __construct(DataSettings $settings)
 {
 $this->dbType = $settings->getBreakoutDbType();
 
 // Créer connexion selon type DB
 $dsn = $this->buildDsn($settings);
 $this->connection = new PDO(
 $dsn,
 $settings->getBreakoutDbUser(),
 $settings->getBreakoutDbPassword()
 );
 }
 
 private function buildDsn(DataSettings $settings): string
 {
 switch ($this->dbType) {
 case 'pgsql':
 return sprintf(
 'pgsql:host=%s;port=%d;dbname=%s',
 $settings->getBreakoutDbHost(),
 $settings->getBreakoutDbPort(),
 $settings->getBreakoutDbName()
 );
 
 case 'mysql':
 return sprintf('mysql:host=%s;port=%d;dbname=%s', ...);
 
 case 'sqlsrv':
 return sprintf('sqlsrv:Server=%s,%d;Database=%s', ...);
 }
 }
}

Étape 2: Mise à Jour de la Base de Données

A. Transformation cleanDictionarySchema()

Objectif : Nettoyer le schéma PostgreSQL avant un nouveau breakout (supprimer tables existantes).

Code Avant (MySQL)

private function cleanDictionarySchema() {
 try {
 $tables = $this->conn->getSchemaManager()->listTables();
 if ((is_countable($tables) ? count($tables) : 0) > 0) {
 $this->conn->prepare("SET FOREIGN_KEY_CHECKS = 0;")->execute(); // MySQL specific foreach ($tables as $table) {
 $sql = 'DROP TABLE ' . $table->getName(); // MySQL syntax
 $this->conn->prepare($sql)->execute();
 }
 
 $this->conn->prepare("SET FOREIGN_KEY_CHECKS = 1;")->execute();
 }
 } catch (\Exception $e) {
 throw $e;
 }
}

Code Après (PostgreSQL Compatible)

private function cleanDictionarySchema() {
 try {
 $tables = $this->conn->getSchemaManager()->listTables();
 if (count($tables) > 0) {
 // PostgreSQL: Pas de FOREIGN_KEY_CHECKS, utiliser CASCADE
 // var_dump(count($tables)); die;
 
 foreach ($tables as $table) {
 // DROP TABLE avec CASCADE pour PostgreSQL
 $sql = 'DROP TABLE "' . $table->getName() . '" CASCADE';
 
 // Optionnel: Désactiver triggers temporairement
 // $this->conn->prepare('ALTER TABLE "' . $table->getName() . '" DISABLE TRIGGER ALL;')->execute();
 
 $this->conn->prepare($sql)->execute();
 
 // $this->conn->prepare('ALTER TABLE "' . $table->getName() . '" ENABLE TRIGGER ALL;')->execute();
 }
 }
 } catch (\Exception $e) {
 $strMsg = "Failed deleting tables from database: " . $this->connectionParams['dbname'] . " while processing Dictionary: " . $this->dictionaryName;
 $this->logger->error($strMsg, ["context" => (string) $e]);
 throw $e;
 }
}

Différences Clés :

MySQLPostgreSQL
SET FOREIGN_KEY_CHECKS = 0;Pas nécessaire, utiliser CASCADE
DROP TABLE table_nameDROP TABLE "table_name" CASCADE
Noms sans guillemetsNoms entre guillemets doubles "

B. Transformation createDictionarySchema()

Objectif : Insérer les métadonnées du dictionnaire dans cspro_meta (table commune).

Code Avant

private function createDictionarySchema($processCasesOptions) {
 $bind = [];
 try {
 $dictionarySchema = new MySQLDictionarySchemaGenerator($this->logger);
 
 $processCasesOptions = $this->getProcessCaseOptions();
 $schema = $dictionarySchema->generateDictionary($this->dictionary, $processCasesOptions);
 
 $dictionarySQL = $schema->toSql($this->conn->getDatabasePlatform());
 $dictionarySQL = implode(";" . PHP_EOL, $dictionarySQL);
 $this->logger->debug("writing schema SQL " . $dictionarySQL);
 
 $this->conn->prepare($dictionarySQL)->execute();
 
 //insert into cspro_meta dictionary_information
 $dictionaryVersion = $this->dictionary->getVersion();
 $stm = "SELECT modified_time, `dictionary_full_content` FROM cspro_dictionaries` ";
 $stm .= " WHERE `dictionary_name` = '" . $this->dictionaryName . "'";
 
 // ... insertion dans cspro_meta
 }
}

Code Après (PostgreSQL)

private function createDictionarySchema($processCasesOptions) {
 $bind = [];
 try {
 $dictionarySchema = new MySQLDictionarySchemaGenerator($this->logger);
 
 $processCasesOptions = $this->getProcessCaseOptions();
 $schema = $dictionarySchema->generateDictionary($this->dictionary, $processCasesOptions);
 
 $dictionarySQL = $schema->toSql($this->conn->getDatabasePlatform());
 $explodedDictionarySQL = implode(";" . PHP_EOL, $dictionarySQL);
 $this->logger->debug("writing schema SQL " . $explodedDictionarySQL);
 
 // PostgreSQL: Exécuter chaque statement séparément foreach($dictionarySQL AS $oneDictionarySQL){
 $this->conn->prepare($oneDictionarySQL)->execute();
 }
 
 //insert into cspro_meta dictionary_information
 $dictionaryVersion = $this->dictionary->getVersion();
 
 //Récupération du label du dictionnaire pour synchronisation
 $dictionaryLabel = str_replace(" ", "_", str_replace("_DICT", "", $this->dictionary->getName()));
 
 $stm = "SELECT modified_time, `dictionary_full_content` FROM cspro_dictionaries` "
 . " WHERE `dictionary_name` = '" . $this->dictionaryName . "'";
 
 $result = $this->pdo->fetchOne($stm);
 if ($result) {
 $stm = "INSERT INTO `cspro_meta`(`cspro_version`, `dictionary`, `source_modified_time`) "
 . " VALUES (:version, :dictionary, :source_modified_time)";
 $bind['version'] = $dictionaryVersion;
 $bind['dictionary'] = $result['dictionary_full_content'];
 $bind['source_modified_time'] = $result['modified_time'];
 $stmt = $this->conn->executeUpdate($stm, $bind);
 }
 } catch (\Exception $e) {
 // ... error handling
 }
}

Amélioration PostgreSQL :

foreach($dictionarySQL AS $oneDictionarySQL){
 $this->conn->prepare($oneDictionarySQL)->execute();
}

Pourquoi ? PostgreSQL n'accepte pas les multi-statements séparés par ; dans un seul execute(). Il faut exécuter chaque statement individuellement.


Étape 3: Transformation des Scripts

A. Transformation generateDictionary()

Objectif : Générer le schéma complet d'un dictionnaire (tables hierarchiques).

Code Avant (MySQL)

public function generateDictionary(Dictionary $dictionary, $processCasesOptions) {
 DictionarySchemaHelper::updateProcessCasesOptions($dictionary, $processCasesOptions);
 
 $this->schema = new Schema();
 $this->createDefaultTables();
 $parentLevel = null;
 
 //TODO check for Charset | Collation | comment for ($iLevel = 0; $iLevel < (is_countable($dictionary->getLevels()) ? count($dictionary->getLevels()) : 0); $iLevel++) {
 $level = $dictionary->getLevels()[$iLevel];
 $level->setLevelNumber($iLevel);
 $this->generateLevel($level, $parentLevel);
 $parentLevel = $dictionary->getLevels()[$iLevel];
 }
 
 return $this->schema;
}

Code Après (PostgreSQL Compatible)

public function generateDictionary(Dictionary $dictionary, $processCasesOptions) {
 DictionarySchemaHelper::updateProcessCasesOptions($dictionary, $processCasesOptions);
 
 //Récupérer le label du dictionnaire pour synchronisation données
 $this->nomSchema = str_replace(" ", "_",
 str_replace("_DICT", "", $dictionary->getName()));
 
 $this->schema = new Schema();
 $this->createDefaultTables();
 $parentLevel = null;
 
 //TODO check for Charset | Collation | comment for ($iLevel = 0; $iLevel < (is_countable($dictionary->getLevels()) ?
 count($dictionary->getLevels()) : 0); $iLevel++) {
 
 $level = $dictionary->getLevels()[$iLevel];
 $level->setLevelNumber($iLevel);
 $this->generateLevel($level, $parentLevel);
 $parentLevel = $dictionary->getLevels()[$iLevel];
 }
 
 return $this->schema;
}

Clé : Ajout de $this->nomSchema pour stocker le nom du schéma PostgreSQL (basé sur le label du dictionnaire).


B. Transformation createDefaultTables()

Objectif : Créer les 4 tables système : cases, notes, cspro_jobs, cspro_meta.

Structure Complète (PostgreSQL)

Table cases
public function createDefaultTables()
{
 //cases
 /* "CREATE TABLE cases ("
 "id TEXT NOT NULL,"
 "`key` TEXT NOT NULL,"
 "label TEXT,"
 "questionnaire TEXT NOT NULL,"
 "last_modified_revision INTEGER NOT NULL,"
 "deleted INTEGER NOT NULL DEFAULT 0,"
 "verified INTEGER NOT NULL DEFAULT 0,"
 "partial_save_mode TEXT NULL,"
 "partial_save_field_name TEXT NULL,"
 "partial_save_level_key TEXT NULL,"
 "partial_save_record_occurrence INTEGER NULL,"
 "partial_save_item_occurrence INTEGER NULL,"
 "partial_save_subitem_occurrence INTEGER NULL,"
 "FOREIGN KEY(last_modified_revision) REFERENCES file_revisions(id)"
 ");\\n" */
 
 $casesTable = $this->schema->createTable($this->nomSchema."_cases");
 $casesTable->addColumn("id", "text", ["notnull" => true]);
 $casesTable->addColumn("key", "text", ["notnull" => true]);
 $casesTable->addColumn("label", "text");
 $casesTable->addColumn("questionnaire", "text", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("last_modified_revision", "integer", ["notnull" => true]);
 $casesTable->addColumn("deleted", "integer", ["notnull" => true, "default" => 0]);
 $casesTable->addColumn("verified", "integer", ["notnull" => true, "default" => 0]);
 $casesTable->addColumn("partial_save_mode", "text", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("partial_save_field_name", "text", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("partial_save_level_key", "text", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("partial_save_record_occurrence", "integer", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("partial_save_item_occurrence", "integer", ["notnull" => false, "default" => null]);
 $casesTable->addColumn("partial_save_subitem_occurrence", "integer", ["notnull" => false, "default" => null]);
 
 $casesTable->addUniqueIndex(["`id`"], null, ["lengths" => [191]]);
 $casesTable->addIndex(["deleted"]);
 
 //notes
 // ...
}

Différences Clés MySQL vs PostgreSQL :

MySQLPostgreSQL
Backticks `column`Guillemets doubles "column"
VARCHAR(191)TEXT (pas de limite)
AUTO_INCREMENTSERIAL ou IDENTITY
ENGINE=InnoDBPas nécessaire (défaut)
Table notes
//notes
/* "CREATE TABLE notes ("
 "case_id TEXT NOT NULL,"
 "field_name TEXT NOT NULL,"
 "level_key TEXT NOT NULL,"
 "record_occurrence INTEGER NOT NULL,"
 "item_occurrence INTEGER NOT NULL,"
 "subitem_occurrence INTEGER NOT NULL,"
 "content TEXT NOT NULL,"
 "operator_id TEXT NOT NULL,"
 "modified_time INTEGER NOT NULL,"
 "FOREIGN KEY(case_id) REFERENCES cases(id)"
 ");\\n"
 "CREATE INDEX `notes-case-id` ON notes(case_id);"; */
 
$notesTable = $this->schema->createTable($this->nomSchema."_notes");
$notesTable->addColumn("case_id", "text", ["notnull" => true]);
$notesTable->addColumn("field_name", "text", ["notnull" => true]);
$notesTable->addColumn("level_key", "text", ["notnull" => true]);
$notesTable->addColumn("record_occurrence", "integer", ["notnull" => true]);
$notesTable->addColumn("item_occurrence", "integer", ["notnull" => true]);
$notesTable->addColumn("subitem_occurrence", "integer", ["notnull" => true]);
$notesTable->addColumn("content", "text", ["notnull" => true]);
$notesTable->addColumn("operator_id", "text", ["notnull" => true]);
$notesTable->addColumn("`modified_time`", "datetime",
 ['columnDefinition' => 'timestamp default current_timestamp']);
 
$notesTable->addIndex(["case_id"], null, [], ["lengths" => [191]]);
 
//DBAL has issues with creating foreign key constraint on text columns with lengths.
//not adding for now, if needed add it in the future
// $notesTable->addForeignKeyConstraint($this->quoteString('cases'),
// array($this->quoteString('case_id')),
// array($this->quoteString('id')),
// array("lengths" => array(191,191)), 'notes_cases_fk');
Table cspro_jobs
/* CREATE TABLE IF NOT EXISTS `cspro_jobs` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `startCaseId` int unsigned NOT NULL,
 `startRevision` int unsigned NOT NULL,
 `endCaseId` int unsigned NOT NULL,
 `endRevision` int unsigned NOT NULL,
 `casesProcessed` int unsigned NULL,
 `created_time` timestamp NOT NULL NULL DEFAULT CURRENT_TIMESTAMP,
 `modified_time` timestamp NOT NULL NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
*/
 
$jobsTable = $this->schema->createTable($this->nomSchema."_cspro_jobs");
$jobsTable->addColumn("`id`", "integer", ["unsigned" => true, "notnull" => true, "autoincrement" => true]);
$jobsTable->addColumn("`start_caseid`", "integer", ["unsigned" => true, "notnull" => true]);
$jobsTable->addColumn("`start_revision`", "integer", ["unsigned" => true, "notnull" => true]);
$jobsTable->addColumn("`end_caseid`", "integer", ["unsigned" => true, "notnull" => true]);
$jobsTable->addColumn("`end_revision`", "integer", ["unsigned" => true, "notnull" => true]);
$jobsTable->addColumn("`cases_to_process`", "integer", ["unsigned" => true, "notnull" => false, "default" => null]);
$jobsTable->addColumn("`cases_processed`", "integer", ["unsigned" => true, "notnull" => false, "default" => null]);
$jobsTable->addColumn("`status`", "integer", ["unsigned" => true, "notnull" => true, "default" => 0]);
$jobsTable->addColumn("`created_time`", "datetime",
 ['columnDefinition' => 'timestamp default current_timestamp']);
$jobsTable->addColumn("`modified_time`", "datetime",
 ['columnDefinition' => 'timestamp default current_timestamp on update current_timestamp']);
 
$jobsTable->setPrimaryKey(["`id`"]);
Table cspro_meta
//Create meta table
$metaTable = $this->schema->createTable($this->nomSchema."_cspro_meta");
$metaTable->addColumn("`id`", "integer", ["unsigned" => true, "notnull" => true, "autoincrement" => true]);
$metaTable->addColumn("`cspro_version`", "text", ["notnull" => true]);
$metaTable->addColumn("`dictionary`", "text", ["notnull" => true]);
$metaTable->addColumn("`source_modified_time`", "datetime", ["default" => null]);
$metaTable->addColumn("`created_time`", "datetime",
 ['columnDefinition' => 'timestamp default current_timestamp']);
$metaTable->addColumn("`modified_time`", "datetime",
 ['columnDefinition' => 'timestamp default current_timestamp on update current_timestamp']);
 
$metaTable->setPrimaryKey(["`id`"]);

Étape 4: Génération des Schémas PostgreSQL

Nomenclature des Tables

Pattern :

{nom_schema}_{table}

Exemples :

  • Dictionnaire : EVAL_PRODUCTEURS_USAID
  • Schéma : eval_producteurs (minuscules, _DICT supprimé)
  • Tables générées :
  • eval_producteurs_cases
  • eval_producteurs_notes
  • eval_producteurs_cspro_jobs
  • eval_producteurs_cspro_meta
  • eval_producteurs_producteurs (level 1)
  • eval_producteurs_observations (level 2)
  • eval_producteurs_photos (level 3)

Reproduire sur CSWeb Vanilla

Prérequis

  • CSWeb vanilla installé (version 7.7+)
  • PostgreSQL 12+ installé et accessible
  • PHP 8.0+ avec extension pdo_pgsql activée
  • Composer installé
  • Accès SSH au serveur CSWeb

Étapes de Transformation

1. Installer l'Extension pdo_pgsql

Sur Ubuntu/Debian :

# Installer pdo_pgsql
sudo apt-get update
sudo apt-get install php8.1-pgsql
 
# Vérifier
php -m | grep pdo_pgsql
# Sortie attendue: pdo_pgsql
 
# Redémarrer Apache/PHP-FPM
sudo systemctl restart apache2
# ou
sudo systemctl restart php8.1-fpm

Sur Docker (Dockerfile) :

FROM php:8.1-apache
 
# Installer pdo_pgsql
RUN apt-get update && apt-get install -y libpq-dev \\
 && docker-php-ext-install pdo_pgsql pgsql
 
# Vérifier
RUN php -m | grep pdo_pgsql

2. Modifier les Fichiers PHP (Selon Document PDF)

Fichier 1 : src/AppBundle/Service/DictionarySchemaHelper.php

Ajouter le support pdo_pgsql comme montré dans Étape 1.

Fichier 2 : src/AppBundle/Service/DataSettings.php

Ajouter les getters pour configuration breakout PostgreSQL.

Fichier 3 : src/AppBundle/Repository/MapDataRepository.php

Modifier le constructor pour accepter PostgreSQL.

Fichier 4 : src/AppBundle/CSPro/DictionarySchemaGenerator/MySQLDictionarySchemaGenerator.php

C'est le fichier le plus critique. Appliquer toutes les transformations de Étape 2 et Étape 3.


3. Configurer .env pour PostgreSQL

# Créer/Éditer .env
vim /var/www/csweb/.env
 
# Ajouter configuration breakout
BREAKOUT_DB_TYPE=pgsql
BREAKOUT_DB_HOST=localhost
BREAKOUT_DB_PORT=5432
BREAKOUT_DB_NAME=csweb_analytics
BREAKOUT_DB_USER=csweb_user
BREAKOUT_DB_PASSWORD=STRONG_PASSWORD_HERE
BREAKOUT_DB_SCHEMA=public

4. Créer la Base PostgreSQL

# Se connecter à PostgreSQL
sudo -u postgres psql
 
# Créer database
CREATE DATABASE csweb_analytics;
 
# Créer user
CREATE USER csweb_user WITH PASSWORD 'STRONG_PASSWORD_HERE';
 
# Accorder privilèges
GRANT ALL PRIVILEGES ON DATABASE csweb_analytics TO csweb_user;
 
# Sortir
\\q

5. Tester le Breakout PostgreSQL

# Se connecter au container/serveur CSWeb
cd /var/www/csweb
 
# Lancer breakout vers PostgreSQL
php bin/console csweb:process-cases-by-dict EVAL_PRODUCTEURS_USAID
 
# Vérifier les tables créées
psql -U csweb_user -d csweb_analytics -c "\\dt"
 
# Résultat attendu:
# eval_producteurs_cases
# eval_producteurs_notes
# eval_producteurs_producteurs
# eval_producteurs_observations
# ...

6. Vérifier les Données

-- Compter les cases
SELECT COUNT(*) FROM eval_producteurs_cases;
 
-- Voir échantillon producteurs
SELECT * FROM eval_producteurs_producteurs LIMIT 10;
 
-- Vérifier les métadonnées
SELECT * FROM eval_producteurs_cspro_meta;

Références Techniques

Document Source

DOC-20251121-WA0004.pdf (48 pages) Auteur : Assietou DIAGNE (ANSD, Sénégal) Date : 21 Novembre 2025

Code PHP Complet Le code complet de transformation est disponible dans le repository CSWeb Community v2.0 :

# Cloner le repository
git clone https://github.com/BOUNADRAME/csweb-community.git
 
# Fichiers clés transformés
src/AppBundle/CSPro/DictionarySchemaGenerator/MySQLDictionarySchemaGenerator.php
src/AppBundle/Service/DictionarySchemaHelper.php
src/AppBundle/Service/DataSettings.php
src/AppBundle/Repository/MapDataRepository.php

Ressources Additionnelles


Contact & Support

Questions Techniques Pour toute question technique sur la transformation PostgreSQL :

Assietou DIAGNE Email : siatou.sissi@gmail.com LinkedIn : Assiétou Diagne (opens in a new tab) Organisation : ANSD (Sénégal)

Contribution Vous avez amélioré le code ou trouvé des bugs ?

  1. Fork le repository CSWeb Community
  2. Créer une branche (git checkout -b feature/amelioration-pgsql)
  3. Commit vos changements
  4. Push et créer une Pull Request

Repository GitHub (opens in a new tab)


Conclusion

Impact de cette Transformation

Le travail technique d'Assietou DIAGNE a permis de :

  • Démocratiser PostgreSQL pour les instituts statistiques africains
  • Améliorer les performances de breakout (10x plus rapide sur grands volumes)
  • Rendre possible l'analytics moderne (Power BI, Tableau, Metabase)
  • Poser les bases de l'architecture flexible CSWeb Community v2.0

Ce guide est un hommage technique au travail d'Assietou. Merci pour cette contribution majeure !


Prochaines Étapes :


Documentation créée par Bouna DRAME basée sur le travail technique d'Assietou DIAGNE © 2026