Benutzer-Werkzeuge

Webseiten-Werkzeuge


python:psycopg
  • Postgres-Datenbank-Treiber für Python

Transaction-Handling

  • jede Aktion in der Datenbank (jedes aufrufen von cursor.execute()) erzeugt eine Transaktion (wenn nicht eine offene besteht)
  • per Default ist autocommit=False für Datenbankverbindungen
    • nach dem Absetzen von cursor.execute wird die Transaktion also nicht committed
  • es muss also explizit committed werden

Es gibt mehrere Möglichkeiten das commit oder rollback über Context-Blöcke zu steuern

with psycopg.connect(someconnectionninformation) as connection:
    cursor = connection.cursor()
    cursor.execute(somestatement)
    cursor.execute(someotherstatement)

Außerdem wird die Verbindung zur Datenbank danach geschlossen

with psycopg.connect(someconnectionninformation) as connection:
    cursor = connection.cursor()
    with connection.transaction():
        cursor.execute(somestament)
        cursor.execute(somestament)
    with connection.transaction():
        cursor.execute(somestament)
        cursor.execute(somestament)

Man muss allerdings aufpassen, wenn außerhalb der transaction-Blöcke Statements ausgeführt werden, dann starten diese implizit Transaktionen und die transaction-Blöcke erstellen dann nur savepoint sub-transactions.
Ggf. sollte man beim connect dann autocommit=True setzen, dann werden alle Statements außerhalb der transaction-Blöcke als eigene Transaktionen gehandhabt, die sofort commited sind.

Server-side binding

  • Technik bei der das SQL-Statement und die Werte getrennt an die Datenbank gesendet werden
  • Ziel ist u.a. die Vermeidung von sql-Injection durch Trennung von Statement und Werten
    • die Datenbank wertet Werte nicht aus → dadurch können sie keine Statements enthalten
  • ist auf Statement limitiert
    • ein execute()-Call kann nur ein Statement enthalten das Server-side-binding macht

conn.execute(insert into whatever values(%s); select * from somethingelse where id = %s, ("firstvalue", "secondvalue"))

  • obiges funktioniert nicht
  • wenn mehrere Statements vorhanden sind, aber nur eins Server-side-binding nutzt funktioniert das

Mischen von Transaktions- und Nicht-Transaktionsstatements in einem execute

  • die meisten Kommandos (auch DDL-Kommandos) können in einer Transaktion ausgeführt werden
    • es gibt aber Ausnahmen wie „create database“
  • Postgres fasst intern alle Statements eines cursor()-Aufrufs zu einer Transaktion zusammen
    • oder auch
  • ^ deswegen kann man im gleichen execute()-Aufruf keine Kommandos mischen die Transaktionen unterstützen und solche die es nicht tun

Temporary tables

  • Existieren nur in der Session in der sie erstellt wurden
    • verschiedene Verbindungen zur Datenbank können also die gleiche Tabelle ggf. mit unterschiedlichem Inhalt haben

create temp table somename (somerow varchar) on commit delete rows;

  • „on commit“ definiert was bei einem Commit mit der Tabelle passieren soll
    • preserve_rows (Default in postgres, aber nicht im Standard) → erhalte die Tabelle wie sie ist, inkl. Inhalt
    • delete rows → truncate aka. lösche alle Einträge, aber erhalte die leere Tabelle
    • drop → lösche die Tabelle

Copy

  • Eigentlich eine Möglichkeit eine größere Menge Einträge aus Dateien (oder stdin) einzulesen oder nach stout/Datei auszugeben
  • ist meist effizienter als insert into
  • statt einer Tabelle kann man auch sub-select-Statements benutzen
  • schlägt das einfügen eines Datensatzes fehl wird alles rückabgewickelt
    • im Gegensatz zu insert into gibt es kein „on conflict“ → alles oder nichts
    • es dürfen also auch keine Doubletten in den Daten sein (die einen unique-Spalte betreffen würden)

Daten in eine Tabelle einfügen:

    with cursor.copy("copy table_name (column1, column2) from stdin") as copy:
        for record in records:
            copy.write_row(record)

  • record muss dabei ein iterateable sein, auch wenn nur eine Spalte eingefügt wird
    • also zum Beispiel ein tuple
    • liegt daran das es auch mehrere Spalten sein könnten → (spalte1, spalte2 usw.)

Daten aus einer Tabelle auslesen:

    with cursor.copy("copy table_name (column1, column2) to stdout") as copy:
        for record in copy.rows():
            Some action

Traceback (most recent call last):
  File "mytest.py", line 24, in <module>
    copy.write_row(word)
  File "/usr/lib/python3.8/contextlib.py", line 120, in __exit__
    next(self.gen)
  File "usr/share/virtualenvs/noise3-IfVx2oqf/lib/python3.8/site-packages/psycopg/cursor.py", line 902, in copy
    raise ex.with_traceback(None)
psycopg.errors.BadCopyFileFormat: extra data after last expected column
CONTEXT:  COPY personen, line 1: "H a n s"

Daher wurde „Getting“ nicht als String, sondern als Sequenz betrachtet und versucht in mehr Spalten einzufügen als vorhanden sind.

Fehlschlagender Code (Beispiel):

names = ["Hans", "Peter", "Dieter"]
with cursor.copy("copy personen (vorname) from stdin") as copy:
    for name in names:
        copy.write_row(name)

  • Daten werden aus einem iterateable names gelesen
  • name ist eine Zeichenkette
  • copy.write_row erwartet aber einen Datensatz
    • ein Datensatz ist ein iterateable → ein tuple, eine Liste usw.
    • hier wurde ein String übergeben → ein String ist auch ein iterateable → also wird über den String iteriert
    • da der String länger ist als die Menge der Spalten → es gibt nur eine, kommt es zum Fehler

Hier sollte man auch aufpassen:

names = ["Hans", "Peter", "Dieter"]
with cursor.copy("copy personen (vorname) from stdin") as copy:
    for name in names:
        copy.write_row((name))

  • name wird hier bei der Übergabe an copy.write_row scheinbar auf tuple gecastet
    • allerdings nicht wirklich, es fehlt ein „,“
      • es muss (name,) heißen, sonst ist es ein String
python/psycopg.txt · Zuletzt geändert: 2023/07/01 00:00 von root