Skip to content

Flux de données : import / export / sauvegarde / migration

SkylerX centralise tous les chemins « les données entrent / sortent de la base » dans un ensemble cohérent de dialogues, le tout passant par un SaveFileDialog personnalisé (cohérent multiplateforme, sans dialogue système natif) et un parsing côté renderer (CSV/JSON/Excel traités en mémoire). Ce chapitre suit l'ordre « sortie → entrée → sauvegarde/restauration → migration inter-bases → dictionnaire de données → comparaison de données ».

1. Vue d'ensemble : ce que cette zone couvre

ScénarioEntréeDialogue / fonction principalFormats
Quelques lignes copiées à la voléeClic droit grille → "Copier en"ResultGrid.vue::copyRowsCSV / TSV / JSON / Markdown / SQL VALUES
Téléchargement d'une table / schema entierNavTree clic droit "Exporter SQL" → ExportOptionsDialogWorkspace.vue::doTableExport / doSchemaExportSQL (CREATE + INSERT)
Déménagement de tout l'espace de travailPalette act:export-conns / WorkspaceExportDialogWorkspaceExportDialog.vueJSON .skylerxws
Injecter CSV/JSON/Excel dans une tableNavTree clic droit "Importer données" → ImportDialogImportDialog.vue + io.tsCSV / TXT / JSON / NDJSON / XLSX
Coller directement Excel/Feishu Sheets⌘V dans la zone principale (ou PasteImportDialog)PasteImportDialog.vueTSV / CSV
Visualiser un fichier .ndjsonPalette act:ndjson-viewerNdjsonViewerDialog.vue.ndjson / .jsonl
Sauvegarde / restauration de basePalette act:backup:<id> (une par connexion)BackupRestoreDialog.vue.sql / .ndjson
Copier une table entre connexionsNavTree clic droit "Transfert de données"DataTransferDialog.vueSELECT par ligne → INSERT par batch
Générer un dictionnaire de donnéesNavTree clic droit schema/db → "Dictionnaire de données"Workspace.vue::genDataDict + dump.tsMarkdown / HTML
Comparer les données de deux tablesPalette act:data-diffDataDiffDialog.vue + data-diff.tsDiff par ligne → SQL de sync

Les IO fichier passent toutes par client.files (implémenté dans le main process : openText / saveText / listDir / commonDirs / mkdir). Le web ne dispose pas de listDir, fallback vers le download/upload navigateur (formats texte seulement).

2. Export

2.1 Copie multi-format depuis le résultat

ResultGrid.vue clic droit sur cellule / sélection, sous-menu "Copier en" :

ItemImplémentationUsage
CSVio.ts::toCSVColler directement dans Excel / Numbers
TSVio.ts::toTSVExcel / Notion / Feishu Sheets (séparateur \t)
JSONio.ts::toJSONPour code JSON.parse, Date en toISOString()
Markdownio.ts::toMarkdownColler dans documents / descriptions PR (échappe `
SQL VALUESio.ts::toSqlValuesList(1, 'a'), (2, 'b'), à coller dans INSERT...VALUES / VALUES (...) AS t / ON CONFLICT ... EXCLUDED
SQL INSERTio.ts::toInsertSqlINSERT INTO tbl (...) VALUES (...) une par ligne, exécutable

Détails de conversion de types (io.ts) :

  • null/undefined → vide (CSV) / NULL (SQL) ;
  • DatetoISOString() ;
  • number → tel quel, Infinity/NaN deviennent NULL en SQL ;
  • booleanTRUE/FALSE en SQL (attention : SQLite reconvertit en 1/0) ;
  • object/arrayJSON.stringify, entouré de guillemets simples en SQL ;
  • Le ' simple est doublé (a'b'a''b'), anti-injection.

CSV ajoute des guillemets si la cellule contient " / , / saut de ligne ; TSV pour \t / saut de ligne / ". Pas de guillemets systématiques, le collage Excel est plus propre.

2.2 ExportOptionsDialog — Export complet table / schema

NavTree clic droit table ou schema → "Exporter SQL", ouvre un mini-dialogue ExportOptionsDialog :

  • Structure seulementwithData = false, sort seulement CREATE TABLE ;
  • Structure + donnéeswithData = true, suivi de SELECT * FROM ref + liste d'INSERT.

À la réception de pick, Workspace.vue lance doTableExport / doSchemaExport :

  1. client.connections.metadata(... group: 'columns') extrait les colonnes ;

  2. dump.ts::buildCreateFromColumns reconstruit le CREATE TABLE depuis les métadonnées (v1 inclut PK, sans index ni FK — la syntaxe d'index varie trop entre dialectes, on assure d'abord la stabilité) ;

  3. Si withData, SELECT * FROM ref (sans pagination, pour grandes tables utilisez sauvegarde/migration) ;

  4. buildTableDump produit :

    sql
    -- Structure de la table
    CREATE TABLE `users` (...);
    
    -- Données (N lignes)
    INSERT INTO `users` (...) VALUES (...);
  5. Nom de fichier par défaut <nom_objet>.sql, extension fixe .sql, sauvegarde via client.files.saveText + SaveFileDialog personnalisé.

L'export schema itère sur les tables et ajoute un -- ws.dumpHeader { label, n } en en-tête.

2.3 Export complet du Workspace (.skylerxws)

WorkspaceExportDialog.vue couvre les scénarios "changement de machine" et "partage entre collègues". Structure du fichier :

ts
interface WorkspaceFile {
  version: 1
  exportedAt: number
  source: string                  // 'SkylerX'
  connections?: ConnectionConfig[]
  snippets?: typeof snippets
}

Options d'export (cochables indépendamment) :

OptionDéfautDescription
Inclure les connexionsVia client.connections.list(), anonymisé par défaut (sans mot de passe)
⚠ Inclure les mots de passeCoché → appel client.connections.get(id) ligne par ligne pour récupérer le texte clair. Le fichier devient portable entre machines — sans dépendre du keychain OS, au prix de mots de passe en clair, à utiliser avec précaution
Inclure les snippets SQLCopie JSON intégrale, sans modification d'ID

Nom par défaut skylerx-workspace-YYYY-MM-DD.skylerxws, accepte .skylerxws et .json.

À l'import, décompte "connexions + snippets" → double confirmation → fusion selon la stratégie de conflit :

  • skip : ignorer si même nom (défaut) ;
  • overwrite : même nameupdate avec l'id du doublon, écrase tous les champs (mots de passe inclus si présents) ;
  • rename : nouveau avec suffixe (importé) au name.

2.4 Export chiffré .sql.enc (AES-256-GCM + PBKDF2)

export-encrypt.ts fournit une API en fonctions pures, l'UI l'appelle selon le scénario (typique : exporter un dump SQL contenant des données sensibles pour un partenaire externe). Choix d'algorithmes :

ÉlémentValeurCompromis
Magic en-têteSKYLERX-ENC-v1Identification de version pour upgrade
KDFPBKDF2-HMAC-SHA-256Natif dans navigateur/Node, sans dépendance
ItérationsDEFAULT_ITER = 200_000OWASP 2023 recommande ≥ 600k, ici compromis pour vieilles machines, augmentable
AlgorithmeAES-GCMTag d'auth 128-bit intégré, mot de passe faux / fichier modifié → WRONG_PASSWORD
Longueur clé256 bitderiveKey produit AES-GCM 256
Salt16 octets aléatoiresRegénéré à chaque fois, jamais réutilisé
IV16 octets aléatoiresRegénéré à chaque fois, jamais réutilisé
SérialisationJSON une ligneLecture/écriture streaming, .sql.enc lisible dans un éditeur texte

Format disque (JSON une ligne) :

json
{ "magic": "SKYLERX-ENC-v1", "salt": "<b64>", "iv": "<b64>", "iter": 200000, "data": "<b64-cipher+tag>" }

Détails d'implémentation :

  • Utilise globalThis.crypto.subtle, sans dépendance tierce ; vieux Node sans subtle → exception directe pour forcer la mise à jour ;
  • Uint8Array toujours initialisé via new ArrayBuffer(n), contourne l'erreur de typage TS 5.7 + lib.dom qui durcit BufferSource sur ArrayBuffer ;
  • Encodage base64 par blocs de 32 KB, évite String.fromCharCode(...bytes) qui explose la pile sur grands fichiers ;
  • L'échec de vérification GCM est traduit uniformément en WRONG_PASSWORD, sans révéler l'OperationError brut, anti side-channel.

3. Import

3.1 ImportDialog — Assistant 3 étapes universel CSV / JSON / NDJSON / Excel

NavTree clic droit table → "Importer données", ImportDialog.vue est un assistant en 3 étapes (step: 'pick' | 'map' | 'run') :

Étape 1 : Choisir le fichier

  • Bouton principal "Sélectionner un fichier" → client.files.openText, filtre csv / txt / json (JSON détecté par \.json$/i ou premier caractère [/{, passe par parseJSON).
  • Bouton secondaire "Excel…" → <input type=file> côté renderer, lit l'ArrayBuffer puis import dynamique à la demande de xlsx (SheetJS). Ne lit que la première feuille, raw: false (valeurs affichées Excel, dates non converties en nombres), defval: ''. Le binaire ne passe pas par le canal texte, pas de blocage IPC sur gros fichiers.
  • Après parsing, aperçu des 5 premières lignes, case "Première ligne = en-têtes" ajustable manuellement.

io.ts::parseCSV est un state machine à la main : BOM, échappement "", CRLF / LF, virgules / sauts de ligne dans les guillemets. Filtre les lignes "vides" à champ unique.

io.ts::parseJSON accepte trois formes :

  • Tableau d'objets : union des clés en en-tête (ordre d'apparition) ;
  • Tableau de tableaux : première ligne = en-tête ;
  • Objet unique : traité comme 1 ligne.

Étape 2 : Mappage des champs + inférence de type

autoMap() apparie source/cible par "égalité exacte après minuscule". Chaque colonne a un dropdown manuel, "Ignorer" = -1.

Inférence de type inferType(srcIdx) échantillonne les 50 premières valeurs non vides de la colonne :

InférenceRegex
number/^-?\d+(\.\d+)?$/
date/^\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}(:\d{2}(\.\d+)?)?)?Z?$/i
boolean`/^(true
stringFallback

Présence de chaînes vides → marquage nullable, badge UI ·∅. Note : l'inférence est juste indicative, l'exécution insère en chaîne, le cast réel se fait côté DB selon la définition de colonne — tolère les différences de dialecte (MySQL '2024-01-01' → DATE auto, SQLite → TEXT).

Étape 3 : Options + exécution

OptionDéfautComportement
TRUNCATE avant importInsère un TRUNCATE TABLE <ref> avant les INSERT ; à utiliser avec prudence, non annulable (TRUNCATE est DDL en MySQL/PG)
Lignes par batch200 (min 1, max 2000)Contrôle le nombre de lignes par INSERT INTO t (...) VALUES (...), (...), ..., évite la troncature de très longues instructions par le driver

Exécution via client.connections.executeBatch, les chaînes vides ('') sont traitées comme NULL (io.ts::buildInsertStatements : s == null || s === '' ? 'NULL' : ...), donc l'import ne distingue pas "vraie chaîne vide" et "absence de valeur". Pour les cas stricts, passez par l'éditeur SQL manuellement.

3.2 PasteImportDialog — Insertion directe depuis le presse-papier

PasteImportDialog.vue est l'alternative légère à ImportDialog : à l'ouverture, navigator.clipboard.readText(), sans choix de fichier.

EntréeChemin de parsing
Contient \tTSV (format de copie par défaut d'Excel / Feishu Sheets) split par \t
Sans \tParsing CSV manuel simple (échappement "", mais pas les guillemets imbriqués complexes — basculez sur ImportDialog dans ces cas)

Les colonnes de la table cible viennent de information_schema.columns en direct (MySQL / MariaDB / OB / TiDB / Doris / StarRocks : table_schema + table_name ; PG / autres : table_name + table_catalog). Mappage auto par normalisation (toLowerCase + retrait _-espaces), le reste manuel, vide = ignorer.

Taille de batch fixe BATCH = 500, chaque batch une instruction INSERT INTO ... VALUES (...), (...) ; sqlLiteral simplifié : vide → NULL, numérique tel quel, le reste entre guillemets simples (' doublé). Redis / bases documents non SQL exclus (n'affiche que les connexions dialectKind === DbKind.Sql).

Cas d'usage : copier quelques dizaines à milliers de lignes depuis Feishu / Excel et coller. Pour plus, ImportDialog (executeBatch) ou DataTransferDialog (paginé).

4. Visualiseur NDJSON (NdjsonViewerDialog)

Palette act:ndjson-viewer → choisir .ndjson / .jsonl → visualisation tableau, sans connexion DB.

Règles de parsing (parse()) :

  • Split par ligne, lignes vides / erreurs de parsing → comptées dans skipped (sans bloquer) ;
  • Reconnaît le format dbgate Archives { __table, data } → ligne assignée à __table, données dans data ;
  • Reconnaît { __error: "..." } → compteur skipped++ ;
  • Autres lignes : JSON ordinaire, table = ''.

Fonctionnalités UI :

  • Onglets inter-tables : en haut, un onglet par __table rencontré, click pour ne voir que cette table ;
  • Union des colonnes : union des Object.keys des lignes visibles comme en-têtes (lignes avec champs manquants affichent null) ;
  • Détail de ligne : double-clic à droite / en bas développe le JSON complet ;
  • Copier intégral / Enregistrer sous : fichier entier dans le presse-papier, ou saveText sous (nom original conservé par défaut) ;
  • V1 lecture seule : pas d'édition ni de réimport en base, à venir.

5. Sauvegarde / restauration (BackupRestoreDialog)

Palette act:backup:<connId>BackupRestoreDialog. MVP en SQL pur : pas d'appel externe à mysqldump / pg_dump (détection de chemin cross-platform fastidieuse, peut être absent) ; à venir si besoin DDL complet (trigger / view / ordre FK) via IPC child_process.spawn.

Formats de sauvegarde

FormatImplémentationCaractéristiques
SQLRenvoie au NavTree clic droit "Exporter SQL" (réutilise doSchemaExport)Chemin traditionnel, directement consommable par mysql/psql
NDJSONdoBackupNdjson() natifFormat dbgate Archives, import/export cross-connexion facile

Flux NDJSON :

  1. metadata({ group: 'tables', path: [database] }) extrait toutes les base tables ;
  2. Pour chaque table SELECT * FROM <sqlName>, écrit {"__table":"t","data":{...}}\n par ligne ;
  3. Échec d'une table : ne s'arrête pas, écrit {"__table":"t","__error":"..."} (visible à la restauration) ;
  4. saveText vers skylerx-<nom_connexion>-<timestamp>.ndjson, filtre .ndjson / .jsonl ;
  5. Barre de progression (done / total · phase) + bouton "⏹ Arrêter" (stopRequested vérifié avant chaque table).

Limite connue : BLOB / Buffer via JSON.stringify deviennent { type: 'Buffer', data: [...] }, ne se reconvertissent pas en binaire à la restauration ; pour cas strict, utilisez le chemin SQL.

Flux de restauration

CheminFlux
SQLclient.files.openTextsplitStatements(content) par ; → double confirmation → execute séquentiel, échec d'une instruction n'interrompt pas, erreur dans restoreProgress.errors[] (200 premiers caractères)
NDJSONBucket par __tableun gros INSERT par bucket, internement chunks de chunkSize = 100 (évite max_allowed_packet) → collecte d'erreurs idem

UI : barre de progression temps réel + liste d'erreurs (tronquée + wrap) + toast 3 états restoreOk / restoreWithErrors / restoreStopped.

6. Migration entre connexions (DataTransferDialog)

NavTree clic droit table → "Transfert de données". Plus ciblé que sauvegarde/restauration : une table vers une table, on choisit la source et c'est parti, adapté à dev→staging.

ChampDéfautDescription
Connexion cibleConnexion couranteListe de toutes les connexions, suffixe (courante)
Database cibleCtx sourceSémantique varie par dialecte ; PG = catalog, MySQL = base
Schema cibleCtx sourcePG/KB obligatoire (défaut public), MySQL vide
Nom de table cibleNom sourceInexistant = échec d'insertion ; pas de création auto
Lignes par batch500Contrôle SELECT ... LIMIT ? OFFSET ? côté source
TRUNCATE cible d'abordExécute en fait DELETE FROM <ref> (pas TRUNCATE, transactable)

Boucle d'exécution :

ts
for (let page = 0; page < 100000; page++) {
  const res = await execute(srcId, `SELECT * FROM ${srcRef}`, [],
    { ..., limit: size, offset: page * size })
  if (!res.rows.length) break
  await executeBatch(tgtId, rowInserts(tgt.dialect, dstRef, cols, res.rows), dstOpts)
  if (res.rows.length < size) break    // arrêt anticipé
}
  • Max 100 000 pages = garde-fou anti-boucle infinie ;
  • Les noms de colonnes viennent du metadata source, donc la table cible doit avoir les mêmes noms de colonnes (l'ordre est libre, rowInserts les liste explicitement) ;
  • Conversion de types confiée à JS → SQL literal (io.ts::sqlLiteral) + cast implicite DB cible. Pour types complexes (Postgres jsonb, MySQL BIT(1)), risque de perte ; faire un spot-check après migration.

7. Export du dictionnaire de données (Markdown / HTML)

NavTree clic droit schema (ou db) → "Dictionnaire de données → Markdown / HTML". Workspace.vue::genDataDict appelle dump.ts::buildDataDictMarkdown / buildDataDictHtml.

Une section par table, colonnes fixes :

ChampTypeNullablePKDéfautCommentaire
idbigint unsignedN🔑Clé primaire utilisateur
emailvarchar(255)YNULLEmail

Source : metadata({ group: 'columns' }) retourne MetadataNode.detail.{dataType, nullable, primaryKey, defaultValue, comment}.

Différences Markdown vs HTML

DimensionMarkdownHTML
Échappement`|`, sauts de ligne → espaces
TOCAucune (utilisez l'outline IDE)TOC 3 colonnes, ancres #t-<urlencoded>
Mise en formeMarkdown pur<style> inline, sans-serif fixe, tableaux bordés, lignes paires/impaires, @media print anti coupure
UsageInclusion documents / Wiki / GitLabOuverture navigateur pour impression PDF

Nom du fichier <schema-or-db>-data-dict.md|html. Entièrement offline — le dictionnaire est un besoin fréquent en audit conformité, possible en environnement sans réseau.

8. Comparaison de données (DataDiffDialog)

Palette act:data-diff. Deux connexions × deux tables → diff par ligne → SQL de sync.

Algorithme cœur dans data-diff.ts::diffRows (fonction pure, testable) :

ts
diff = {
  inserts: Row[],            // Présent en source / absent en cible
  updates: RowUpdate[],      // Même PK, colonnes non-clé différentes
  deletes: Row[]             // Présent en cible / absent en source
}

Clés d'appariement (keyCols) :

  • Par défaut, clé primaire depuis information_schema.table_constraints + key_column_usage (SQL commun MySQL / PG) ;
  • L'utilisateur peut saisir / modifier keyColsInput (virgules) pour surcharger.

Comparaison de valeurs same(a, b) via normalisation chaîne : null/undefined = vide, sinon String(a) === String(b) — tolère les différences de driver (MySQL2 retourne BigInt, pg retourne Number, SQLite retourne string).

Matrice de support : familles MySQL (MySQL / MariaDB / OB) + PostgreSQL (PG / KingbaseES) seulement ; autres dialectes (SQLite / Oracle / SQL Server / Redis etc.) : UI affiche "MyPg seulement", bouton désactivé.

Résultats d'exécution :

IndicateurSens
insertsCompléter la cible avec la source
updatesAligner la cible sur la source (SET uniquement les colonnes différentes)
deletesLignes superflues sur cible, fin de script + commenté "Présent uniquement en cible ; à valider avant exécution", anti-suppression accidentelle

Final : generateDataSync produit un SQL lisible, à "Copier" ou "Ouvrir en requête", à exécuter sur la cible — donne une fenêtre de dry-run / human-review.

LIMIT (défaut 2000) anti-OOM, en cas de grosses différences de PK il faut restreindre d'abord.

9. Sécurité (résumé)

Détails dans Modèle de sécurité. Points clés de ce chapitre :

  • Workspace export sans mot de passe par défaut ; coché → JSON nu, UI affiche un "⚠" rouge explicite ;
  • .sql.enc chiffré en AES-256-GCM, mot de passe faux et fichier modifié donnent la même erreur, pas de side-channel ;
  • NDJSON backup ne masque pas ; le vrai masquage : générer avec PII Scanner ou écrire SELECT replace(...) manuellement ;
  • Les données temporaires d'import/export sont uniquement en mémoire, pas de fichier intermédiaire, libérées à la fermeture du dialogue.

10. Matrice de compatibilité

CapacitéFamille MySQLFamille PGSQLiteOracleSQL ServerDM / KingbaseESRedisMongoDB
Copier en CSV/TSV/JSON/MD
Copier en SQL VALUES/INSERT
Export SQL table/schema
Export .skylerxws complet
Export chiffré .sql.enc
ImportDialog (CSV/JSON/Excel)Via RedisImportExportVia NDJSON
Import presse-papier
Visualiseur NDJSONSans dépendance DBSans dépendance DB
Sauvegarde/restauration SQLPartiel
Sauvegarde/restauration NDJSON
Migration inter-connexion
Dictionnaire de données (MD/HTML)
Comparaison ligne + SQL sync✓ (KB)

"✗" = explicitement désactivé ; "—" = sans sens pour ce dialecte (KV / docs via RedisImportExportDialog dédié).

Aide-mémoire de déclenchement

ActionBarre d'outilsClic droitPalette ⌘KRaccourci
Copier résultat en CSV / TSV / ...Grille → Copier en → ...
Exporter table SQLNavTree table → Exporter SQL
Exporter Schema SQLNavTree schema → Exporter SQL
Exporter WorkspaceEngrenage haut → ExporterExporter Workspace (act:export-conns)
Importer WorkspaceEngrenage haut → ImporterImporter Workspace (act:import-conns)
Importer données (CSV/JSON/Excel)NavTree table → Importer données
Import presse-papierPasteImport (menu haut)
Visualiser NDJSONVisualiseur NDJSON (act:ndjson-viewer)
Sauvegarde / restaurationBackup/Restore · <connexion> (act:backup:<id>)
Transfert de donnéesNavTree table → Transfert de données
Dictionnaire de donnéesNavTree schema/db → Dictionnaire → MD / HTML
Comparaison de donnéesComparaison de données (act:data-diff)

Note : toute action "Enregistrer sous" passe par le même SaveFileDialog personnalisé (packages/ui/src/components/SaveFileDialog.vue) — cohérent macOS / Windows / Linux, sans dialogue natif ; supporte favoris, répertoires récents, ↑↓ pour parcourir, Enter pour valider, ⌘L pour focus barre d'adresse.

Apache License 2.0 · Wuhan Skyler Network Technology Co., Ltd.