PDF als bytea in Postgres-DB einfügen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Mittwoch 16. Februar 2011, 10:37

Hallo zusammen,

ich möchte gerne über ein Python-Script eine PDF-Datei in das bytea-Feld einer Tabelle in einer PG-Datenbank schreiben.

Die Tabelle sieht folgendermaßen aus:

Code: Alles auswählen

-- Table: test

-- DROP TABLE test;

CREATE TABLE test
(
  id serial NOT NULL,
  "document" bytea,
  "text" text
)
WITH (
  OIDS=FALSE
);
ALTER TABLE test OWNER TO um;
COMMENT ON TABLE test IS 'Testtabelle';

Mein Problem ist, dass ich nicht weiß, was für das bytea-Feld als Eingabe erwartet wird. Mein Versuch sieht so aus:

Code: Alles auswählen

    dateipfad = "/home/user/meinedatei.pdf"
    text = "bla bla"

    sql="""INSERT INTO test (document, text) VALUES (%s, '%s')""" % (dateipfad, text)
    
    curs.execute(sql)
    conn.commit()
        
Fehlermeldung:

Code: Alles auswählen

psycopg2.ProgrammingError: syntax error at or near "/"
LINE 1: INSERT INTO test (document, text) VALUES (/home/user/...

Kann mir da jemand weiterhelfen?

Vielen Dank für jeden Tip!!
Benutzeravatar
sparrow
User
Beiträge: 2801
Registriert: Freitag 17. April 2009, 10:28

Mittwoch 16. Februar 2011, 12:08

Versuch mal:

Code: Alles auswählen

sql = """INSERT INTO test (document, text) VALUES (?, ?)"""
cursor.execute(sql, (dateipfad, psycopg.Binary(text)) )
oder:

Code: Alles auswählen

sql = """INSERT INTO test (document, text) VALUES (?, ?)"""
cursor.execute(sql, (dateipfad, psycopg2.Binary(text)) )
Ich bin mir gerade nicht sicher und bin nicht daheim um zu schauen ;)

Gruß
Sparrow


Edit: zweites Beispiel sollte funktionieren. Das Lesen funktioniert dann ohne Spezialbehandlung.
Zuletzt geändert von sparrow am Mittwoch 16. Februar 2011, 12:28, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Mittwoch 16. Februar 2011, 12:12

Willst Du das gesamte Dokument in die DB schreiben, oder nur den Pfad? Bei ersterem musst Du die Datei vorher wohl erst einlesen und den Inhalt der Query übergeben, bei letzterem verstehe ich nicht, wieso Du keinen String als Typen wählst.

Das Hauptproblem ist der falsche (und gefährliche!) Aufbau der dynamischen Query, aber das hat Sparrow ja schon beantwortet.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Freitag 18. Februar 2011, 09:19

Hallo ihr zwei,

vielen Dank für euere Antworten!!

Ja, ich möchte die Datei selbst in der DB speichern und nicht den Pfad als String. Ich hab jetzt eueren Rat befolgt, die Datei erst einzulesen (Reicht mir da ein open?), was dann so aussieht:

Code: Alles auswählen

	text = "bla bla"
	sql="""INSERT INTO um.test (document, text) VALUES ('%s'::bytea, '%s')""" % (dateipfad, text)

	# Datei einlesen
	file = open ("/home/user/myfile.pdf","r")

	# DB-Verbindung herstellen
	conn = initDBConnection()
	curs = createCursor(conn)
	 
	# Daten zur DB schicken
	curs.execute(sql, (file, psycopg2.Binary(text)) )
	conn.commit()
		
	# DB-Verbindung schließen
	closeConnection(conn)	
Fehlermeldung bekomme ich jetzt keine mehr und in der Datenbank steht zunächst mal der Pfad als Text drin. Haut das so hin, wie ich es versucht habe?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Freitag 18. Februar 2011, 09:24

andi24 hat geschrieben:Hallo ihr zwei,

vielen Dank für euere Antworten!!
Nuja, Du ignorierst die Inhalte ja teilweise ;-)

Das Zusammenbauen Deines Strings ist immer noch genauso wie vorher!
andi24 hat geschrieben: Fehlermeldung bekomme ich jetzt keine mehr und in der Datenbank steht zunächst mal der Pfad als Text drin. Haut das so hin, wie ich es versucht habe?
Nein! Lies doch mal dir Doku zu open() und weiterführend zu file-Objekten. Du musst den Inhalt schon noch einlesen. Außerdem solltest Du das im Binärmodus erledigen:

Code: Alles auswählen

with open(filename, "rb") as handler:
    # hier kannst Du dann die Daten einlesen
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Freitag 18. Februar 2011, 09:56

Oh, entschuldige - das kann natürlich nicht funktionieren, wenn ich die Datei nur öffen, sie aber nicht einlese. Man sollte nicht vor dem ersten Kaffee programmieren :-)

Ich probiers gleich nochmal ...
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Freitag 18. Februar 2011, 10:03

So, neuer Versuch:

Code: Alles auswählen

	text = "bla bla"

	# Datei einlesen
	with open ("/home/user/meinedatei.pdf","rb") as f:
		filedata = f.read()
	f.closed

	# DB-Verbindung herstellen
	conn = initDBConnection()
	curs = createCursor(conn)
	
	sql="""INSERT INTO test (document, text) VALUES ('%s'::bytea, '%s')""" % (filedata, text)
	 
	#curs.execute(sql)
	curs.execute(sql, (filedata, psycopg2.Binary(text)) )
	conn.commit()
		

	# DB-Verbindung schließen
	closeConnection(conn)	
Jetzt gibts zumindest mal eine Fehlermeldung:

Code: Alles auswählen

    curs.execute(sql, (filedata, psycopg2.Binary(text)) )
IndexError: tuple index out of range
...verstehen tu ich sie nur leider nicht. Was sollte er denn da für ein Index-Problem haben?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Freitag 18. Februar 2011, 10:16

Code: Alles auswählen

f.closed
gibt schon mal keinen Sinn - läuft das denn durch? Der Vorteil won "with" ist ja grad, dass man Dateien nicht explizit schließen muss, sondern dass Python sich da selber drum kümmert.

Zum Fehler: Du solltest Dir mal genau angucken, was Sparrow Dir geschrieben hat! Genau! Dann klappt's sicher auch. ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
sparrow
User
Beiträge: 2801
Registriert: Freitag 17. April 2009, 10:28

Freitag 18. Februar 2011, 10:19

Warum verwendest du das SQL-Statement nicht wie von mir vorgeschlagen?
Und warum führst du dort einen expliziten Cast durch? Hat es sonst nicht funktioniert?
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Freitag 18. Februar 2011, 10:32

Oh, stimmt, ich hatte das SQL-Statement garnicht übernommen. Hier die neue Version:

Code: Alles auswählen

	text = "bla bla"

	# Datei einlesen
	with open ("/home/user/meinedatei.pdf","rb") as f:
		filedata = f.read()


	# DB-Verbindung herstellen
	conn = initDBConnection()
	curs = createCursor(conn)
	
	sql="""INSERT INTO test (document, text) VALUES (?, ?)"""
	 
	curs.execute(sql, (filedata, psycopg2.Binary(text)) )
	conn.commit()
		

	# DB-Verbindung schließen
	closeConnection(conn)	
Aber wo sollte ich einen expliziten Cast durchführen?

Bekommen tu ich zu diesem Code jetzt komischerweise einen Syntaxfehler ... seltsam:

Code: Alles auswählen

    curs.execute(sql, (filedata, psycopg2.Binary(text)) )
psycopg2.ProgrammingError: syntax error at or near ","
LINE 1: INSERT INTO test (document, text) VALUES (?, ?)
Hab das doch genau gemacht wie von Dir vorgeschlagen? Oder überseh ich da was?
Benutzeravatar
/me
User
Beiträge: 3446
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Freitag 18. Februar 2011, 10:33

andi24 hat geschrieben:

Code: Alles auswählen

	sql="""INSERT INTO test (document, text) VALUES ('%s'::bytea, '%s')""" % (filedata, text)
Bitte schau dir an, was in diesem Thread bereits geschrieben wurde. Man baut NIEMALS ein SQL-Statement aus Rohdaten so zusammen. Damit reißt du dir Sicherheitslöcher in Scheunentorgröße ins System.

Das passt ja ohnehin nicht zum später erfolgenden execute().
Benutzeravatar
/me
User
Beiträge: 3446
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Freitag 18. Februar 2011, 10:42

andi24 hat geschrieben:

Code: Alles auswählen

	sql="""INSERT INTO test (document, text) VALUES (?, ?)"""
	 
	curs.execute(sql, (filedata, psycopg2.Binary(text)) )
Unterstützt psycopg2 wirklich die "?"-Syntax? Ein ganz kurzer Blick auf http://packages.python.org/psycopg2/usa ... ql-queries zeigt mir da eine Variante mit %s.
Benutzeravatar
sparrow
User
Beiträge: 2801
Registriert: Freitag 17. April 2009, 10:28

Freitag 18. Februar 2011, 14:08

Andi24 hat geschrieben:Aber wo sollte ich einen expliziten Cast durchführen?
::bytea ist ein ein Cast. Du machst also aus irgendetwas ein bytea-Objekt. Das hat mich verwundert.
/me hat geschrieben:Unterstützt psycopg2 wirklich die "?"-Syntax? Ein ganz kurzer Blick auf http://packages.python.org/psycopg2/usa ... ql-queries zeigt mir da eine Variante mit %s.
Deshalb hab ich ja gefragt ob die Query nicht funkitoniert.

Code: Alles auswählen

        sql="""INSERT INTO test (document, text) VALUES (%s, %s)"""         
        curs.execute(sql, (filedata, psycopg2.Binary(text)) )
@Andi24: das von /me verlinkte Dokument enthält viele wichtige Informationen. Auch warum man den String nicht selsbt zusammen klebt sondern die Werte als Parameter bei execute mitgibt. Stichwort: SQL-Injection.
andi24
User
Beiträge: 56
Registriert: Freitag 5. März 2010, 11:42

Dienstag 22. Februar 2011, 10:58

Hallo zusammen,

entschuldigt bitte die späte Reaktion, aber ich hatte jetzt für ein paar Tage keine Zeit daran weiterzuarbeiten :-)

@/me:
Vielen Dank für den Link - die Infos dort haben mir weitergeholfen. Ich weiß nur nicht, ob ich sie richtig umsetzen konnte :-) Mein neuer Versuch:

Code: Alles auswählen

	dateipfad = "/home/user/mypdf.pdf"
	mytext = "bla bla"

	# Datei einlesen
	with open (dateipfad,"rb") as f:
		filedata = f.read()

	# DB-Verbindung herstellen
	conn = initDBConnection()
	curs = createCursor(conn)
	
	curs.execute(
		"""INSERT INTO um.test (document, text) VALUES (%(bytea)s, %(text)s);""",
		{'bytea':filedata, 'text':mytext})

	conn.commit()
		
	# DB-Verbindung schließen
	closeConnection(conn)	
bringt die Fehlermeldung:

Code: Alles auswählen

psycopg2.DataError: invalid input syntax for type bytea
LINE 1: INSERT INTO um.test (document, text) VALUES (E'%PDF-1.4

Lieg ich denn da mit dem "bytea" falsch? Ich hab das Datenbankfeld jedenfalls als bytea definiert...
Bin langsam echt am Verzweifeln :-(
Benutzeravatar
sparrow
User
Beiträge: 2801
Registriert: Freitag 17. April 2009, 10:28

Dienstag 22. Februar 2011, 11:42

Öhm... ja.
Du machst es ja auch wieder anders?

Du musst den Inhalt des Binären-Feldes einmal neu encoden. Darum geht es den ganzen Thread. Und dafür gibt es: psycopg2.Binary(value)

Also:

Code: Alles auswählen

        curs.execute(
                """INSERT INTO um.test (document, text) VALUES (%(bytea)s, %(text)s);""",
                {'bytea' : psycopg2.Binary(filedata), 'text' : mytext})
Antworten