hmm: Request Error

Hi Leute,

ich bekomme in meinem NodeJs/Javascript Projekt folgenden Fehler wenn ich Daten vom Client in der Serverseitigen sqlite3 DB speichern möchte:

Error: Request aborted
    at onaborted (/home/ubuntu/workspace/node_modules/express/lib/response.js:984:15)
    at Immediate._onImmediate (/home/ubuntu/workspace/node_modules/express/lib/response.js:1026:9)
    at processImmediate [as _immediateCallback] (timers.js:383:17)

Mein service:

app.post('/saveSkillData', function(req, res) {
    if(req.session.email) {     
        for(var i = 0; i < req.body.arr.length; i++) {
           var data = req.body.arr[i];
           if(data.skill.trim() != "" && data.subskill.trim() != "" 
                && data.name.trim() != "" && data.nivau.trim() != "") {
                dbManager.generateOneUpdate(req.session.email, data);
            }
       }
    }
});

Meine DB Methode:

this.generateOneUpdate = function(email, data) {
        var db = new sqlite3.Database(dbRef);
        
        var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
        db.run(statment);
        
        function callback(user_id, m_id, data) {
            var dbLokal = new sqlite3.Database(dbRef);
            var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
            statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
            dbLokal.run(statmentLokal);
            statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
            statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
            statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
            statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
            dbLokal.run(statmentLokal);
            statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
            statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
            statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
            statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
            dbLokal.run(statmentLokal);
            dbLokal.close();
        }
        
        statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
        statment = statment.replace(/\?name/g, "'" + data.name + "'");
        
        var d = db.all(statment, function(err, rows) {
            var lastIndex = rows.length - 1;
            callback(rows[lastIndex].user_id, rows[lastIndex].mitarbeiter_id, data);
        });
        
        db.close();
    }

Füge ich nur eine Sache hinzu so funktioniert alles, aber wenn ich zwei Sachen speichern möchte kommt es zu dem Fehler. Was hab ich falsch gemacht und wie mache ich es richtig? Leider kann ich kein ORM's verwenden.

akzeptierte Antworten

  1. Tach!

    Füge ich nur eine Sache hinzu so funktioniert alles, aber wenn ich zwei Sachen speichern möchte kommt es zu dem Fehler. Was hab ich falsch gemacht und wie mache ich es richtig?

    Du findest mittels Debugging die Ursache und beseitigst sie. Das ist das leidige Los des Programmierers.

    Wenn du die Ursache im DB-Teil vermutest, dann für den zum Fehlerfinden direkt aus, ohne den Umweg über den Request zu gehen.

    Was du aber ganz offensichtlich falsch machst: du verwendest nicht den Prepared-Statements-Machanismus von SQLite3, sondern hast anscheiend einen eigenen Nachbau erstellt, der zudem den Kontextwechsel nicht berücksichtigt. Wenn in deinen Daten ein ' vorkommt, bekommst du mindestens Syntaxfehler, und bei gezieltem Ausnutzen dieser Lücke auch größere Probleme.

    dedlfix.

    1. hm, ok, dann schreibe ich am besten erstmal einen passenden unit test, so wie ich es auch in java tun würde.

      hier die klasse die ich testen und aus dem test heraus debuggen möchte:

      /**
       *  Datenbank Manager.
       */
      function DbManager(dbPfadName) {
          
          var dbRef = dbPfadName + ".db";
          const sqlite3 = require('sqlite3').verbose();
          
          var SQL_CREATE_TABLE = "create table if not exists ? (produkt1 integer, produkt2 integer, produkt3 integer);";
          var SQL_INSERT_DATA = "insert into ? (produkt1, produkt2, produkt3) values (?1, ?2, ?3);";
          var SQL_SELECT_DATA = "select * from ?;";
          var SQL_SELECT_USER_MITARBEITER_ID = "select user_id, mitarbeiter_id from user, mitarbeiter where user.email = ?email and mitarbeiter.name = ?name;";
          var SQL_INSERT_MITARBEITER = "insert into mitarbeiter (name) select ?name where not exists (select 1 from mitarbeiter where name = ?name );";
          var SQL_INSERT_USER_MITARBEITER = "insert into user_mitarbeiter (user_id, mitarbeiter_id) select ?user_id, ?m_id where not exists (select 1 from user_mitarbeiter where user_id = ?user_id and mitarbeiter_id = ?m_id);";
          var SQL_INSERT_SKILL = "insert into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) select ?m_id, ?skill, ?subskill, ?nivau where not exists (select 1 from mitarbeiter_skill where mitarbeiter_id = ?m_id and skill = ?skill and subskill = ?subskill);";
          var SQL_REPLACE_SKILL = "replace into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) select ?m_id, ?skill, ?subskill, ?nivau where exists (select 1 from mitarbeiter_skill where mitarbeiter_id = ?m_id and skill = ?skill and subskill = ?subskill);";
          
          /**
           *  Sollte es keine Tabelle Namens tableName in der DB geben,
           *  so wird durch diese Funktion eine solche erstellt.
           */
          this.createTable = function(tableName) {
              var db = new sqlite3.Database(dbRef);
              var statment = SQL_CREATE_TABLE.replace("?", tableName);
              db.run(statment);
              db.close();
          }
          
          /**
           *  Generiert Querys die fuer das hinzufuegen von Daten notwendig sind
           *  und fuehrt diese aus.
           */
          this.generateOneUpdate = function(email, data) {
              var db = new sqlite3.Database(dbRef);
              
              var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
              db.run(statment);
              
              function callback(user_id, m_id, data) {
                  var dbLokal = new sqlite3.Database(dbRef);
                  var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                  statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                  dbLokal.run(statmentLokal);
                  statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                  statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                  statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                  statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                  dbLokal.run(statmentLokal);
                  statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                  statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                  statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                  statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                  dbLokal.run(statmentLokal);
                  dbLokal.close();
              }
              
              statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
              statment = statment.replace(/\?name/g, "'" + data.name + "'");
              
              var d = db.all(statment, function(err, rows) {
                  var lastIndex = rows.length - 1;
                  callback(rows[lastIndex].user_id, rows[lastIndex].mitarbeiter_id, data);
              });
              
              db.close();
          }
          
          /**
           *  Schreibt die Daten p1, p2, p3 in die Tabelle tableName.
           */
          this.insertData = function(tableName, p1, p2, p3) {
              var db = new sqlite3.Database(dbRef);
              var statment = SQL_INSERT_DATA.replace("?", tableName);
              statment = statment.replace("?1", p1);
              statment = statment.replace("?2", p2);
              statment = statment.replace("?3", p3);
              db.run(statment);
              db.close();
          }
          
          /**
           *  Fuehrt die Insert Query aus.
           */
          this.insertDataWithQuery = function(insertQuery) {
              var db = new sqlite3.Database(dbRef);
              db.run(insertQuery);
              db.close();
          }
          
          /**
           *  Fuehrt die Insert Query aus.
           */
          this.insertDataWithQuerys = function(insertQuerys) {
              var db = new sqlite3.Database(dbRef);
              for(var i = 0; i < insertQuerys.length; i++) {
                  db.run(insertQuerys[i]);
              }
              db.close();
          }
          
          /**
           *  Fuehrt das Delete Statment aus.
           */
          this.deleteStatment = function(statment) {
              var db = new sqlite3.Database(dbRef);
              db.run(statment);
              db.close();
          }
          
          /**
           *  Uebergibt alle Daten aus der Tabelle tableName an
           *  die Callback Funktion dataProcess.
           */
          this.selectData = function(tableName, dataProcess) {
              var db = new sqlite3.Database(dbRef);
              var statment = SQL_SELECT_DATA.replace("?", tableName);
              var data = db.all(statment, function(err, rows) {
                  dataProcess(rows);
              });
              db.close();
          }
          
          /**
           *  Uebergibt alle Daten aus der Tabelle tableName an
           *  die Callback Funktion dataProcess.
           */
          this.selectDataWithParam = function(query, dataProcess, param) {
              var db = new sqlite3.Database(dbRef);
              var data = db.all(query, function(err, rows) {
                  dataProcess(rows, param);
              });
              db.close();
          }
          
          /**
           *  Uebergibt alle Daten die sich aus der Query ergeben an
           *  die Callback Funktion dataProcess.
           */
          this.selectDataWithQuery = function(query, dataProcess) {
              var db = new sqlite3.Database(dbRef);
              var data = db.all(query, function(err, rows) {
                  dataProcess(rows);
              });
              db.close();
          }
      }
      
      module.exports = DbManager;
      

      welches unit test framework nimmt man für sowas? qunit? unit js? jasmine? ... es gibt extrem viele im netz, was ist der standart?

  2. Hallo hmm,

    ich kann gerade nicht sagen, wie dein Callstack mit deinem Code zusammenhängt. Ist timers.js von Dir? Ist dein Code überhaupt im Callstack? Entsteht das Problem überhaupt aus den DB-Zugriffen heraus?

    Gibt es einen node.js Debugger, mit dem Du auf if(req.session.email) einen Breakpoint setzen und von da aus lossteppen kannst?

    Wenn nicht - solltest Du pro Durchlauf den Wert von i und den verarbeiteten data Properties ausgeben. Es könnte auch hilfreich sein, das übersetzte SQL auszugeben und einen scharfen Blick darauf zu werfen.

    Rolf

    --
    Dosen sind silbern
    1. danke, habs analysiert.

      das hier geht so leider nicht:

       var d = db.all(statment, function(err, rows) {
                  var lastIndex = rows.length - 1;
                  callback(rows[lastIndex].user_id, rows[lastIndex].mitarbeiter_id, data, db);
              });
      

      das problem ist, dass "wärend" des selectstatments ein insert statment durchgeführt wird, was einen fehler zur folge hat.

      frage: ich brauche die daten die ich mir mit select hole um die querys zu bauen mit denen ich inserte. wie hole ich mir die select daten so dass es keine konflikte gibt?

      1. Tach!

        frage: ich brauche die daten die ich mir mit select hole um die querys zu bauen mit denen ich inserte. wie hole ich mir die select daten so dass es keine konflikte gibt?

        Erst abholen, dann durchlaufen und die Inserts ausführen.

        dedlfix.

        1. wie hole ich die daten ab? das script ist serverseitig und ich kenne bisher nur die möglichkeit selectdaten innerhalb einer callback funktion auszuführen

          1. Tach!

            wie hole ich die daten ab? das script ist serverseitig und ich kenne bisher nur die möglichkeit selectdaten innerhalb einer callback funktion auszuführen

            Genau dort steckst du sie in ein Array. Und wenn du das getan hast, dann gehts ans Durchlaufen. Innerhalb dieses Callbacks, denn erst wenn der aufgerufen wird, hast du ja die nötigen Daten.

            In der Praxis ist das Schachteln allen Codes in Callbacks unschön und so hat man die Promises erfunden, die eine angenehmere Codestruktur ermöglichen.

            dedlfix.

            1. wenn ich das so versuche:

                this.generateOneUpdate = function(email, data) {
                      var dbLokal = new sqlite3.Database(dbRef);
                      
                      var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                      dbLokal.run(statment);
                      console.log("generateOneUpdate");
                      function callback(user_id, m_id, data) {
                          console.log("callback");
                          //var dbLokal = new sqlite3.Database(dbRef);
                          var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                          statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);
                          //dbLokal.close();
                      }
                      
                      statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                      statment = statment.replace(/\?name/g, "'" + data.name + "'");
                      console.log("select:");
                      console.log(statment);
                      
                      dbLokal.run(statment, function(err, rows) {
                          console.log("test");
                          console.log(rows);
                          var array = rows;
                          
                          for(var i = 0; i < array.length; i++) {
                              console.log(array[i]);
                          }
                          
                          /*var lastIndex = rows.length - 1;
                          var user_id = rows[lastIndex].user_id;
                          var m_id = rows[lastIndex].mitarbeiter_id;*/
                          //callback(rows[lastIndex].user_id, rows[lastIndex].mitarbeiter_id, data);
                          
                          /*var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                          statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          console.log(statmentLokal + "\n");
                          dbLokal.run(statmentLokal);*/
                      });
                      
                      dbLokal.close();
                  }
              

              bekomme ich folgenden fehler:

              generateOneUpdate
              select:
              select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'a';
              generateOneUpdate
              select:
              select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
              generateOneUpdate
              select:
              select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
              generateOneUpdate
              select:
              select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Jens';
              generateOneUpdate
              select:
              select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'a';
              test
              undefined
              /home/ubuntu/workspace/node_modules/sqlite3/lib/trace.js:27
                                  throw err;
                                  ^
              
              TypeError: Cannot read property 'length' of undefined
                  at Statement.<anonymous> (/home/ubuntu/workspace/skillmatrixtool/js/database/dbManager.js:71:37)
              --> in Statement#run([Function])
                  at Database.<anonymous> (/home/ubuntu/workspace/node_modules/sqlite3/lib/sqlite3.js:75:19)
                  at Database.<anonymous> (/home/ubuntu/workspace/node_modules/sqlite3/lib/sqlite3.js:21:19)
                  at DbManager.generateOneUpdate (/home/ubuntu/workspace/skillmatrixtool/js/database/dbManager.js:66:17)
                  at /home/ubuntu/workspace/skillmatrixtool/js/view/services.js:59:27
                  at Layer.handle [as handle_request] (/home/ubuntu/workspace/node_modules/express/lib/router/layer.js:95:5)
                  at next (/home/ubuntu/workspace/node_modules/express/lib/router/route.js:137:13)
                  at Route.dispatch (/home/ubuntu/workspace/node_modules/express/lib/router/route.js:112:3)
                  at Layer.handle [as handle_request] (/home/ubuntu/workspace/node_modules/express/lib/router/layer.js:95:5)
              
            2. was hab ich hier mit den promise falsch gemacht:

                  this.generateOneUpdate = function(email, data) {
                      var db = new sqlite3.Database(dbRef);
                      var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                      db.run(statment);
                      
                      function callback(user_id, m_id, data, dbLokal) {
                          var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                          statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          dbLokal.run(statmentLokal);
                          statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                          statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                          statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                          statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                          dbLokal.run(statmentLokal);
                      }
                      
                      statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                      statment = statment.replace(/\?name/g, "'" + data.name + "'");
              
                      var test = new Promise(function(resolve, reject) {db.all(statment, function(err, rows) {
                          resolve(rows);
                      })});
                      
                      var pro = function () {
                          test.then(function (rows) {
                              var dbLokal = new sqlite3.Database(dbRef);
                              var last_index = rows.length - 1;
                              var user_id = rows[last_index].user_id;
                              var m_id = rows[last_index].mitarbeiter_id;
                              callback(user_id, m_id, data, dbLokal);
                              dbLokal.close();
                          })
                          .catch(function (error) {
                              console.log(error.message);
                          });
                      }
                      
                      pro();
                      db.close();
                  }
              

              ich krieg zwar in dem pro zeugs die daten, aber trotzdem bekomme ich zu 90% eine sqlite_busy: database is locked meldung...

              1. Tach!

                was hab ich hier mit den promise falsch gemacht:

                Ich muss dir konstatieren, dass du eine ganze Menge falsch machst. Zum einen scheint es mir, hast du die Arbeitsweise des sqlite3-Packages nicht verstanden. Wenn du dir auf der verlinkten Seite den Abschnitt Usage genau anschaust, solltest du das db.serialize() bemerken, in dem sämtliche Zugriffe eingebettet sind. Ein Blick in die Dokumentation steht da erstmal an, um aufzuklären, was es damit auf sich hat: Query serialization/parallelization.

                "node-sqlite3 provides two functions to help control the execution flow of statements. The default mode is to execute statements in parallel."

                Das heißt, dass dieses Paket ohne das serialize() asynchron arbeitet. Die Funktionen kommen sofort wieder zurück und nicht erst, wenn sie fertig sind. Damit hast du noch Querys offen, wenn du die nächsten abfeuerst. Es gibt Vorgänge, die sind exclusiv und können nicht parallel ausgeführt werden. Dazu klärt die SQLite-Dokumentation auf:

                "But only one process can be making changes to the database at any moment in time, however."

                Also muss das zweite Insert kaputtgehen, weil das erste noch im Paralleluniversum läuft.

                Schlussfolgerung: Serialize-Modus einschalten. Aber auch dann musst du immer noch aufpassen. Die Funktionen arbeiten weiterhin asynchron und kehren sofort zurück, ohne dass das Statement überhaupt angefangen wurde. Nur warten die Statements im Hintergrund in einer Warteschlage auf die Ausführung. Wenn du Code ausführen möchtest, der die Beendigung des Statements voraussetzt, muss du den in den jeweiligen Callback stecken.


                Und noch ein ganz wichtiger Punkt. Schau dir die Dokumentation des sqlite3-Paketes nochmal an, vor allem die Beispiele im Teil API Documentation → Database#run. Die Querys können als Prepared Statement formuliert werden. Es besteht keine Notwendigkeit, dass du mit Stringmanipulation

                        var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                

                rumhantieren musst. Das ist außerdem kaputt, so wie du das machst, weil das nicht beachtet, dass ' in den Daten vorkommen kann. Die Folge wären in dem Fall Syntaxfehler oder SQL-Injection. Und sowas merkt man natürlich erst im Produktivbetrieb, weil man in seinen Testdaten keine Hochkommas vorkommen hat.

                dedlfix.

                1. Hallo dedlfix,

                  also habe ich nach wie vor nur vom Tuten Ahnung gehabt, und konnte dazu ein bisschen auf dem Kamm blasen. Für ein Klarinettenkonzert hat's nicht gereicht…

                  Schlussfolgerung: Serialize-Modus einschalten. Aber auch dann musst du immer noch aufpassen. Die Funktionen arbeiten weiterhin asynchron und kehren sofort zurück...

                  D.h. man kommt nicht drumherum, die Dinge, die nacheinander ablaufen müssen, per Callback zu serialisieren?

                  Lösung für hmm müsste also sein, dass er INSERT_MITARBEITER und SELECT_USER_MITARBEITER nicht serialisieren muss, aber INSERT_USER_MITARBEITER, INSERT_SKILL und REPLACE_SKILL auf drei Callback Funktionen verteilt werden müssen?

                  Wobei ich ja ehrlicherweise die SQL Magic, die da ausgeübt wird, nicht verstanden habe. Beispiel: Skillniveaus.

                  -- INSERT_SKILL
                  INSERT INTO mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) 
                  SELECT ?m_id, ?skill, ?subskill, ?nivau
                  WHERE NOT EXISTS (SELECT 1 FROM mitarbeiter_skill 
                                    WHERE mitarbeiter_id = ?m_id and skill = ?skill and subskill = ?subskill)
                  
                  -- REPLACE_SKILL
                  REPLACE INTO mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) 
                  SELECT ?m_id, ?skill, ?subskill, ?nivau 
                  WHERE EXISTS (SELECT 1 FROM  mitarbeiter_skill 
                                WHERE mitarbeiter_id = ?m_id and skill = ?skill and subskill = ?subskill)
                  

                  Das erste Statement fügt einen neuen Satz mit ID, Skill, Subskill und Niveau ein, wenn ein Satz mit ID, SKILL und SUBSKILL noch nicht existiert. D.h. ein Update für bestehende Sätze erfolgt nicht.

                  Das zweite Statement macht einen REPLACE aus einem SELECT ... WHERE EXISTS. Das klingt nach komplexem Gewusel. Zuerst wird per EXISTS (SELECT 1...) geprüft, ob der Satz zu (id,skill,subskill) schon da ist. Wenn ja, DANN wird er replaced, d.h. gelöscht und neu angelegt.

                  Diese beiden Statements laufen parallel los und es ist klar, dass sie kollidieren. Aber wenn man sie serialisiert, machen sie dann überhaupt noch Sinn? Wenn der INSERT etwas einfügt, wird der REPLACE es definitiv gleich wieder löschen.

                  Hier steht, dass der Callback eines db.run() Aufrufs ein this bekommt, in dem die Anzahl der beeinflussten Sätze steht (this.changes). Es wäre aus meiner Sicht sinnvoller, zunächst einen einfachen Update auf's Niveau zu versuchen, und wenn im Callback dazu dann this.changes===0 ist, einen INSERT folgen zu lassen. Die "INSERT ... ON DUPLICATE KEY UPDATE..." Erweiterung aus MySQL scheint's in Sqlite ja nicht zu geben. Aber diese WHERE EXISTS Hexerei kommt mir doch so vor, als gehöre sie nach Hogwarts und nicht auf einen Node-Server.

                  Rolf

                  --
                  Dosen sind silbern
                  1. naja, die replace query ist aus meiner faulheit heraus entstanden. beide querys sollen eigendlich zusammen folgendes tun:

                    1. Fall: existiert keine zeile die die where klausel erfüllt, so wird eine hinzugefügt -> fertig oder 2. Fall: existiert so eine zeile bereits, so updaten wir die daten

                    hintergrund:

                    clientseitig existiert eine tabellenförmite darstellung die excel ähnelt. es kann eine zeile hinzugefügt werden und es kann eine zeile geändert werden. im anschluss klickt der anwender auf speichern, was zur folge hat, dass die daten an diese sql-methoden geschickt werden.

                    so funktioniert das übrigens auch nicht:

                        this.generateOneUpdate = function(email, data) {
                            var db = new sqlite3.Database(dbRef);
                            var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                            db.run(statment);
                            
                            function callback(user_id, m_id, data, dbLokal) {
                                console.log("test");
                                var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                                statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                                dbLokal.serialize(function() {
                                    dbLokal.run(statmentLokal);
                                });
                                statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                                statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                                statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                                statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                                dbLokal.serialize(function() {
                                    dbLokal.run(statmentLokal);
                                });
                                statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                                statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                                statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                                statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                                dbLokal.serialize(function() {
                                    dbLokal.run(statmentLokal);
                                });
                                console.log("end");
                            }
                            
                            statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                            statment = statment.replace(/\?name/g, "'" + data.name + "'");
                    
                            /*db.all(statment, function(err, rows) {
                                var dbLokal = new sqlite3.Database(dbRef);
                                dbLokal.serialize(function() {
                                    var last_index = rows.length - 1;
                                    var user_id = rows[last_index].user_id;
                                    var m_id = rows[last_index].mitarbeiter_id;
                                    callback(user_id, m_id, data, dbLokal);
                                });
                                dbLokal.close();
                            });*/
                    
                            var test = new Promise(function(resolve, reject) {db.all(statment, function(err, rows) {
                                resolve(rows);
                            })});
                            
                            var pro = function () {
                                test.then(function (rows) {
                                    console.log(rows);
                                    //var dbLokal = new sqlite3.Database(dbRef);
                                    var last_index = rows.length - 1;
                                    var user_id = rows[last_index].user_id;
                                    var m_id = rows[last_index].mitarbeiter_id;
                                   // db.serialize(function() {
                                        callback(user_id, m_id, data, db);
                                    //});
                                    console.log("lokales fertig");
                                    //dbLokal.close();
                                })
                                .catch(function (error) {
                                    console.log(error.message);
                                });
                            }
                            
                            db.serialize(function() {
                                pro();
                            });
                            
                            db.close();
                            console.log("fertig");
                        }
                    

                    das db.serialize(function() { pro(); });

                    gehört wohl wo anders hin....

                    1. Moin,

                      naja, die replace query ist aus meiner faulheit heraus entstanden. beide querys sollen eigendlich zusammen folgendes tun:

                      1. Fall: existiert keine zeile die die where klausel erfüllt, so wird eine hinzugefügt -> fertig oder 2. Fall: existiert so eine zeile bereits, so updaten wir die daten

                      genau dafür gibt es doch eine SQL-Lösung.

                      Viele Grüße
                      Robert

                      1. Hallo Robert,

                        wir reden hier aber nicht von MySQL.

                        Rolf

                        --
                        Dosen sind silbern
                        1. Hallo Rolf,

                          du darfst gerne ein Minus geben, wenn ich auf den richtigen Pfad zeige, der zum Ziel führen kann.

                          Viele Grüße
                          Robert (der Lösungen sucht)

                          1. Hallo Robert,

                            ich habe nur die Stackoverflow-Frage gelesen, da stand MySQL. Ich habe daher angenommen, dass Du Unsinn verlinkt hast und mein Minus gegeben. Da war ich zu schnell, bitte entschuldige.

                            Die Antwort weiter unten, die auf REPLACE verweist, ist allerdings falsch gewesen (da müsste ich im SO ein Minus geben), weil REPLACE nämlich KEIN Update macht wenn der INSERT schief geht. Laut SQLite Doku prüft REPLACE, ob ein INSERT einen Constraint verletzen würde, und wenn ja, macht er erstmal einen DELETE. Und dann einen INSERT. Das ist was anderes als der INSERT ON DUPLICATE UPDATE aus MYSQL (weil es aufwändiger ist und weil auto-IDs neu vergeben werden), und der REPLACE stand ja auch gerade deshalb in der Kritik.

                            Weswegen ich zu meinem Minus stehe. Fachlich nicht hilfreich.

                            Rolf

                            --
                            Dosen sind silbern
                            1. Hallo Rolf,

                              also bei mir hat der Stackoverflow-Link mehrere Bildschirmseiten mit Diskussion, auch jenseits von INSERT OR REPLACE … hm?

                              Lesende Grüße
                              Robert

                              1. Hallo Robert,

                                aber nur eine Antwort (answered Nov 17 '16 at 7:11 Dawn Song), die sich auf SQLite bezieht. Oder verstehe ich Dich einfach miss ?

                                Rolf

                                --
                                Dosen sind silbern
                                1. Hallo Rolf,

                                  eine?

                                  Wie dem auch sei: Anscheinend gibt es INSERT OR UPDATE in SQLite leider nicht.

                                  Viele Grüße
                                  Robert

                                  1. Hallo Robert,

                                    was soll das jetzt? Wegen dieses Links hat es das Minus nicht gegeben. Den hast Du erst später nachgelegt.

                                    Anscheinend gibt es INSERT OR UPDATE in SQLite leider nicht.

                                    Was ich schon um 9:34 Uhr bemerkt hatte.

                                    Rolf

                                    1. Hallo Rolf,

                                      was soll das jetzt?

                                      was soll was jetzt? Ich stehe gerade auf dem Schlauch.

                                      Wegen dieses Links hat es das Minus nicht gegeben.

                                      ???

                                      Den hast Du erst später nachgelegt.

                                      Ja, und wo ist das Problem?

                                      Anscheinend gibt es INSERT OR UPDATE in SQLite leider nicht.

                                      Was ich schon um 9:34 Uhr bemerkt hatte.

                                      Es ist doch nicht verboten, auf einem anderen Weg zum gleichen Ergebnis zu kommen. Wie gesagt, sprich dich aus, man kann alles auch ohne - klären.

                                      Viele Grüße
                                      Robert

                                      1. Hallo Robert,

                                        ich versuch's nochmal…

                                        (Robert) genau dafür gibt es doch eine SQL-Lösung. (mit Link auf MySQL Thread)

                                        (Rolf) (gibt Minus) wir reden hier aber nicht von MySQL.

                                        (Robert) du darfst gerne ein Minus geben, wenn ich auf den richtigen Pfad zeige, der zum Ziel führen kann. (mit Link auf SQLite-Thread)

                                        (Rolf) (erläutert das Minus in Bezug auf den MySQL Link)

                                        (Robert) (spricht von Fundstellen im SO-Link ohne zu sagen welchen von beiden er meint)

                                        (Rolf) (findet nur eine, nicht hilfreiche, und meint den MySQL Link)

                                        (Robert) Eine? (mit Link auf den SQLite Thread)

                                        Drum gab's ein zweites Minus, und die Frage "Was soll das jetzt?". Das zweite Minus hab ich wieder weggenommen - da wir offenbar aneinander vorbeiredeten. Zum ersten stehe ich weiterhin, DIESER Beitrag ist nicht hilfreich.

                                        Vielleicht hast Du beim ersten Link eine falsche URL im Clipboard gehabt. Das Folgegespräch schien jedenfalls ein Verwirrspiel, und das war das Problem.

                                        Rolf

                                        --
                                        Dosen sind silbern
                    2. Hallo hmm,

                      ich habe schon verstanden, was Du erreichen willst. Nur halte ich deinen Weg für sehr umständlich, daher die Frage, ob es mit einer UPDATE/INSERT Sequenz nicht einfacher wäre.

                      Die dbLokal.run() Befehle einzeln in einen serialize zu packen, nützt definitiv nichts. Das habe ich so auch nicht vorgeschlagen. Und dedlfix meinte, dass serialize() nicht unbedingt nützt, weil die run() Befehle IMMER noch sofort zurückkehren.

                      dedlfix hat dich auch auf prepared statements hingewiesen - sqlite hat eine Syntax für einen transparenten Prepare. Die verwende ich nun.

                      Das Promise-Geraffel ist VIEL Boilerplate-Code, das gefällt mir nicht wirklich. Aber mit Promises geht's wohl nicht anders.

                      var SQL_INSERT_MITARBEITER = "insert into mitarbeiter (name) select $name where not exists (select 1 from mitarbeiter where name = $name );";
                      
                      var SQL_SELECT_USER_MITARBEITER_ID = "select user_id, mitarbeiter_id from user, mitarbeiter where user.email = ?email and mitarbeiter.name = $name;";
                      
                      var SQL_INSERT_USER_MITARBEITER = "insert into user_mitarbeiter (user_id, mitarbeiter_id) select $user_id, $m_id where not exists (select 1 from user_mitarbeiter where user_id = $user_id and mitarbeiter_id = $m_id);";
                      
                      var SQL_INSERT_SKILL = "insert into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) values ($m_id, $skill, $subskill, $nivau);";
                      var SQL_UPDATE_SKILL = "update mitarbeiter_skill set niveau=$niveau where mitarbeiter_id = $m_id and skill = $skill and subskill = $subskill;";
                      
                      this.generateOneUpdate = function(email, data) {
                         var db = new sqlite3.Database(dbRef);
                      
                         new Promise(function(resolve, reject) {
                            db.run(SQL_INSERT_MITARBEITER,
                                   { name: data.name },
                                   function(err) {
                                      if (err)
                                         // todo: Setze sinnvolle Message
                                         reject({ success: false, dbError: err, msg: "msg1" })
                                      else
                                         resolve();
                                   });
                         })
                         .then(function() {
                            return new Promise(function(resolve,reject) {
                               db.get(SQL_SELECT_USER_MITARBEITER_ID,
                                      { email: email, name: data.name },
                                      function(err, row) {
                                         if (err)
                                            reject({ success: false, dbError: err, msg: "msg2" });
                                         else
                                            resolve(row);
                                      });
                            });
                         })
                         .then(function(umaRow) {
                            if (umaRow == null) {
                               // Deine Entscheidung, ob das ein Fehler ist. Wenn ja, msg setzen.
                               reject({success: ???});
                            } else {
                               return new Promise(function(resolve, reject) {
                                  db.run(SQL_INSERT_USER_MITARBEITER,
                                         { user_id: umaRow.user_id, m_id: umaRow.mitarbeiter_id },
                                         function(err) {
                                            if (err)
                                               reject({dbError: true, err: err, msg: "msg3"});
                                            else
                                               resolve();
                                         });
                               });
                         })
                         .then(...) // UPDATE-Versuch, reject({success:true}) wenn erfolgreich
                         .then(...) // INSERT
                         .catch(function(err) {
                            // Catch mit Success:true diente nur dem Abbruch der Sequenz
                            if (err.success) return;
                            if (err.dbError) {
                               // db-Fehler behandeln, err.msg protokollieren
                            } else if (err.msg) {
                               // msg ohne dbFehler, kann auch sein
                            } 
                         });
                      }
                      

                      Ich habe es nicht komplett aufgeschrieben, der Rest ist für dich zum Üben (um beim Fisch/Angel Modell zu bleiben: ich habe dir gezeigt wie man die Angel auswirft und der Fisch hat angebissen, nun nimm die Angel und hol den Fisch selbst ein.)

                      Gezeigt habe ich jedenfalls:

                      1. Wie verkettest Du Promises, wenn im then ein weiterer asynchroner Prozess gestartet werden muss
                      2. Wie brichst Du eine then-Kette ab, wenn eine Voraussetzung zur Fortsetzung nicht erfüllt ist
                      3. Wie vermeidest Du Deine .replace() Orgien

                      Und einen Hinweis auf eine bessere SQL Sequenz für die Skills hast Du auch.

                      Möglicherweise sind Syntaxfehler im Code, ich habe das geschrieben ohne es auf irgendeine Script-Engine loszulassen.

                      Und - äh, ja - es geht mir wir Dedlfix: Ich habe NUR Doku gelesen und wiedergegeben, was ich verstanden habe. Praxiserfahrung mit sqlite3 unter node habe ich nicht.

                      Hoffe, dass es nicht nur verwirrt hat...
                      Rolf

                      --
                      Dosen sind silbern
                      1. danke!

                        so gehts:

                            var SQL_INSERT_MITARBEITER = "insert into mitarbeiter (name) select $name where not exists (select 1 from mitarbeiter where name = $name );";
                            var SQL_SELECT_USER_MITARBEITER_ID = "select user_id, mitarbeiter_id from user, mitarbeiter where user.email = $email and mitarbeiter.name = $name;";
                            var SQL_INSERT_USER_MITARBEITER = "insert into user_mitarbeiter (user_id, mitarbeiter_id) select $user_id, $m_id where not exists (select 1 from user_mitarbeiter where user_id = $user_id and mitarbeiter_id = $m_id);";
                            var SQL_INSERT_SKILL = "insert into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) values ($m_id, $skill, $subskill, $nivau);";
                            var SQL_UPDATE_SKILL = "update mitarbeiter_skill set nivau=$nivau where mitarbeiter_id = $m_id and skill = $skill and subskill = $subskill;";
                            
                            this.generateOneUpdate = function(email, data) {
                               var db = new sqlite3.Database(dbRef);
                            
                               var pro = function() {
                                   new Promise(function(resolve, reject) {
                                
                                    var st = db.prepare(SQL_INSERT_MITARBEITER);
                                    
                                  st.run({ $name: data.name },
                                         function(err) {
                                            if (err) {
                                               reject({ success: false, dbError: err, msg: "msg1" });
                                            }
                                            else {
                                               resolve();
                                            }
                                         });
                                st.finalize();
                               })
                               .then(function() {
                                  return new Promise(function(resolve,reject) {
                                      
                                      var st = db.prepare(SQL_SELECT_USER_MITARBEITER_ID);
                                      
                                     st.get({ $email: email, $name: data.name },
                                            function(err, row) {
                                               if (err)
                                                  reject({ success: false, dbError: err, msg: "msg2" });
                                               else
                                                  resolve(row);
                                            });
                                    st.finalize();
                                  });
                               })
                               .then(function(umaRow) {
                                    if (umaRow == null) {
                                     reject({success: "msg2"});
                                    } else {
                                     return new Promise(function(resolve, reject) {
                                         
                                         var st = db.prepare(SQL_INSERT_USER_MITARBEITER);
                                         
                                        st.run({ $user_id: umaRow.user_id, $m_id: umaRow.mitarbeiter_id },
                                               function(err) {
                                                  if (err)
                                                     reject({dbError: true, err: err, msg: "msg3"});
                                                  else
                                                     resolve(umaRow);
                                               });
                                        st.finalize();
                                    });
                                    }
                               })
                               .then(function(umaRow) {
                                    if (umaRow == null) {
                                     reject({success: "msg4"});
                                    } else {
                                     return new Promise(function(resolve, reject) {
                                         
                                         var st = db.prepare(SQL_UPDATE_SKILL)
                                         
                                        st.run(
                                               { $m_id: umaRow.mitarbeiter_id, $skill: data.skill, $subskill: data.subskill, $nivau: data.nivau},
                                               function(err) {
                                                  if (err)
                                                     reject({dbError: true, err: err, msg: "msg5"});
                                                  else
                                                     resolve(umaRow);
                                               });
                                        st.finalize();
                                    });
                                    }
                               })
                               .then(function(umaRow) {
                                   if (umaRow == null) {
                                     reject({success: "msg6"});
                                    } else {
                                     return new Promise(function(resolve, reject) {
                                         
                                         var st = db.prepare(SQL_INSERT_SKILL);
                                         
                                        st.run(
                                               { $m_id: umaRow.mitarbeiter_id, $skill: data.skill, $subskill: data.subskill, $nivau: data.nivau },
                                               function(err) {
                                                  if (err)
                                                     reject({dbError: true, err: err, msg: "msg7"});
                                                  else
                                                     resolve(umaRow);
                                               });
                                        st.finalize();
                                    });
                                    }
                               })
                               .catch(function(err) {
                                  if (err.success) return;
                                  if (err.dbError) {}
                                  else if (err.msg) {} 
                               });
                               }
                               
                               pro();
                               db.close();
                            }
                        

                        ich werd mir jetzt das löschen vornehmen.

                        idee:

                        ich speichere alle mitarbeiter_ids die zur user_id gehören vor aufruf von generateoneUpdate in einem arrray old_m. im anschluss führe ich die funktion generateOneUpdate aus und speichere alle neuen mitarbeiter_ids in einem array aktuell_m. danach lösche ich alle daten die zu den mitarbeiter_ids aus old_m gehören und die nicht in aktuell_m vorkommen.

                        übrigens:

                        manchmal muss ich die seite zweimal laden damit mir die daten die ich haben will vom server aus der db ausgelesen und an den client geschickt wurden. woran liegt das?

                  2. Tach!

                    Vorab muss ich mal etwas disclaimern: Mit dem sqlite3-Package habe ich keine Erfahrungen, ich entnehme meine hier präsentierten Erkenntnisse allein aus der Dokumentation und den allgemeinen Erfahrungen mit asynchronen Abläufen aus anderen JavaScript-Projekten. Was ich aber in der Dokumentation lese, klingt verständlich und plausibel, so dass ich stark annehme, zu wissen worauf es ankommt, wenn ich selbst das Paket verwenden wollte.

                    Begrifflichkeiten:

                    Schlussfolgerung: Serialize-Modus einschalten. Aber auch dann musst du immer noch aufpassen. Die Funktionen arbeiten weiterhin asynchron und kehren sofort zurück...

                    D.h. man kommt nicht drumherum, die Dinge, die nacheinander ablaufen müssen, per Callback zu serialisieren?

                    Jein. Callbacks braucht man nur, wenn das Ergebnis der Aktion für den weiteren Verlauf wichtig ist. Ansonsten reicht es, die Aktionen im Serialize-Modus in die Warteschlange einzureihen und dann den Vorgang zu vergessen. Allerdings hat man unter Umständen ein Problem, wenn Fehler auftreten, denn die würden bei Verwendung eines Callback über diesen rückgemeldet. Ohne Callback würden sie aber geworfen werden, und das noch dazu zu einem unbestimmten Zeitpunkt. Ein try-catch um die Datenbankfunktionsaufrufe wird besonders die Exceptions der späten SQL-Commandos nicht fangen können, weil die Ausführung bereits an ganz anderer Stelle im Code angekommen ist.

                    Lösung für hmm müsste also sein, dass er INSERT_MITARBEITER und SELECT_USER_MITARBEITER nicht serialisieren muss, aber INSERT_USER_MITARBEITER, INSERT_SKILL und REPLACE_SKILL auf drei Callback Funktionen verteilt werden müssen?

                    Bei allen Inserts findet auf Seiten von SQLite ein Locking der gesamten Datenbank statt. Ob ein Select gefahrlos parallel abgeschickt werden kann und sich dann in eine SQLite-interne Warteschlange einreiht, weiß ich nicht. Ich weiß auch nicht, ob die im sqlite3-Package

                    Ich würde hier keinen Mischbetrieb von Database#serialize() und Database#parallelize() fahren wollen, weil mir das zu unübersichtlich wird und ich vermute, dass die Risiken den Vorteil der Parallelität nicht wert sind.

                    Ich kann mich noch nicht entscheiden, ob ich - jeweils innerhalb von Database#serialize() - eine Callback-Schlange aufbaue, in der sich sozusagen jedes Statement einreihen muss (vor allem, um Fehler direkt abfangen zu können) oder ob ich Statements ohne Ergebnis einfach so abschicken würde, und nur die, deren Ergebnis ich brauche, mit Callbacks versehe.

                    Definition Callback-Schlange: Jedes nachfolgende Statement muss innerhalb des Callbacks des vorhergehenden stattfinden.

                    Auf alle Fälle gibt das mit den Callbacks eine ziemliche Verschachtelung, die ich mit Promises entschärfen würde. Das sqlite3-Package hat keine eingebaute Promise-Chaining-Unterstützung, so dass man die zu verwendenden Funktionen selbst in jeweils einzelne Promises packen müsste. Dann kann statt Callback-Verschachtelung eine lineare .then()-Kette aufbauen.

                    Alles in allem nicht ganz einfach, aber die Kompliziertheit liegt hier in der Asynchronität des Geschehens. Generelles Wissen um Promises sind empfehlenswert. Sie sind hier zwar nicht nötig, und fügen einen weitere Komplexität hinzu, aber richtig angewendet kommt das der Lesbarkeit des Codes zugute.

                    let with = whatever;
                    do(something, with, function(with) {
                      do(something, with, function(with) {
                        do(something, with, function() {
                          ...
                        });
                      });
                    });
                    
                    let with = whatever;
                    do(something, with)
                    .then(function (with) {
                      return do(something, with)
                    })
                    .then(function (with) {
                      return do(something, with)
                    })
                    .then(function (with) {
                      return do(something, with)
                    });
                    

                    Ohne Anspruch auf Exaktheit, es soll nur optisch zeigen, wie der Code ohne und mit Promises aussieht. Die Erstellung der eigentlichen Promises ist auch nicht enthalten. Die Variante ohne Promises sieht hier einfacher aus, aber das ändert sich noch, a) mit jeder neuen Ebene, b) wenn die Funktionen mit richtigem Inhalt gefüllt sind.

                    dedlfix.

                    1. danke. leider geht meine umsetzung davon nicht:

                              var test = new Promise(function(resolve, reject) {db.all(statment, function(err, rows) {
                                  resolve(rows);
                              })});
                              
                              var user_id;
                              var m_id;
                              var pro = function () {
                                  test.then(function (rows) {
                                      var last_index = rows.length - 1;
                                      user_id = rows[last_index].user_id;
                                      m_id = rows[last_index].mitarbeiter_id;
                                      var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                                      statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                                      db.run(statmentLokal);
                                  })
                                  .then(function() {
                                      var statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                                      statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                                      statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                                      statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                                      db.run(statmentLokal);
                                  })
                                  .then(function(){
                                      var statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                                      statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                                      statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                                      statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                                      db.run(statmentLokal);
                                  })
                                  .catch(function (error) {
                                      console.log(error.message);
                                  });
                              }
                      
                      1. bei einem von 5 versuchen wird eine zeile gespeichert...

                      2. Tach!

                        leider geht meine umsetzung davon nicht:

                        Es sieht mir so aus, als ob du den zeitlichen Ablauf noch nicht verstanden hast, denn in dem Code ist er nicht berücksichtigt.

                                var test = new Promise(function(resolve, reject) {db.all(statment, function(err, rows) {
                                    resolve(rows);
                                })});
                                
                                var pro = function () {
                                    test.then(function (rows) {
                                        db.run(statmentLokal);
                                    })
                                    .then(function() {
                                        db.run(statmentLokal);
                                    })
                                    .then(function(){
                                        db.run(statmentLokal);
                                    })
                                    .catch(function (error) {
                                        console.log(error.message);
                                    });
                                }
                        

                        Ich hab mal die Teile entfernt, die nichts mit dem zeitlichen Ablauf zu tun haben. Du hast da einen db.run()-Aufruf (mehrere, aber das Folgende gilt für alle gleichermaßen). Dieser läuft asynchron. Er ist erst dann beendet, wenn die Callback-Funktion aufgerufen wird. Die hast du aber weggelassen, so dass du nicht mitbekommst, wann der nächste Aufruf stattfinden darf. Stattdessen kehrt db.run() sofort zurück und beendet damit die anonyme Funktion, in der es steht. Damit ist für das Promisehandling klar, dass mit dem nächsten then() fortgefahren werden kann. Das darf es aber nicht. Du musst an der Stelle ein unerfülltes Promise zurückgeben, das sich erst dann erfüllen darf, wenn der Callback durch ist. Erst mit diesem unerfüllten Promise wartet das Promisehandling mit dem Fortfahren beim nächsten then(). Du bekommst aber vom db-Objekt keine Promises, und musst dir deshalb eins selbst erstellen. Für jeden Vorgang in der Kette.

                        Das nennt man Promise-Chaining und ist unter anderem beim Promise.prototype.then() beschrieben. (Der Abschnitt Return value ist auch wichtig).

                        dedlfix.

          2. Hallo hmm,

            d.h. der Fehler entsteht dadurch, dass die gerade per SELECT durchlaufenen Daten gelockt sind und der INSERT deswegen DB-intern als Deadlock scheitert? Ansonsten sollte es doch kein Thema sein, weil Du im Callback eine eigene DB-Verbindung herstellst.

            In dem Fall kommst Du wohl nicht drumherum, im Callback zu all() die Rows erstmal zu speichern. Bzw. die letzte - andere interessieren Dich ja eh nicht.

            Erwartest Du eigentlich überhaupt mehrere Zeilen aus SELECT_USER_MITARBEITER_ID? Wenn nicht - warum nimmst Du die letzte Zeile? Die erste wäre einfacher, da greifst Du auf Index 0 zu. Und prüfen, ob Du überhaupt eine Zeile bekommen hast, musst Du glaube ich auch noch.

            Rolf

            --
            Dosen sind silbern
            1. eventuell reicht mir auch der erste anstelle des letzten, das problem hierbei ist, dass die selectanweisung abhängig von dem was in der tabelle steht ein einzelnes ergebnis liefert oder ein array von ergebenissen. das führt dann zu problemen mit dem indexzugriff.

              wie speichere ich den erstmal einen wert in run()?

              1. irgendwie ist das zum mäusemelken:

                generateOneUpdate
                select:
                select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'a';
                generateOneUpdate
                select:
                select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
                generateOneUpdate
                select:
                select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
                generateOneUpdate
                select:
                select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Jens';
                generateOneUpdate
                select:
                select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 's';
                test
                [ { user_id: 1, mitarbeiter_id: 3 } ]
                insert into user_mitarbeiter (user_id, mitarbeiter_id) select 1, 3 where not exists (select 1 from user_mitarbeiter where user_id = 1 and mitarbeiter_id = 3);
                
                insert into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) select 3, 'b', 'c', '2' where not exists (select 1 from mitarbeiter_skill where mitarbeiter_id = 3 and skill = 'b' and subskill = 'c');
                
                replace into mitarbeiter_skill (mitarbeiter_id, skill, subskill, nivau) select 3, 'b', 'c', '2' where exists (select 1 from mitarbeiter_skill where mitarbeiter_id = 3 and skill = 'b' and subskill = 'c');
                
                events.js:141
                      throw er; // Unhandled 'error' event
                      ^
                
                Error: SQLITE_BUSY: database is locked
                    at Error (native)
                

                code:

                    this.generateOneUpdate = function(email, data) {
                        var dbLokal = new sqlite3.Database(dbRef);
                        
                        var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                        dbLokal.run(statment);
                        console.log("generateOneUpdate");
                               
                        statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                        statment = statment.replace(/\?name/g, "'" + data.name + "'");
                        console.log("select:");
                        console.log(statment);
                
                        dbLokal.all(statment, function(err, rows) {
                            console.log("test");
                            console.log(rows);
                            
                            var user_id = rows[0].user_id;
                            var m_id = rows[0].mitarbeiter_id;
                            //callback(rows[lastIndex].user_id, rows[lastIndex].mitarbeiter_id, data);
                            
                            var statmentLokal = SQL_INSERT_USER_MITARBEITER.replace(/\?user_id/g, user_id);
                            statmentLokal = statmentLokal.replace(/\?m_id/g, m_id);
                            console.log(statmentLokal + "\n");
                            dbLokal.run(statmentLokal);
                            statmentLokal = SQL_INSERT_SKILL.replace(/\?m_id/g, m_id);
                            statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                            statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                            statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                            console.log(statmentLokal + "\n");
                            dbLokal.run(statmentLokal);
                            statmentLokal = SQL_REPLACE_SKILL.replace(/\?m_id/g, m_id);
                            statmentLokal = statmentLokal.replace(/\?skill/g, "'" + data.skill + "'");
                            statmentLokal = statmentLokal.replace(/\?subskill/g, "'" + data.subskill + "'");
                            statmentLokal = statmentLokal.replace(/\?nivau/g, "'" + data.nivau + "'");
                            console.log(statmentLokal + "\n");
                            dbLokal.run(statmentLokal);
                        });
                        
                        dbLokal.close();
                    }
                
                1. Hallo hmm,

                  wie soll das denn auch besser werden? Die Schachtelung ist doch immer noch da.

                  So sollte es gehen:

                  this.generateOneUpdate = function(email, data) {
                     var dbLokal = new sqlite3.Database(dbRef);
                          
                     var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                     dbLokal.run(statment);
                     console.log("generateOneUpdate");
                                 
                     statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                     statment = statment.replace(/\?name/g, "'" + data.name + "'");
                     console.log(statment);
                  
                  // ########################################################## MAGIC BEGINS HERE
                     var selectResult;
                     dbLokal.all(statment, function(err, rows) { selectResult = rows; });
                  
                     console.log("test");
                     console.log(rows);
                  // ########################################################## MISCHIEF MANAGED
                  
                     if (rows.length > 0) {                      // ########### THIS TEST MIGHT BE VITAL!            
                        var user_id = rows[0].user_id, 
                            m_id    = rows[0].mitarbeiter_id;
                  
                        var statmentLokal = SQL_INSERT_USER_MITARBEITER
                                               .replace(/\?user_id/g, user_id);
                                               .replace(/\?m_id/g, m_id);
                        console.log("Insert User/Mitarbeiter: " + statmentLokal + "\n");
                        dbLokal.run(statmentLokal);
                  
                        statmentLokal = SQL_INSERT_SKILL
                                           .replace(/\?m_id/g, m_id)
                                           .replace(/\?skill/g, "'" + data.skill + "'")
                                           .replace(/\?subskill/g, "'" + data.subskill + "'")
                                           .replace(/\?nivau/g, "'" + data.nivau + "'");
                        console.log("Insert Skill: " + statmentLokal + "\n");
                        dbLokal.run(statmentLokal);
                  
                        statmentLokal = SQL_REPLACE_SKILL
                                           .replace(/\?m_id/g, m_id)
                                           .replace(/\?skill/g, "'" + data.skill + "'")
                                           .replace(/\?subskill/g, "'" + data.subskill + "'")
                                           .replace(/\?nivau/g, "'" + data.nivau + "'");
                        console.log("Replace skill: " + statmentLokal + "\n");
                        dbLokal.run(statmentLokal);
                     }
                     dbLokal.close();
                  }
                  

                  Rolf

                  --
                  Dosen sind silbern
                  1. leider nicht.

                    this.generateOneUpdate = function(email, data) {
                            var db = new sqlite3.Database(dbRef);
                            
                            var statment = SQL_INSERT_MITARBEITER.replace(/\?name/g, "'" + data.name + "'");
                            db.run(statment);
                            console.log("generateOneUpdate");
                    
                            statment = SQL_SELECT_USER_MITARBEITER_ID.replace(/\?email/g, "'" + email + "'");
                            statment = statment.replace(/\?name/g, "'" + data.name + "'");
                            console.log("select:");
                            console.log(statment);
                    
                            var res;
                            db.all(statment, function(err, rows) {
                                console.log("test");
                                console.log(rows);
                                res = rows;
                            });
                            
                            console.log("res: " + res);
                            db.close();
                        }
                    
                    

                    undefinde, weil db.all erst später ausgeführt wird:

                    Mainserver wurde gestartet...
                    express deprecated res.sendfile: Use res.sendFile instead ../view/view.js:24:25
                    express deprecated res.sendfile: Use res.sendFile instead ../view/services.js:16:21
                    express deprecated res.sendfile: Use res.sendFile instead ../view/services.js:19:13
                    express deprecated res.sendfile: Use res.sendFile instead ../view/view.js:29:29
                    generateOneUpdate
                    select:
                    select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'a';
                    res: undefined
                    generateOneUpdate
                    select:
                    select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
                    res: undefined
                    generateOneUpdate
                    select:
                    select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Christopher';
                    res: undefined
                    generateOneUpdate
                    select:
                    select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'Jens';
                    res: undefined
                    generateOneUpdate
                    select:
                    select user_id, mitarbeiter_id from user, mitarbeiter where user.email = 'wyczisk@gmx.de' and mitarbeiter.name = 'a';
                    res: undefined
                    test
                    [ { user_id: 1, mitarbeiter_id: 6 } ]
                    test
                    [ { user_id: 1, mitarbeiter_id: 3 } ]
                    test
                    [ { user_id: 1, mitarbeiter_id: 7 } ]
                    test
                    [ { user_id: 1, mitarbeiter_id: 6 } ]
                    test
                    [ { user_id: 1, mitarbeiter_id: 3 } ]
                    

                    so, alles scheiße ich hol mir erstmal ne neue rittersport ^^

                    1. Hallo hmm,

                      das kommt davon, wenn der Berater nur Ahnung vom Tuten hat, aber nicht vom Blasen 😏. Also hab ich jetzt übungsweise ein paar Luftballons aufgepustet und herausgefunden, dass Du scheinbar node-sqlite3 von mapbox benutzt. Das wusste ich bisher nicht und bin daher von einer seriellen Ausführung ausgegangen.

                      In deiner Meldung von 12:09, wo .length nicht gefunden wurde, dürfte das Problem am db.run liegen. Der ist für SELECT nicht geeignet weil er keine Result Sets liest. Das macht nur db.all().

                      Aber ich vermute derzeit, dass das Problem in den Interna deiner Callbackfunktion liegen könnte. Die läuft, wenn der SELECT_USER_MITARBEITER_ID durch ist, aber sie startet drei db.run Aufrufe. Der sqlite-Adapter von mapbox parallelisiert das per Default, und daher könnten sich diese Statements auf den Füßen stehen. Bau es mal so, unter Verwendung der urspünglichen callback-Funktion:

                      dbLokal.all(statment, function(err, rows) {
                         if (rows.length < 1) return;
                         dbLokal.serialize(function() {
                            callback(rows[0].user_id, rows[0].mitarbeiter_id);
                         });
                      });
                      

                      Damit werden die SQL-Befehle im Callback nacheinander ausgeführt.

                      Ich bin jetzt weg, hab Rest des Tages anderweit zu tun - viel Glück.

                      Rolf

                      --
                      Dosen sind silbern
                      1. leider geht das auch nicht, es bleibt beim sqlite_busy fehler. aber du hast recht, ich benutze node-sqlite3