Beispiel update Blob-Feld

Themen rund um den praktischen Einsatz von Firebird. Fragen zu SQL, Performance, Datenbankstrukturen, etc.

Moderator: thorben.braun

Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo zusammen,

kann mir bitte jemand ein Beispiel posten, wie ich per "update"
ein PDF in einen existierenden Datensatz in das Blob-Feld "Image" speicher ?

In diese Tabelle:
CREATE TABLE BLOB_TEST (
RID_BLOB_TEST RID NOT NULL,
BLOB_TEST_NR II NOT NULL,
PFAD NTXT,
DATEI MTXT,
IMAGE BLOB SUB_TYPE 0 SEGMENT SIZE 4096,
STATUS SI DEFAULT 0,
EDITDATE TS DEFAULT '11.11.1111 00:00',
INDATE TS DEFAULT 'NOW',
SCRIPT MTXT,
SCRIPT_USER_NR STXT,
SCRIPT_USER STXT,
BATCH_SCRIPT MTXT,
BATCH_DATE TS DEFAULT '11.11.1111 00:00'
);

Danke und Gruss
Hamburgo
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Genauso wie jedes andere Feld auch.
Allerdings: Wenn man es gewohnt ist, alle Update/Insert/Deletes komplett als SQL zusammen zu bauen, kann das nicht klappen.

Jede Programmiersprache unterstützt das Konzept von Command-Objekten und Parametermarkern. Damit funktionieren SQL's i.Ü. um Faktoren schneller.

EIn Insert sähe da dan so aus:

insert into mytable (F1, F2, F3, ..., Fn)
values (?, ?, ?, ..., ?)

Jedes Fragezeichen ist ein Parametermarker die man dann, je nach Sprache als Array dem Execute mit angibt.

Dieser sähe dann so aus:
MyCmd.Execute(new object[] {Var1, Var2, Var3, ..., Varn})
Statt "new object[] ..." kann man natürlich auch Arrays verwenden oder die Werte den Parameterauflistungen des Commandobjects zuweisen.
So sähen dann alle SQL's aus und die Wiederverwendbarkeit an allen Stellen wäre dann gewährleistet:

Select * from MyTable Where F1 = ? and F2 = ? ...
Update myTable set F1 = ?, F2 = ?, ...
where F1 = ? and F2 = ? and ...

In .Net z.B. kann man Parameter sogar benamen:
insert into mytable (F1, F2, F3, ..., Fn)
values (@F1, @F2, @F3, ..., @Fn)

Vorteile: Man muss sich um die Umwandlung von Feldern nicht mehr kümmern. Die Verdopplung von Hochkommata bei Zeichenketten entfällt. Man muss nicht mehr, je nach Sprachumgebung, aufpassen, ob Dezimalpunkt oder Komma verwendet wird. Man kann sogar NULL als Parameterwert übergeben, falls das Feld es zulässt.
Man schafft da z.B. beim Insert durchaus mehrere 1000 Inserts je Sekunde.
Die Firebird muss nicht bei jedem Aufruf einen neuen Syntaxcheck machen, da sich der SQL ja nicht ändert.
BLOB's werden dann genauso behandelt wie jedes andere Feld auch.
Die Summe der Längen der Feldinhalte spielen bei der max. Länge eines SQL's keine Rolle.

Nachteile: absolut keine!
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo bfuerchau,

ich bin echt total glücklich, dass es Dich gibt und sehr dankbar für Deine
in der Regel sehr hilfreichen Antworten und erstaunt über die Ausführlichkeit.

Aber manchmal bin ich einfach überfordert, so wie in diesem Fall und kann
Dir schlichtweg nicht folgen.

Was ich bisher habe:

Code: Alles auswählen

 
           // PDF von HDD lesen
           $Datei  = "R02626-63322031.001-O.pdf";
           $Pfad   = "Pool/Rechnung/2017/04/"; 
           
           $i      = fopen(InputOutput.$Update.$Temp, "r");
           $Inhalt = fread($i, filesize(InputOutput.$Pfad.$Datei));
           fclose($i);
           
           $Db['Pointer'] = fbird_connect($Db['File'], $Db['Username'], $Db['Password']);

           // PDF als BLOB in DB speichern
           $blh    = fbird_blob_create($Db['Pointer']);
           fbird_blob_add($blh, $Inhalt);
           $blobid = fbird_blob_close($blh);

           $Update = "UPDATE 
                            BLOB_TEST
                      SET
                            IMAGE = '".$blobid."',
                            PFAD  = '".$Pfad."',
                            DATEI = '".$Datei."'
                      WHERE
                            RID_BLOB_TEST = '1'
                     ";
           echo "\$Update = ".$Update."<BR>";
    //       $Update = "";
           $ResultU = fbird_query($Db['Pointer'], $Update);
           
           // PDF aus BLOB in DB lesen
           $Select = "SELECT 
                              *
                      FROM
                              BLOB_TEST
                      WHERE
                              RID_BLOB_TEST = '1'
                     "; 
           
           $ResultS   = fbird_query($Db['Pointer']], $Select);           
           
           $Posten    = fbird_fetch_object($ResultS);           
/*
echo von:
$Posten = Array
(
    [rid_blob_test] => 1
    [blob_test_nr] => B0001
    [pfad] => Pool/Rechnung/2017/04/
    [datei] => R02626-63322031.001-O.pdf
    [image] => 0x0000000200000081
    [status] => 1
    [editdate] => 2017-04-26 13:47:02
    [indate] => 2017-04-26 13:35:33
    [script] => TestBlob
    [script_user_nr] => M64297001
    [script_user] => Willi
    [batch_script] => 
    [batch_date] => 1111-11-11 00:00:00
)
//*/
           
           $blob_data = fbird_blob_info($Posten['image']);
           $blob_hndl = fbird_blob_open($Posten['image']);
           $Inhalt    = fbird_blob_get($blob_hndl, $blob_data[0]);
           
           fbird_blob_close($blob_hndl); 

           // PDF auf HDD speichern
           $Datei = "R02626-63322031.001-B.pdf";
           $Pfad  = "Pool/Rechnung/2017/04/"; 
           
           $i      = fopen(InputOutput.$Pfad.$Datei, "w");
           fwrite($i, $Inhalt);
           fclose($i);           

           echo "PDF erstellt: ".$Datei."<BR>";
Die Ausgabe-Datei ist nur 18 Byte groß wie die eingelesene Datei und für den Acrobat-Reader natürlich nicht lesbar.

Kannst Du mir sagen, was ich da falsch mache ?
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Wenn du mir sagen kannst, welche Programmiersprache das ist und welche Bibliothek für FB du verwendest?

Code: Alles auswählen

          // PDF aus BLOB in DB lesen
           $Select = "SELECT 
                              *
                      FROM
                              BLOB_TEST
                      WHERE
                              RID_BLOB_TEST = ?
                     "; 
Wenn du diesen Select hinbekommst, also das "?" durch ein Parameter-Objekt ersetzten kannst, kann ich dir auch bei dem Insert/Update helfen.
Ich muss mir dazu dann mal die Doku ansehen, es wäre daher nett, wenn du einen Link dahin hättest.
Der zusammengestrickte Update sieht mir da nach einem sehr großen String aus.
Der kann auf Grund der Länge scheitern (bis 2.5 32K, ab 3.0 10MB) oder beim Einbetten von Zeichen die fehlende Verdopplung der Hochkommata (wird auch gerne vergessen).
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Die Programmiersprache ist PHP und nutze die Standard
Firebird-Klassen (ehemals InterBase).
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Vielleicht macht es mehr Sinn mein Problem / Bedarf anders zu formulieren.

Um mein ERP-System GoBD-konform zu bekommen, muss ich meine PDF-
Rechnungen in einer Datenbank speichern/archivieren und natürlich auch
wieder auslesen / exportieren können.

Die PDFs haben eine Größe von ca. 50 KB bis ca. 3 MB.

Die Programmiersprache ist PHP. unter Nutzung der Standard-Firebird-Klassen
(ehemals InterBase).

a. Wie muss die Tabelle konfiguriert werden ?
b. Wie bringe ich die PDFs in die Tabelle rein.
c. Wie lese ich die PDFs wieder aus bzw. exportiere sie.

Google war bei dieser Problemstellung für FireBird leider nicht mein Freund.

Für MySQL und andere DBs gibt es reichlich Muster-Codes.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Hallo,

wenn du die PDFs direkt in der DB speichern möchtest, müssen die Felder als Binär-BLOB deklariert werden.

Dann kannst du die ibase_blob-Funktionen verwenden:
SIehe auch https://www.php.net/manual/de/function. ... ob-add.php

Alternativ wäre es auch möglich die Dateien im Dateisystem abzulegen und in der DB den relativen Dateipfad vorzuhalten. Das hält die DB schlank.

Gruß
Martin
Martin Köditz
it & synergy GmbH
Hamburgo
Beiträge: 125
Registriert: Di 28. Mai 2019, 17:28

Hallo Martin,

das Problem mit den ibase_blob-Funktionen zu lösen, habe ich ja versucht und bin grandios
gescheitert (Siehe einen Post weiter oben mit Code).

Einen Link in einer DB zu speichern, der auf eine Datei im jeweiligen Datei-System verweist,
ist genau das, was die GoBD NICHT erlaubt.

Alle Firmen-Jahres-Abschlüsse die mit einer Software erstellt wurden, die nur das machen,
werden bei einer Prüfung nicht anerkannt, verbunden mit der dramatischen Folge, dass
alle PDF-Rechnung ihre steuerliche Wirkung verlieren und das Finanzamt dann schätzen darf.

Das kann richtig teuer werden.

Gibt es denn hier im Forum niemanden, der schon mal mit PHP Binär-Blobs erfolgreich
gehandelt hat, egal ob PDFs, Bilder oder etwas anderes ?

Oder kennst Du jemanden in der internationalen FireBird-Community, der das kann ?
bfuerchau
Beiträge: 485
Registriert: Mo 7. Mai 2018, 18:09
Kontaktdaten:

Nun ja, das meinte ich mit "Parametermarkern":

https://www.php.net/manual/de/function. ... ob-add.php

Code: Alles auswählen

 $dbh = ibase_connect($host, $user, $pass);  // Connection

  $blh = ibase_blob_create($dbh);    // Puffererstellung
  ibase_blob_add($blh, $data);          // Füllen des Puffers
  $blobid = ibase_blob_close($blh);  // Handle für den Blob

  $sql = "INSERT INTO blobtable(blobfield) VALUES ([b]?[/b])";  // ? = Parameter
  $sth = ibase_query($dbh, $sql, [b]$blobid[/b]);  // Zuordnung "?" zu Parameter
Auf Parameter wird ein wenig hier hingewiesen:
https://www.php.net/manual/de/function. ... repare.php

Leider geht die Doku tatsächlich nicht weiter auf den Hinweis ein:
"Bereitet eine Abfrage für späteres Binden der Parameter-Platzhalter"

Diese Parameter-Platzhalter sind meine oben erwähnten "?".
Am Beispiel des Insert sieht man die Verwendung und die Bindung beim ibase_query. Blob's werden da nun speziell behandelt, obwohl man sie wie Felder behadeln könnte, aber seis drum.

Generell sehe ich das Thema Prepare/Query/Parameter eher unbehandelt. Kein Wunder, dass SQL da etwas fehlerhaft verwendet wird. Es läuft zwar irgendwie, aber es könnte besser designed und weniger fehleranfällig sein.

Zumal gerade die Verwendung von Parmetern das Thema "SQL-Injection" auf Gund der Technologie unmöglich macht.
Benutzeravatar
martin.koeditz
Beiträge: 443
Registriert: Sa 31. Mär 2018, 14:35

Oder kennst Du jemanden in der internationalen FireBird-Community, der das kann ?
Natürlich - im Zweifel mich. ;)

Wichtig ist, dass man zunächst folgendes versteht: BLOBs werden in eigenen Bereichen, sogenannte Seiten, gespeichert. In der Tabelle selbst wird nur der Link hinterlegt, dies ist die BLOB-ID. Beim Ablegen in der DB wird das BLOB in Segmente aufgetrennt. Ab FB 3.0 ist die maximale Segmentgröße 64k.

Mit folgendem Code kannst du die Binärdatendaten in die DB schreiben.

Code: Alles auswählen

// Maximale Segmentgröße - 64K
define('MAX_SEGMENT_SIZE', 65535);

// Hilfsfunktion zum Erstellen von BLOBs
// $data beinhaltet die Binärdaten
// Blob-ID wird zurückgegeben
function blob_create($data) {
    if (strlen($data) == 0)
        return false;
    $handle = ibase_blob_create();
    $len = strlen($data);
    for ($pos = 0; $pos < $len; $pos += MAX_SEGMENT_SIZE) {
        $buflen = ($pos + MAX_SEGMENT_SIZE > $len) ? ($len - $pos) : MAX_SEGMENT_SIZE;
        $buf = substr($data, $pos, $buflen);
        ibase_blob_add($handle, $buf);
    }
    return ibase_blob_close($handle);
}

// DB-Verbindung anlegen
if(($dbh = ibase_connect('10.1.12.200:/srv/firebird/db01.fdb', 'sysdba', 'masterkey')) === false)
        die(ibase_errmsg ());

// Blob erzeugen
$blob = blob_create(file_get_contents('/srv/www/htdocs/dev05.syndesk.de/media/koeditz.jpg'));

// Blob per Update in DB schreiben
$query = ibase_query($dbh, "UPDATE TBL_TEST set pdf_bin = ? where first_name ='Martin'", $blob) or die(ibase_errmsg());
Gruß
Martin
Martin Köditz
it & synergy GmbH
Antworten