Bild in bytea über MS Access

Hi,

ich habe mein MS Access 2000 Frontend MySQL au PostGreSQL umgestellt. Bis auf ein Problem mit den ehemaligen blob-Daten geht auch alles. Für die bilter nutze ich nun bytea und geife folgendermaßend lesend und schreibend mittels ADO 2.7 über PG-ODBC Vers.8.03.04.00 (UNICODE) drauf zu:

schreiben:

Public Function InsertPixFile(strQuelle As String, strPixFile, ID As Variant)
On Error GoTo Err_InsertPixFile
'Diese Prozedur speichert eine Datei (strPixFile) unter dem Namen in strName in
'der Tabelle Bilder binär im Feld "Bild"

    Dim pix() As Byte
    Dim rs As ADODB.Recordset
    Dim strSQL As String
    Dim F As Integer
    F = FreeFile
    
    If Nz(ID) = "" Then Exit Function
    
    Open strPixFile For Binary As #F
        ReDim pix(LOF(F))
        Get #F, , pix()
    Close #F
    
    Select Case strQuelle
    Case "Paket"
        strSQL = "SELECT Bild AS Pic, BildName AS PicName FROM tbl_P_Paket " _
            & "WHERE Deleted=0 AND ID_Paket=" & ID
    Case "Sparepart"
        strSQL = "SELECT picture AS Pic, picturename AS PicName FROM spareparts_details " _
            & "WHERE id_spareparts_details=" & ID
    Case "NIK_Anlagen"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM tbl_kunden_documents " _
            & "WHERE id_kunden_documents=" & ID
    Case "Spareparts_Dokument"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM spareparts_documents " _
            & "WHERE id_spareparts_documents=" & ID
    Case "Pakete_Dokument"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM tbl_p_documents " _
            & "WHERE id_p_documents=" & ID
    End Select
    Set rs = New ADODB.Recordset
    Set rs = clsODBC.GetSQL(strSQL)
    With rs
        If .RecordCount = 0 Then
            MsgBox "Bitte speichern Sie Ihre Eingaben bevor Sie ein Bild/Datei hinzufügen.", _
                vbOKOnly + vbInformation, "Speichern"
        Else
            'Binär speichern
            rs("Pic").AppendChunk pix()
            rs("PicName") = Mid(strPixFile, InStrRev(strPixFile, "\") + 1)
            .Update
        End If
        .Close
        Set rs = Nothing
    End With
    
Exit_InsertPixFile:
    Erase pix
    Set rs = Nothing
    Exit Function
Err_InsertPixFile:
    Reset
    FehlerAufnahme strModul, "InsertPixFile"
    Resume Exit_InsertPixFile
End Function

lesen:

Public Function CreatePix(strQuelle As String, ByRef strPicName As String, ID As Long) As StdPicture
On Error GoTo Err_CreatePix

    Dim rs As ADODB.Recordset
    Dim pix() As Byte
    Dim lSize As Long
    Dim strSQL As String
    Dim Resultat As Double
    Dim F As Integer
    F = FreeFile

    If ID = 0 Then Exit Function

    Select Case strQuelle
    Case "Paket"
        strSQL = "SELECT Bild AS Pic, BildName AS PicName FROM tbl_P_Paket WHERE Deleted=0 AND ID_Paket=" & ID
    Case "Sparepart"
        strSQL = "SELECT picture AS Pic, picturename AS PicName FROM spareparts_details " _
            & "WHERE id_spareparts_details=" & ID
    Case "Tyres"
        strSQL = "SELECT picture AS Pic, picturename AS PicName FROM spareparts_details " _
            & "WHERE id_spareparts_details=" & ID
    Case "Anlagen_Kunden"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM tbl_kunden_documents " _
            & "WHERE id_kunden_documents=" & ID
    Case "Anlagen_Pakete"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM tbl_p_documents " _
            & "WHERE id_p_documents=" & ID
    Case "Anlagen_Spareparts"
        strSQL = "SELECT document AS Pic, document_name AS PicName FROM spareparts_documents " _
            & "WHERE id_spareparts_documents=" & ID
    End Select
    Set rs = New ADODB.Recordset
    Set rs = clsODBC.GetSQL(strSQL)
    With rs
        If .RecordCount > 0 Then
            lSize = rs("Pic").ActualSize
            If Not lSize = 0 Then
                ReDim pix(lSize)
                pix() = rs("Pic").GetChunk(lSize)       'Binärdaten in Arry laden
                Set CreatePix = BytesToPicture(pix)     'Siehe Funktion im Modul mdlArrayToStdPicture
                strPicName = rs("PicName")
                'Das passiert bei nicht unterstützten Bildformaten
                If CreatePix Is Nothing Then MsgBox "Das Bildformat wird nicht unterstützt oder die " _
                    & "Anlage ist nicht vorhanden oder defekt. Laden Sie die Anlage neu!", _
                    vbOKOnly + vbCritical, "Anlage kann nicht angezeigt werden"
            End If
        Else
            Erase pix
        End If
        .Close
        Set rs = Nothing
    End With
    
    If Left(strQuelle, 8) = "Anlagen_" Then
        Open GetSpecFolder(CSIDL_PERSONAL) & "\Anlagen_" & strPicName For Binary As #F
            Put #F, , pix
        Close #F
        MsgBox "Die Anlage wurde unter '" & GetSpecFolder(CSIDL_PERSONAL) & "\Anlagen_" & strPicName _
            & "' abgelegt. Es wird nun versucht diese Anlage zur Anzeige zu bringen. Sollte dies " _
            & "nicht gelingen, verfügen Sie über keinen geeignetten Viewer für dieses Dateiformat.", _
            vbOKOnly + vbInformation, "Anlage anzeigen"
        Resultat = ShellExecute(0, "open", GetSpecFolder(CSIDL_PERSONAL) & "\Anlagen_" & strPicName, "", "", vbNormalFocus)
    End If
    
Exit_CreatePix:
    Erase pix
    Set rs = Nothing
    Exit Function
Err_CreatePix:
    FehlerAufnahme strModul, "CreatePix"
    Resume Exit_CreatePix
End Function

die Datenbankabfrage geschieht so:

Public Function GetSQL(ByVal strSQL As String) As ADODB.Recordset
On Error GoTo Err_GetSQL
'Ausführen SQL-Abfrage

    Set GetSQL = New ADODB.Recordset
    GetSQL.CursorLocation = adUseClient
    GetSQL.Open strSQL, CurrentProject.Connection, adOpenKeyset, adLockOptimistic   'adOpenDynamic, adLockOptimistic
    
Exit_Err_GetSQL:
    Exit Function
Err_GetSQL:
        MsgBox "Fehler Nummer " & Err.Number & " (" & Err.description & ") in 'GetSQL' aufgetreten"
    Resume Exit_Err_GetSQL
End Function

Bei .AppendChunk und .GetChunk erhalte ich jeweis den Fehler “3219 Der Vorgang ist in diesem Zusammenhang nicht zugelassen.”
Komisch ist auch dass bei lSize = rs(“Pic”).ActualSize immer 255 raus kommt, was nicht sein kann. Hat jemand eine Idee dazu?

Danke Sylvio

Die Fehlermeldungen kommen nicht von PG, sondern von M$-Dingens.

Ich kann Dir nur empfehlen, in PG zu loggen, und zwar sowohl die ankommenden Befehle als auch die evtl. Fehler.

Andreas

Hi Andreas,

danke für den Tip. Probiere ich morgen gleich! Da ich Neuling bei PG bin, kann du mir bitte noch sagen wo oder wie ich das Loggig aktiviere und wo das Log-File oder so dann liegt?

LG Sylvio

In der Datei postgresql.conf wird alles definiert, was du benötigst. Dort stehen auch die Pfade zur Logdatei.

Näheres vermittelt dir die Dokumentation, in diese solltest du wirklich den ein oder anderen Blick werfen :wink:

OK, danke!!! Ich geb hier Feedback, wenn ich das Problem lösen konnte.

Das ist nett, Danke schön.

Hi Andreas,

also du hattest recht mit der Vermutung, dass es nicht an PG liegt. Ich habe alle Logs aktiviert und folgende Einträge erhalten:

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:27 DEBUG: StartTransaction

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:27 DEBUG: name: unnamed; blockState: DEFAULT; state: INPROGR, xid/subid/cid: 12837418/1/0, nestlvl: 1, children: <>

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:27 DEBUG: CommitTransactionCommand

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:27 DEBUG: CommitTransaction

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:27 DEBUG: name: unnamed; blockState: STARTED; state: INPROGR, xid/subid/cid: 12837418/1/0, nestlvl: 1, children: <>

2009-01-06 08:00:27 STATEMENT: SELECT “picture” ,“picturename” FROM “public”.“spareparts_details” WHERE (“id_spareparts_details” = 448187 )

2009-01-06 08:00:28 DEBUG: StartTransactionCommand

Wo könnte das Problem nun liegen? ODBC? Access? Datentyp in PG?

Es schein mit diesem ODBC-Treiber garnicht zu gehen.

Ich hab nun ein etwas “speziellen” Weg eingeschlagen :slight_smile: . Ich kodiere die binäre Datei mit Base64 und lege sie als Text in die Datenbank. Sie wird zwar 1/3 größer aber ich bin unabhängig vom Datenbanktyp binary (longblob, bytea,…). Rückwärtz wird der Text wieder dekodiert und in ein Array umgewandelt, das dann direkt zur Anzeige oder in eine Datei gespeichert werden kann. Das ganze geht so ähnlich wie bei Email-Anlagen.

Gruß Sylvio

Das ist übrigens mit BYTEA auch nötig (base64 oder escaped oder hex).
Vielleicht war nur das Dein Fehler…

Andreas

Bytea sollte man auch ohne Umwege ansprechen können, nur braucht man dafür andere Funktionen. Die “regulären” Funktionen - in C geschrieben - können keine binären Daten verarbeiten. Da ist beim ersten Null-Byte Schluß.

Es ist schon komisch, dass man binäre Daten (in Form eines Array) nicht direkt in batea mit AppendChunk schreiben kann. Dafür ist es doch schließlich da (heißt doch Byte-Array - oder nicht?). In MySQL geht das problemlos.

Das Problem ist nicht PostgreSQL sondern die Programmiersprache. In C und C++ (und darin wird der ODBC Treiber wohl geschrieben sein) ist das Null-Byte (\000) ein Trenner für Strings. Enthält eine Anfrage so ein Byte, muss es speziell behandelt werden.

MySQL kennt mysql_query() (das kann nicht mit Null-Bytes umgehen) und mysql_real_query() (das kann mit Null-Bytes umgehen, weil die Länge der gesamten Anfrage als Parameter übergeben wird). Je nachdem, welche Funktion verwendet wird, funktioniert das also.

PostgreSQL kennt auch verschiedene Funktionen zum Absenden der Anfragen. PQexec() z. B. bekommt wie mysql_query() einfach nur die Anfrage, wenn da drin ein Null-Byte vorkommt ist das quasi das Ende-Zeichen - nur ist die Anfrage dann natürlich unvollständig. Elegant umgeht man dieses Problem mit PQexecParams() und gibt die Parameter einzeln samt ihrer Länge an.

Wenn der ODBC-Treiber damit nicht umgehen kann ist das natürlich unschön - die Datenbank kann es. Bedingt durch die zugrunde liegende Programmiersprache sind jedoch einige Umwege notwendig, die vielleicht nicht jeder eingehen will.

Vielen dank für die Mühe! Ich werde die Lösung mit Base64 jetzt erst mal so lassen, da mir das Andere wirklich zu aufwendig ist.

LG Sylvio

Jetzt gehts, auch ohne vorherige Verschlüsselung. Es lag doch am ODBC-Treiber! In den DSN gibt es eine Einstellung die heiß ‘bytea in LO’. die muss eingeschaltete werden. Im Connectionstring muss ‘ByteaAsLongVarBinary=1;’ angehängt werden. Dann klappts zwar immer noch nicht mit AppendChunk und GetChunk (weder mit DAO noch mit ADO) aber ADO kennt ein Streaming-Object das super Dienste leistet:

zum schreiben:

      
                Dim oStream As New ADODB.Stream
                oStream.type = adTypeBinary
                oStream.Open
                oStream.LoadFromFile strFileName
                rs!Pic = oStream.Read
                rs.Update
                oStream.Close

zum lesen:

      
                Dim oStream As New ADODB.Stream
                oStream.type = adTypeBinary
                oStream.Open
                oStream.Write rs!Pic
                oStream.SaveToFile strFileName
                oStream.Close

So klappt in VB und in Access. Vielen Dank an alle Helfer.

LG Sylvio

Ich habe doch noch eine schönere Lösung mittels ‘ADODB.Stream’ gefunden. Vorrausetzung ist allerdings, dass man im ODBC-Treiber von PostGre die Option ‘bytea to LO’ setzt oder den Connectionstring mit ‘Trusted_Connection=yes;’ erweitert. Mit ‘AppendChunk’ ist trotzdem nicht viel zu machen. Hier der Code um ein Bild oder irgend eine Datei in die Datenbank (Feldtyp bytea) zu bekommen:

Dim oStream As New ADODB.Stream
...
    With RS
            .Filter = "ID=" & lngID
            'Binär speichern
            oStream.type = adTypeBinary
            oStream.Open
            oStream.LoadFromFile strPixFile
            !Pic = oStream.Read
            .Update
            oStream.Close
        End If
        .Close
        Set RS = Nothing
    End With
...

So holt man die Daten wieder aus und schreibt sie als Datei weg:

Dim oStream As New ADODB.Stream
...
    With RS
        If .RecordCount > 0 Then
                'binäre Feld in Datei File schreiben
                oStream.type = adTypeBinary
                oStream.Open
                oStream.Write !Pic
                oStream.SaveToFile strFileName
                oStream.Close
            End If
        End If
        .Close
        Set RS = Nothing
    End With
...

LG Sylvio

Bitte, bitte, der Name ist “PostgreSQL”, alternativ “Postgres”.

Postgre vs PostgreSQL vs Postgres
PostgreSQL: Frequently Asked Questions (FAQ) for PostgreSQL