Alex: (PHP) SQL-Dump aus Textarea ausführen

Hallo,

ich bin gerade dabei, ein MySQL-Backup-Skript zu basteln. Jetzt bleibe ich aber gerade bei der Restore-Funktion hängen.

Eigentlich sollte das ja ganz einfach sein: Ich splitte den SQL-Dump aus der Textarea an den ";" auf (explode) und packe die Teile in einen Array. Dann arbeite ich den Array ab und führe jede DB-Abfrage einzeln aus.

Das Problem dabei ist, dass ja auch ";" in den Datenbank-Einträgen vorkommen kann. Dann würden unvolständige Queries entstehen.

Hat mir jemand einen Tipp?

Schon mal danke!
Alex

P.S.:
Ja, ich kenne phpMyAdmin, das hilft mir in diesem Fall aber nicht weiter, da die Backup-Funktion bestandteil eines Skriptes werden soll.
Diesen Eintrag habe ich auch schon entdeckt. Ich hätte es aber lieber Zeilenweise, damit ich bei einem Fehler noch abbrechen kann.

  1. echo $begrüßung;

    Quick and dirty fällt mir ein, nicht an ; sondern an ";\n" (oder ";\r\n"), also ein ; mit folgendem Zeilenende, aufzuspalten, wenn du sicher sein kannst, dass diese Kombination nicht in den Daten vorkommt.

    Sonst würde ich einen Parser bauen, der bei Auftreten von " oder ' ein Flag setzt und dann das ; ignoriert, wobei auch noch " und ' beachtet werden müssen.

    echo "$verabschiedung $name";

    P.S. Hast du auch die max_execution_time berücksichtigt?

    1. Hallo dedlfix,

      Quick and dirty fällt mir ein, nicht an ; sondern an ";\n" (oder ";\r\n"), also ein ; mit folgendem Zeilenende, aufzuspalten, wenn du sicher sein kannst, dass diese Kombination nicht in den Daten vorkommt.

      Hm... es könnten ja auch mehrere Queries in einer Zeile stehen.

      Sonst würde ich einen Parser bauen, der bei Auftreten von " oder ' ein Flag setzt und dann das ; ignoriert, wobei auch noch " und ' beachtet werden müssen.

      So etwas in der Art habe ich mir auch schon überlegt. Man könnte auch nach dem Spalten in jeder Zeile die nicht escapten Quotes (') zählen. Wenn die Anzahl ungerade ist, hat man eine Query zerhackt und muss sie dann wieder zusammenbauen...

      Scheint auf jeden Fall etwas umständlicher zu werden, wenn's etwas fehlertoleranter sein soll.

      P.S. Hast du auch die max_execution_time berücksichtigt?

      Hab's im Hinterkopf - so weit bin ich nur noch nicht.

      Grüsse
      Alex

      1. echo $begrüßung;

        Hm... es könnten ja auch mehrere Queries in einer Zeile stehen.

        Ich dachte, du kannst das verhindern. Du schreibst doch auch den Backup-Teil, oder nicht?

        Man könnte auch nach dem Spalten in jeder Zeile die nicht escapten Quotes (') zählen. Wenn die Anzahl ungerade ist, hat man eine Query zerhackt und muss sie dann wieder zusammenbauen...

        Das sagst du so einfach in deinem jugendlichen Leichtsinn :-) Würde dein Zähler erkennen, dass das " in \" ein ganz normales " ist?

        echo "$verabschiedung $name";

        1. Hallo dedlfix,

          Hm... es könnten ja auch mehrere Queries in einer Zeile stehen.
          Ich dachte, du kannst das verhindern. Du schreibst doch auch den Backup-Teil, oder nicht?

          Ja, aber der User könnte ja etwas an dem SQL-Dump verändern.

          Das sagst du so einfach in deinem jugendlichen Leichtsinn :-) Würde dein Zähler erkennen, dass das " in \" ein ganz normales " ist?

          Ich meine ja gerade, die "unescapten" Quotes zu zählen. Also nur die, die keinen Backslash davor haben. Das Müsste doch gehen, oder?

          Grüsse
          Alex

          1. echo $begrüßung;

            Ja, aber der User könnte ja etwas an dem SQL-Dump verändern.

            Na dann wird er schon sehen was er davon hat. Alles kannst du nicht verhindern. Wenn er unsachgemäß etwas an Quotierungen verändert, kannst du das auch nur schwer abfangen.

            Das sagst du so einfach in deinem jugendlichen Leichtsinn :-) Würde dein Zähler erkennen, dass das " in \" ein ganz normales " ist?

            Ich meine ja gerade, die "unescapten" Quotes zu zählen. Also nur die, die keinen Backslash davor haben. Das Müsste doch gehen, oder?

            Das ist ja grade das hüpfende Komma \" hat sogar zwei Backslashes vorndran und ist trotzdem nicht maskiert.
            Reguläre Ausdrücke könnten beim Zählen helfen, da sie recht mächtig sind, aber irgendwo haben sie auch Grenzen. Mir ist nicht bekannt, dass man mit ihnen auf eine gerade oder ungerade Anzahl Zeichen testen kann.

            echo "$verabschiedung $name";

            1. Hallo dedlfix,

              Das ist ja grade das hüpfende Komma \" hat sogar zwei Backslashes vorndran und ist trotzdem nicht maskiert.

              Das kann doch nur in einem Eintrag vorkommen. Ich würde es nicht mitzählen, weil es (mindestens) einen Backslash davor hat. Dann liege ich doch richtig, oder?

              Nehmen wir z.B. mal diesen SQL-Dump:
              INSERT INTO table VALUES ('hallo; wie geht's?', 'noch was; \' bla...');

              Durch Spalten an ";" hätte ich:
              (1) INSERT INTO table VALUES ('hallo
              (2) wie geht's?', 'noch was
              (3) \' bla...')

              in (1) zähle ich eine ungerade Anzahl von ' ohne Backslash davor, also klebe ich ";" und (2) dazu. Jetzt zähle ich immernoch eine ungerade Anzahl von ' ohne Backslash. Also nochmal ";" und (3) dazu und nun habe ich eine gerade Anzahl von ' ohne davorstehendem Backslash.

              Müsste das nicht so gehen? Oder habe ich etwas übersehen. Das ganze muss dann halt von einer ausgeklügelten Schleife durchlaufen werden.

              Grüsse
              Alex

              1. Moin!

                Müsste das nicht so gehen? Oder habe ich etwas übersehen. Das ganze muss dann halt von einer ausgeklügelten Schleife durchlaufen werden.

                Warum einfach und performant, wenn es schwierig und langsam geht?

                MFFG (Mit freundlich- friedfertigem Grinsen)

                fastix®

                --
                Als Freiberufler bin ich immer auf der Suche nach Aufträgen: Schulungen, Seminare, Training, Development
                1. Hallo Fastix,

                  Warum einfach und performant, wenn es schwierig und langsam geht?

                  Was meinst Du jetzt genau damit?

                  Grüsse
                  Alex

              2. echo $begrüßung;

                Das ist ja grade das hüpfende Komma \" hat sogar zwei Backslashes vorndran und ist trotzdem nicht maskiert.

                Das kann doch nur in einem Eintrag vorkommen. Ich würde es nicht mitzählen, weil es (mindestens) einen Backslash davor hat. Dann liege ich doch richtig, oder?

                Nein, da liegst du nicht richtig. \ wird zu einem einfachen \ aufgelöst. Das dahinterkommende " ist der Abschluss einer mit " begonnen Zeichenkette.

                INSERT INTO table VALUES ('hallo; wie geht's?', 'noch was; \' bla...');

                Das erzeugt einen Fehler, weil das hinter \' folgende bla nicht mehr Bestandteil der Zeichenkette ist.

                echo "$verabschiedung $name";

                1. Hallo dedlfix,

                  Nein, da liegst du nicht richtig. \ wird zu einem einfachen \ aufgelöst. Das dahinterkommende " ist der Abschluss einer mit " begonnen Zeichenkette.

                  So etwas wäre aber doch ungültiger SQL-Code:
                  INSERT INTO table VALUES (\'hallo');

                  Bin ich dann nicht doch auf der richtigen Seite, wenn ich alle ', vor denen mindestens ein Backslash steht als "Quote innerhalb eines Eintrags" werte?

                  Hier mal meine erste Bastelei:

                    
                  <?php  
                  function split_sql($sql)  
                   {  
                    // Kommentare und leere Zeilen aussortieren:  
                    $lines = explode("\n", $sql);  
                    $cleared_lines = array();  
                    foreach($lines as $line)  
                     {  
                      $line = trim($line);  
                      if($line != '' && substr($line,0,1)!='#') $cleared_lines[] = $line;  
                     }  
                    // Zeilen zu einem String verbinden:  
                    $sql = stripslashes(implode('',$cleared_lines));  
                    $delimiter = ';';  
                    // am Trennzeichen aufspalten:  
                    $queries = explode($delimiter, $sql);  
                    $query_count = count($queries);  
                    // schauen, ob innerhalb eines Eintrags gespaltet wurde:  
                    for ($i = 0; $i < $query_count; $i++)  
                     {  
                      unset($j);  
                      $quotes_count_total = substr_count($queries[$i],"'");  
                      $escaped_quotes_count = substr_count($queries[$i],"\'");  
                      $unescaped_quotes_count = $quotes_count_total - $escaped_quotes_count;  
                      if($unescaped_quotes_count%2!=0)  
                       {  
                        // ungerade Anzahl nicht escapter Quotes - Eintrag gespaltet!  
                        $query = $queries[$i];  
                        for($j = $i+1; $j < $query_count; $j++)  
                         {  
                          // nächste Zeilen mit Trennzeichen hinzufügen, bis Query komplett:  
                          $query .= $delimiter . $queries[($j)];  
                          $quotes_count_total = substr_count($query,"'");  
                          $escaped_quotes_count = substr_count($query,"\'");  
                          $unescaped_quotes_count = $quotes_count_total - $escaped_quotes_count;  
                          if($unescaped_quotes_count%2==0)  
                           {  
                            // Anzahl nicht escapter Quotes gerade - Query OK!  
                            break;  
                           }  
                         }  
                       }  
                      else  
                       {  
                        $query = $queries[$i];  
                       }  
                      // zu $i die Zeilen dazuzählen, die übersprungen werden sollen:  
                      if(isset($j)) $i = $i + $j;  
                    
                      $query = trim($query);  
                      if($query != '') $sql_array[] = $query;  
                    
                     }  
                    return $sql_array;  
                   }  
                  ?>  
                  
                  

                  Grüsse
                  Alex

                  1. echo $begrüßung;

                    Nein, da liegst du nicht richtig. \ wird zu einem einfachen \ aufgelöst. Das dahinterkommende " ist der Abschluss einer mit " begonnen Zeichenkette.

                    So etwas wäre aber doch ungültiger SQL-Code:
                    INSERT INTO table VALUES (\'hallo');

                    Ja, weil der \ außerhalb von Zeichenketten keine Bedeutung hat gibt es da einen Syntaxfehler.

                    Bin ich dann nicht doch auf der richtigen Seite, wenn ich alle ', vor denen mindestens ein Backslash steht als "Quote innerhalb eines Eintrags" werte?

                    Innerhalb von Zeichenketten hat der Backslash die Bedeutung, das nachfolgende Zeichen als zum Inhalt der Zeichenkette gehörend zu werten.

                    "foo " bar ' bla \" wird aufgelöst zu: foo " bar ' bla \

                    Du musst einfach nur erkennen, dass jedes beliebige Zeichen nach einem doppelten Backslash auf seiner eigenen Hochzeit tanzt und damit zusammen mit dem ihm unmittelbar vorangegangenen Backslash keine Quotierung darstellt.

                    Nochmal auseinandergepflückt:

                    "    Anfang der Zeichenkette
                    foo
                    "   ein maskiertes "
                    bar
                    '   ein maskiertes '
                    bla
                    \   ein maskierter \ "    Ende der Zeichenkette

                    Und noch gemeiner wird es, wenn mehr als zwei \ hintereinander vorkommen:
                    \"  ein \ und ein maskiertes "
                    \\" zwei Backslashes und ein Ende der Zeichenkette
                    \\"  zwei \ und ein maskiertes "
                    usw.

                    Meine Zeichenketten sind in den Beispielen mit "" eingefasst. Analog musst du das gleiche für in '' eingefasste Zeichenketten berücksichtigen.

                    Erschwerend kommt noch hinzu, dass ' in ""-Strings und umgekehrt nicht maskiert werden müssen.
                    In "'" stellt das ' kein Ende einer Zeichenkette dar (und auch keinen Anfang).
                    Du musst also auch noch berücksichtigen, ob du grad in einer "" oder ''-Zeichenkette bist.

                    Beachte bitte auch http://dev.mysql.com/doc/mysql/en/string-syntax.html

                    echo "$verabschiedung $name";

                    P.s. Wenn ich das jetzt nicht verständlich erklärt habe, dann weiß ich auch nicht mehr weiter :-(

  2. Moin!

    Hat mir jemand einen Tipp?

    Ja.

    Schreibe die Daten in das File $strFilename und führe:

    system("mysql -u=benutzer --password=GehHeim datenbank < $strFilename") aus

    Lösche die Datei mit unlink($strFilename)

    Der4 MySQL-Client müsste eigentlich auch bei Deinem Hoster installiert sein. Auf Deinem eventuellen Windows-Testsystem musst Du bestimmt das Verzeichnis c:\mysql\bin mit in den Pfad aufnehmen.

    MFFG (Mit freundlich- friedfertigem Grinsen)

    fastix®

    --
    Als Freiberufler bin ich immer auf der Suche nach Aufträgen: Schulungen, Seminare, Training, Development
    1. Hallo Fastix,

      Schreibe die Daten in das File $strFilename und führe:
      system("mysql -u=benutzer --password=GehHeim datenbank < $strFilename") aus
      Lösche die Datei mit unlink($strFilename)

      Hm, ja... da war ich auch schon mal (siehe Link im Ausgangsposting). Hat mir bis jetzt nicht so zugesagt, aber ich werd's mal testen. Kann $strFilename auch ein String sein? Wäre angenehmer wegen der Schreiberechte.

      Grüsse
      Alex

      1. Moin!

        Hm, ja... da war ich auch schon mal (siehe Link im Ausgangsposting). Hat mir bis jetzt nicht so zugesagt, aber ich werd's mal testen. Kann $strFilename auch ein String sein? Wäre angenehmer wegen der Schreiberechte.

        Ja. Auch ein

        system('echo "INSERT into tabelle SET name="Müller";INSERT into tabelle SET V_name="Maik"" | mysql -u username --password=GehHeim -datenbank')

        Probleme bei dieser Methode bekommst Du maximal mit den Quotas, die Du via Backslash verstecken kannst und den backlshs, die Du mit einem Backslash versteckst. Dafür hat PHP aber nette Funktionen.

        Du kannst (und das ist sicherer...) auch das Passwort in die erste Zeile des zu übergebenden Strings schreiben:

        system('echo "GehHeim\nINSERT into tabelle SET name="Müller";INSERT into tabelle SET V_name="Maik"" | mysql -u username -p -datenbank')

        Dann taucht das Passwort nicht auf, falls ein weiterer Benutzer mit "ps ax|grep password" die Prozessliste abfragt, während das Skript ausgeführt wird.

        MFFG (Mit freundlich- friedfertigem Grinsen)

        fastix®

        --
        Als Freiberufler bin ich immer auf der Suche nach Aufträgen: Schulungen, Seminare, Training, Development
        1. Moin!

          Bevor ich es vergesse:

          Mit dem Upload eines (Dump-) Files und der direkten Ausführung auf dem System mittels system() oder exec() oder dergleichen umgehst Du die Speicherbeschränkungen bei PHP. Die könnten schnell erreicht werden, wenn Deine Datenbank mal etwas größer ist...

          Das gleiche betrifft auch das Herstellen des Backups.
          Wenn Du das nicht berücksichtigst, wirst Du mit dem Tool (sehr) schnell an Grenzen stoßen.

          MFFG (Mit freundlich- friedfertigem Grinsen)

          fastix®

          --
          Als Freiberufler bin ich immer auf der Suche nach Aufträgen: Schulungen, Seminare, Training, Development
        2. Hallo Fastix,

          ...

          Danke, werde das mal ausprobieren.

          Grüsse
          Alex