Zum Inhalt springen

Datenbank zu Code (ORMs)

Hier werde ich Ihnen erklären, wie SQLModel mit der Datenbank interagiert, warum Sie es (oder ein ähnliches Tool) verwenden möchten und wie es sich zu SQL verhält.

SQL Inline im Code

Sehen wir uns dieses Beispiel einer einfachen SQL-Abfrage an, um alle Daten aus der hero-Tabelle zu erhalten

SELECT *
FROM hero;

Und diese SQL-Abfrage würde die Tabelle zurückgeben

idnamesecret_nameageteam_id
1DeadpondDive Wilsonnull2
2Spider-BoyPedro Parqueadornull1
3Rusty-ManTommy Sharp481

Diese SQL-Sprache hat einen kleinen Vorbehalt. Sie wurde nicht dafür entwickelt, mit normalem Code in einer Programmiersprache wie Python vermischt zu werden. 🚨

Wenn Sie also mit Python arbeiten, wäre die einfachste Option, SQL-Code in einen String zu packen und diesen String direkt an die Datenbank zu senden.

statement = "SELECT * FROM hero;"

results = database.execute(statement)

Aber in diesem Fall hätten Sie keine Editor-Unterstützung, keine Inline-Fehler, keine Autovervollständigung usw. Denn für den Editor ist die SQL-Anweisung nur ein Textstring. Wenn Sie einen Fehler haben, könnte der Editor nicht helfen. 😔

Und noch wichtiger ist, dass Sie in den meisten Fällen die SQL-Strings mit Modifikationen und Parametern senden würden. Zum Beispiel, um die Daten für eine bestimmte Element-ID, einen Datumsbereich usw. abzurufen.

Und in den meisten Fällen stammen die Parameter, die Ihr Code zum Abfragen oder Ändern der Daten in der Datenbank verwendet, in irgendeiner Weise von einem externen Benutzer.

Sehen Sie sich zum Beispiel diese SQL-Abfrage an

SELECT *
FROM hero
WHERE id = 2;

Sie verwendet den ID-Parameter 2. Diese Zahl 2 stammt wahrscheinlich in irgendeiner Weise von einer Benutzereingabe.

Der Benutzer teilt Ihrer Anwendung wahrscheinlich auf irgendeine Weise mit

Hey, ich möchte den Helden mit der ID

2

Und das Ergebnis wäre diese Tabelle (mit einer einzelnen Zeile)

idnamesecret_nameageteam_id
2Spider-BoyPedro Parqueadornull1

SQL-Injection

Aber nehmen wir an, Ihr Code nimmt alles, was der externe Benutzer bereitstellt, und fügt es in den SQL-String ein, bevor er es an die Datenbank sendet. So etwas wie das hier

# Never do this! 🚨 Continue reading.

user_id = input("Type the user ID: ")

statement = f"SELECT * FROM hero WHERE id = {user_id};"

results = database.execute(statement)

Wenn der externe Benutzer tatsächlich ein Angreifer ist, könnte er Ihnen einen bösartigen SQL-String senden, der etwas Schreckliches tut, wie z.B. alle Datensätze zu löschen. Das nennt man eine "SQL-Injection".

Stellen Sie sich zum Beispiel vor, dieser neue Angreifer sagt

Hey, ich möchte den Helden mit der ID

2; DROP TABLE hero

Dann würde der obige Code, der die Benutzereingabe nimmt und in SQL einfügt, tatsächlich Folgendes an die Datenbank senden

SELECT * FROM hero WHERE id = 2; DROP TABLE hero;

Schauen Sie sich den am Ende hinzugefügten Abschnitt an. Das ist eine weitere vollständige SQL-Anweisung

DROP TABLE hero;

So weisen Sie der Datenbank in SQL an, die gesamte Tabelle hero zu löschen.

Nein! Wir haben alle Daten in der hero-Tabelle verloren! 💥😱

SQL-Bereinigung

Der Prozess, um sicherzustellen, dass alles, was der externe Benutzer sendet, sicher für die Verwendung im SQL-String ist, wird als Bereinigung bezeichnet.

Dies ist in SQLModel (dank SQLAlchemy) standardmäßig enthalten. Und viele andere ähnliche Tools bieten diese Funktionalität zusammen mit vielen anderen Funktionen.

Jetzt sind Sie bereit für einen Witz von xkcd

Exploits of a Mom

SQL mit SQLModel

Mit SQLModel verwenden Sie anstelle der direkten Eingabe von SQL-Anweisungen Python-Klassen und -Objekte, um mit der Datenbank zu interagieren.

Sie könnten die Datenbank zum Beispiel mit diesem Code nach demselben Helden mit der ID 2 fragen

user_id = input("Type the user ID: ")

session.exec(
    select(Hero).where(Hero.id == user_id)
).all()

Wenn der Benutzer diese ID angibt

2

...wäre das Ergebnis diese Tabelle (mit einer einzelnen Zeile)

idnamesecret_nameageteam_id
2Spider-BoyPedro Parqueadornull1

SQL-Injections verhindern

Wenn der Benutzer ein Angreifer ist und versucht, dies als "ID" zu senden

2; DROP TABLE hero

Dann wandelt SQLModel dies in einen literalen String "2; DROP TABLE hero" um.

Und dann weist es die SQL-Datenbank an, zu versuchen, einen Datensatz mit genau dieser ID zu finden, anstatt die Attacke einzuschleusen.

Der Unterschied in der endgültigen SQL-Anweisung ist subtil, aber er ändert die Bedeutung vollständig

SELECT * FROM hero WHERE id = "2; DROP TABLE hero;";

Tipp

Beachten Sie die doppelten Anführungszeichen ("), die dies zu einem String und nicht zu roherem SQL machen.

Die Datenbank wird keinen Datensatz mit dieser ID finden

"2; DROP TABLE hero;"

Dann sendet die Datenbank eine leere Tabelle als Ergebnis, da sie keinen Datensatz mit dieser ID gefunden hat.

Dann wird Ihr Code weiter ausgeführt und dem Benutzer ruhig mitteilen, dass nichts gefunden wurde.

Aber wir haben die hero-Tabelle nie gelöscht. 🎉

Info

Natürlich gibt es auch andere Möglichkeiten, SQL-Daten zu bereinigen, ohne ein Tool wie SQLModel zu verwenden, aber es ist trotzdem ein nettes Feature, das Sie standardmäßig erhalten.

Editor-Unterstützung

Schauen Sie sich den Python-Snippet oben noch einmal an.

Da wir Standard-Python-Klassen und -Objekte verwenden, kann Ihr Editor Ihnen Autovervollständigung, Inline-Fehler usw. bieten.

Zum Beispiel möchten Sie vielleicht die Datenbank abfragen, um einen Helden anhand seiner geheimen Identität zu finden.

Vielleicht erinnern Sie sich nicht mehr, wie Sie die Spalte genannt haben. Vielleicht war es

  • secret_identity?

...oder war es

  • secretidentity?

...oder

  • private_name?
  • secret_name?
  • secretname?

Wenn Sie das in SQL-Strings in Ihrem Code eingeben, kann Ihnen Ihr Editor nicht helfen

statement = "SELECT * FROM hero WHERE secret_identity = 'Dive Wilson';"

results = database.execute(statement)

...Ihr Editor wird dies als einen langen String mit Text darin sehen, und er wird nicht in der Lage sein, zu autovervollständigen oder den Fehler in secret_identity zu erkennen.

Aber wenn Sie gängige Python-Klassen und -Objekte verwenden, wird Ihr Editor Ihnen helfen können

database.execute(
    select(Hero).where(Hero.secret_name == "Dive Wilson")
).all()

ORMs und SQL

Diese Arten von Bibliotheken wie SQLModel (und natürlich SQLAlchemy), die zwischen SQL und Code mit Klassen und Objekten übersetzen, werden als ORMs bezeichnet.

ORM bedeutet Object-Relational Mapper.

Dies ist ein sehr gebräuchlicher Begriff, der aber auch aus ziemlich technischen und akademischen Konzepten stammt 👩‍🎓

  • Objekt: bezieht sich auf Code mit Klassen und Instanzen, normalerweise als "Objektorientierte Programmierung" bezeichnet, daher der Teil "Objekt".

Zum Beispiel ist diese Klasse Teil dieser objektorientierten Programmierung

class Hero(SQLModel):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None
  • Relational: bezieht sich auf die SQL-Datenbanken. Erinnern Sie sich, dass sie auch Relationale Datenbanken genannt werden, weil jede dieser Tabellen auch "Relation" genannt wird? Daher kommt das "Relationale".

Zum Beispiel diese Relation oder Tabelle

idnamesecret_nameageteam_id
1DeadpondDive Wilsonnull2
2Spider-BoyPedro Parqueadornull1
3Rusty-ManTommy Sharp481
  • Mapper: Dies kommt aus der Mathematik. Wenn etwas von einer Menge von Dingen in eine andere umgewandelt werden kann, nennt man das eine "Mapping-Funktion". Daher kommt der Mapper.

Squares to Triangles Mapper

Wir könnten auch eine Mapping-Funktion in Python schreiben, die von der Menge der Kleinbuchstaben in die Menge der Großbuchstaben umwandelt, wie hier

def map_lower_to_upper(value: str):
    return value.upper()

Es ist eigentlich eine einfache Idee mit einem sehr akademischen und mathematischen Namen. 😅

Ein ORM ist also eine Bibliothek, die von SQL zu Code und von Code zu SQL übersetzt. Alles unter Verwendung von Klassen und Objekten.

Neben SQLModel gibt es viele andere ORMs. Sie können mehr über einige davon im Abschnitt Alternativen, Inspiration und Vergleiche lesen.

SQL-Tabellennamen

Technischer Hintergrund

Dies ist ein etwas langweiliger Hintergrund für SQL-Puristen. Fühlen Sie sich frei, diesen Abschnitt zu überspringen. 😉

Bei der Arbeit mit reinem SQL ist es üblich, die Tabellen im Plural zu benennen. Die Tabelle würde also heroes statt hero heißen, da sie mehrere Zeilen enthalten könnte, jede mit einem Helden.

Dennoch können SQLModel und viele andere ähnliche Tools einen Tabellennamen automatisch aus Ihrem Code generieren, wie Sie später im Tutorial sehen werden.

Aber dieser Name wird von einem Klassennamen abgeleitet. Und es ist üblich, einfache Namen für Klassen zu verwenden (z.B. class Hero statt class Heroes). Die Verwendung einfacher Namen für Klassen wie class Hero macht Ihren Code auch intuitiver.

Sie werden Ihren eigenen Code viel öfter sehen als die internen Tabellennamen, daher ist es wahrscheinlich besser, die Code-/Klassenkonvention beizubehalten als die SQL-Konvention.

Um die Dinge konsistent zu halten, werde ich weiterhin die gleichen Tabellennamen verwenden, die SQLModel generiert hätte.

Tipp

Sie können den Tabellennamen auch überschreiben. Darüber können Sie im Advanced User Guide lesen.