web-dev-qa-db-fra.com

Importez le fichier sql dans node.js et exécutez-le contre PostgreSQL

Je cherche un moyen efficace de prendre un fichier SQL brut et de l'exécuter de manière synchrone sur une base de données PostgreSQL, comme si vous l'avez exécuté via psql.

J'ai un fichier sql qui crée toutes les bases de données, importe les données, etc. J'ai besoin de l'exécuter en utilisant node.js mais je ne trouve aucun module qui le fasse automatiquement. Pour l'application node.js elle-même, nous utilisons node-postgres ('pg'), knex.js et bookshelf.js. Je suppose cependant que pg est le meilleur pour cela.

Une alternative à laquelle je peux penser est de lire le fichier complet, de le diviser par des points-virgules, de remplacer les sauts de ligne par des espaces, de découper tout espace en double, puis de l'introduire dans pg un par un de manière à ce qu'ils soient exécutés séquentiellement, pas de manière asynchrone. Je suis un peu surpris si c'est vraiment le moyen le plus efficace et aussi si aucune bibliothèque n'existe encore pour résoudre ce problème. J'hésite un peu à y plonger car la syntaxe SQL peut elle-même être un peu difficile et je pourrais accidentellement l'écraser.

Quelques précisions à l'avance:

  • psql ne peut pas être utilisé car il n'est pas installé sur la machine cible
  • J'ai choisi de développer et de contrôler à la source les instructions SQL sous forme native SQL, car il est beaucoup plus facile pour un DBA de l'utiliser et de le manipuler.
22
rgareth

J'ai écrit la fonction suivante qui fonctionne pour mon cas. Cela aurait été beaucoup plus simple s'il n'y avait pas eu:

  • Utilisation de batch pour gérer la concurrence
  • Avoir le cas délicat de COPY PostgreSQL à considérer

Extrait de code:

function processSQLFile(fileName) {

  // Extract SQL queries from files. Assumes no ';' in the fileNames
  var queries = fs.readFileSync(fileName).toString()
    .replace(/(\r\n|\n|\r)/gm," ") // remove newlines
    .replace(/\s+/g, ' ') // excess white space
    .split(";") // split into all statements
    .map(Function.prototype.call, String.prototype.trim)
    .filter(function(el) {return el.length != 0}); // remove any empty ones

  // Execute each SQL query sequentially
  queries.forEach(function(query) {
    batch.Push(function(done) {
      if (query.indexOf("COPY") === 0) { // COPY - needs special treatment
        var regexp = /COPY\ (.*)\ FROM\ (.*)\ DELIMITERS/gmi;
        var matches = regexp.exec(query);
        var table = matches[1];
        var fileName = matches[2];
        var copyString = "COPY " + table + " FROM STDIN DELIMITERS ',' CSV HEADER";
        var stream = client.copyFrom(copyString);
        stream.on('close', function () {
          done();
        });
        var csvFile = __dirname + '/' + fileName;
        var str = fs.readFileSync(csvFile);
        stream.write(str);
        stream.end();
      } else { // Other queries don't need special treatment
        client.query(query, function(result) {
          done();
        });
      }
    });
  });
}

Sachez que cela échouerait si vous utilisiez des points-virgules ailleurs que pour terminer les instructions SQL.

11
rgareth

Vous pouvez simplement séparer les requêtes conséquentes par un point-virgule lors de leur transmission à client.query

Ça marche:

var pg = require('pg');

pg.connect('postgres://test:test@localhost/test', function(err, client, done){
        client.query('CREATE TABLE test (test VARCHAR(255)); INSERT INTO test VALUES(\'test\') ');
        done();
});

Et par conséquent, cela fonctionne aussi:

var pg = require('pg');
var fs = require('fs');

var sql = fs.readFileSync('init_database.sql').toString();

pg.connect('postgres://test:test@localhost/test', function(err, client, done){
    if(err){
        console.log('error: ', err);
        process.exit(1);
    }
    client.query(sql, function(err, result){
        done();
        if(err){
            console.log('error: ', err);
            process.exit(1);
        }
        process.exit(0);
    });
});
35
OhJeez