====== Datenbanken & SQL ====== Daten dauerhaft ablegen, abfragen und ändern: ''std.db'' spricht SQLite über die C-Bibliothek, PostgreSQL, MySQL und Redis dagegen **nativ über das jeweilige Netzprotokoll** — ohne Client-Bibliothek. Diese Seite zeigt den Arbeitsweg je Backend, die gemeinsamen Muster (Prepared Statements, Transaktionen, Ressourcen) und benennt, was heute trägt. → [[lyx_-_programmiersprache:units:db|std.db — Unit-Referenz]] · [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]] · [[lyx_-_programmiersprache:sprache:capabilities|Capabilities]] ---- ===== 1. Welches Backend? ===== ^ Aufgabe ^ Unit ^ Verbindung ^ Zustand ^ | Eingebettete Datenbank, eine Datei | ''std.db.sqlite'' | libsqlite3 | **einsatzfähig** — außer ''REAL'', siehe Abschnitt 5 | | Relationale Datenbank im Netz | ''std.db.postgres'' | natives Protokoll v3 | einsatzfähig — Host **nur als IPv4** | | MySQL / MariaDB | ''std.db.mysql'' | natives Protokoll 4.1 | Abfragen ja, **Prepared Statements nein** — Abschnitt 8 | | Cache, Warteschlange, Zähler | ''std.db.redis'' | RESP | ⚠️ lokaler Server nicht erreichbar, Abschnitt 9 | | Sehr kleiner Redis-Zugriff | ''std.db.redis_simple'' | RESP | nur ''Set''/''Get''/''Ping'' | | Graphdaten (Knoten, Kanten) | ''std.graphdb'' | eigene Datei | eigenständige Familie, 7 Units | | MongoDB | ''std.net.mongo'' | Wire Protocol | liegt bei den Netz-Units, nicht unter ''db'' | Faustregel: **SQLite, solange es geht.** Eine Datei, kein Server, kein Netz, keine Zugangsdaten — und für die allermeisten Anwendungen schnell genug. Ein Server-Backend lohnt, wenn mehrere Prozesse gleichzeitig schreiben oder die Daten getrennt vom Programm leben sollen. ---- ===== 2. SQLite — der vollständige Weg ===== import std.io; import std.db.sqlite; fn main(): int64 { var db: int64 := SQLiteOpen("kunden.db"c); // ":memory:" für flüchtige DB if (db == 0) { PrintLn("Öffnen fehlgeschlagen"); return 1; } if (!SQLiteExec(db, "CREATE TABLE IF NOT EXISTS kunden (id INTEGER PRIMARY KEY, name TEXT, umsatz_cent INTEGER)"c)) { PrintLn(SQLiteErrmsg(db)); SQLiteClose(db); return 1; } // Einfügen mit Prepared Statement var stmt: int64 := SQLiteStmtPrepare(db, "INSERT INTO kunden (name, umsatz_cent) VALUES (?, ?)"c); if (stmt == 0) { PrintLn(SQLiteErrmsg(db)); SQLiteClose(db); return 1; } SQLiteBindStr(stmt, 1, "Meier GmbH"c); SQLiteBindInt(stmt, 2, 125050); SQLiteStmtStep(stmt); SQLiteStmtReset(stmt); // dasselbe Statement erneut verwenden SQLiteClearBindings(stmt); SQLiteBindStr(stmt, 1, "Schulze AG"c); SQLiteBindInt(stmt, 2, 89000); SQLiteStmtStep(stmt); SQLiteStmtFinalize(stmt); Print("letzte ID: "); PrintLn(IntToStr(SQLiteLastInsertId(db))); Print("geaendert: "); PrintLn(IntToStr(SQLiteChanges(db))); // Abfragen var q: int64 := SQLiteStmtPrepare(db, "SELECT id, name FROM kunden WHERE umsatz_cent > ? ORDER BY umsatz_cent DESC"c); SQLiteBindInt(q, 1, 50000); while (SQLiteStmtStep(q) == SQLITE_ROW) { var name: pchar := SQLiteColumnText(q, 1); Print(IntToStr(SQLiteColumnInt(q, 0))); Print(" | "); PrintLn(name); } SQLiteStmtFinalize(q); SQLiteClose(db); return 0; } Ausgabe: letzte ID: 2 geaendert: 1 1 | Meier GmbH 2 | Schulze AG ==== Die vier Schritte eines Statements ==== - **''SQLiteStmtPrepare''** übersetzt das SQL einmal. Rückgabe ''0'' heißt Fehler — ''SQLiteErrmsg'' nennt den Grund. - **''SQLiteBind…''** setzt die Werte. Die Parameter sind **1-basiert**. - **''SQLiteStmtStep''** führt aus. ''SQLITE_ROW'' (100) heißt „eine Zeile liegt bereit", ''SQLITE_DONE'' (101) „fertig". - **''SQLiteStmtFinalize''** gibt das Statement frei. Ohne diesen Aufruf bleibt es am Verbindungsobjekt hängen. Die Spalten in ''SQLiteColumn…'' sind dagegen **0-basiert**. Diese Asymmetrie stammt aus SQLite selbst und ist die häufigste Fehlerquelle beim Einstieg. ^ Werte setzen ^ Werte lesen ^ | ''SQLiteBindInt'', ''SQLiteBindStr'', ''SQLiteBindFloat'', ''SQLiteBindNull'', ''SQLiteBindBlob'' | ''SQLiteColumnInt'', ''SQLiteColumnText'', ''SQLiteColumnFloat'', ''SQLiteColumnBlob'', ''SQLiteColumnBytes'' | | — | ''SQLiteColumnIsNull'', ''SQLiteColumnType'', ''SQLiteColumnCount'', ''SQLiteColumnName'' | ==== Ohne Ergebnismenge ==== ''SQLiteExec(db, sql)'' führt ein Statement in einem Schritt aus — richtig für ''CREATE TABLE'', ''PRAGMA'', ''DELETE'' ohne Parameter. **Niemals** mit selbst zusammengesetztem SQL aus fremden Daten (Abschnitt 4). ==== Verwaltung ==== ^ Funktion ^ Zweck ^ | ''SQLiteLastInsertId'' | ID der zuletzt eingefügten Zeile | | ''SQLiteChanges'' | Zahl der geänderten Zeilen der letzten Anweisung | | ''SQLiteTableExists'', ''SQLiteDropTable'' | Schema prüfen und aufräumen | | ''SQLiteSetJournalMode(db, "WAL"c)'' | WAL statt Rollback-Journal — mehrere Leser neben einem Schreiber | | ''SQLiteSetCacheSize'' | Seiten im Cache | | ''SQLiteVacuum'' | Datei verdichten | | ''SQLiteErrmsg'', ''SQLiteErrno'' | Klartext und Code des letzten Fehlers | Für jeden Dienst, der nebenläufig liest, gehört ''SQLiteSetJournalMode(db, "WAL"c)'' direkt hinter das ''SQLiteOpen''. ---- ===== 3. Transaktionen ===== if (!SQLiteBegin(db)) { PrintLn(SQLiteErrmsg(db)); return 1; } var ok: bool := SQLiteExec(db, "UPDATE konto SET stand = stand - 100 WHERE id = 1"c); if (ok) { ok := SQLiteExec(db, "UPDATE konto SET stand = stand + 100 WHERE id = 2"c); } if (ok) { SQLiteCommit(db); } else { SQLiteRollback(db); PrintLn(SQLiteErrmsg(db)); } * ''SQLiteBegin'' beginnt verzögert (die Sperre entsteht erst beim ersten Schreiben). ''SQLiteBeginImmediate'' nimmt die Schreibsperre sofort — richtig, wenn zwei Prozesse um dieselbe Datei konkurrieren, sonst droht ''SQLITE_BUSY'' mitten in der Transaktion. ''SQLiteBeginExclusive'' sperrt die ganze Datei. * ''SQLiteInTransaction'' beantwortet, ob eine läuft. * **Jeder Fehlerpfad braucht sein ''Rollback''.** Ein früher ''return'' zwischen ''Begin'' und ''Commit'' hinterlässt eine offene Transaktion und damit eine gesperrte Datei. * Ein Massenimport gehört in **eine** Transaktion. Ohne sie schreibt SQLite jede einzelne Zeile mit eigener Synchronisierung auf die Platte — der Unterschied liegt bei Faktor Hundert. PostgreSQL kennt zusätzlich Sicherungspunkte: ''PGSavepoint'', ''PGRollbackTo'', ''PGReleaseSavepoint'', dazu ''PGBeginReadOnly'' und ''PGBeginSerializable''. ---- ===== 4. SQL-Injection: Prepared Statements sind nicht optional ===== Falsch — und zwar unabhängig davon, wie sicher die Quelle wirkt: // NIEMALS so: var sql: int64 := alloc(512); StrReplace(sql as pchar, "SELECT * FROM kunden WHERE name = 'X'"c, "X"c, eingabe); SQLiteExec(db, sql as pchar); Richtig: var q: int64 := SQLiteStmtPrepare(db, "SELECT id FROM kunden WHERE name = ?"c); SQLiteBindStr(q, 1, eingabe); while (SQLiteStmtStep(q) == SQLITE_ROW) { … } SQLiteStmtFinalize(q); Der Unterschied ist nicht Bequemlichkeit, sondern die Grenze zwischen **Code und Daten**: Ein gebundener Wert kann das Statement nicht mehr verändern, egal was darin steht. Ein zusammengesetzter String kann es immer. Dieselbe Regel bei PostgreSQL: ''PGStmtPrepare'' + ''PGBind…'' + ''PGStmtExecute''. Bei Redis sind Schlüssel und Werte ohnehin getrennte Protokollfelder — dort entsteht das Problem nicht. **Bei MySQL/MariaDB steht dieser Schutz derzeit nicht zur Verfügung**, siehe Abschnitt 8. **Ausnahme, die keine ist:** Tabellen- und Spaltennamen lassen sich nicht binden. Wo sie variabel sein müssen, gehören sie gegen eine **feste Liste erlaubter Namen** geprüft, nicht maskiert. ---- ===== 5. Zahlen mit Nachkommastellen ===== > **''REAL''-Spalten verlieren ihren Wert.** ''SQLiteBindFloat(st, 1, 1250.5)'' speichert ''0.0'', und ''SQLiteColumnFloat'' liefert ''0'' — auch bei einem Wert, den SQL selbst korrekt sieht (''SELECT CAST(f*10 AS INTEGER)'' rechnet richtig). Betroffen sind beide Richtungen; ''INTEGER'' und ''TEXT'' arbeiten einwandfrei. Geprüft mit lyxc 1.0.16F und 1.0.16F. > > Bis zur Behebung: Beträge und Messwerte als **ganze Zahlen** führen — Geld in Cent, Längen in Millimetern, Prozent in Hundertsteln. Das ist bei Geld ohnehin die richtige Wahl, weil binäre Gleitkommazahlen 0,10 € nicht exakt darstellen können. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1329|Issue #1329]]) // statt umsatz REAL: CREATE TABLE kunden (id INTEGER PRIMARY KEY, name TEXT, umsatz_cent INTEGER) // Anzeige: 125050 -> "1250,50" var euro: int64 := cent / 100; var rest: int64 := cent % 100; ---- ===== 6. Capabilities und die Sandbox ===== > **Mit ''@capabilities'' stirbt ''std.db.sqlite'' beim ersten Zugriff.** Der Prozess endet mit ''Ungültiger Betriebssystemaufruf'' (Exit 159) — der seccomp-Filter kennt ''fcntl'' nicht, das SQLite für seine Dateisperren braucht. Gewährte ''fs''-Capabilities ändern daran nichts. > > Ein SQLite-Programm läuft damit heute **entweder** gehärtet **oder** überhaupt — nicht beides. Für Server-Backends (PostgreSQL, MySQL, Redis) gilt das nicht; dort genügen die Netz-Capabilities. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1276|Issue #1276]]) Für die Netz-Backends: @capabilities([system.exit, system.memory.heap, network.tcp.connect]) import std.io grant [system.write]; import std.db.postgres grant [network.tcp.connect, system.memory.heap]; → Die vollständige Capability-Matrix steht im [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]]. ---- ===== 7. PostgreSQL ===== ''std.db.postgres'' spricht das Frontend/Backend-Protokoll v3 selbst — inklusive MD5-Authentifizierung, Prepared Statements und Transaktionssteuerung. import std.io; import std.db.postgres; fn main(): int64 { var conn: int64 := PGConnect("127.0.0.1"c, 5432, "app"c, "geheim"c, "kundendb"c); if (conn == 0 || PGIsConnected(conn) == 0) { PrintLn(PGError(conn)); return 1; } var v: pchar := PGServerVersion(conn); Print("Server: "); PrintLn(v); var res: int64 := PGQuery(conn, "SELECT id, name FROM kunden ORDER BY id"c); if (res == 0) { PrintLn(PGError(conn)); PGClose(conn); return 1; } Print("Zeilen: "); PrintLn(IntToStr(PGNumRows(res))); while (PGFetchRow(res) != 0) { var name: pchar := PGGetStr(res, 1); Print(IntToStr(PGGetInt(res, 0))); Print(" | "); PrintLn(name); } PGFreeResult(res); PGClose(conn); return 0; } > **Der Host muss eine IPv4-Adresse in Textform sein.** ''pgTCPConnect'' zerlegt die Zeichenkette Byte für Byte (''pgParseIPv4Byte'') und fragt kein DNS — ''"localhost"'' oder ''"db.example.com"'' führen zu keiner Verbindung. Namen also vorher selbst auflösen (''DNSResolveGoogle'' aus ''std.net.dns'', siehe [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]]) oder die Adresse konfigurieren. ^ Aufgabe ^ Funktion ^ | Verbindung | ''PGConnect'', ''PGClose'', ''PGIsConnected'', ''PGServerVersion'', ''PGBackendPID'' | | Abfrage | ''PGQuery'', ''PGFreeResult'' | | Ergebnis | ''PGNumRows'', ''PGNumFields'', ''PGAffectedRows'', ''PGFetchRow'', ''PGDataSeek'' | | Werte | ''PGGetStr'', ''PGGetInt'', ''PGGetFloat'', ''PGGetBool'', ''PGIsNull'' | | Spalten | ''PGGetFieldName'', ''PGGetFieldTypeOid'' | | Prepared | ''PGStmtPrepare'', ''PGBindInt''/''Str''/''Float''/''Bool''/''Null'', ''PGStmtExecute'', ''PGStmtClose'', ''PGStmtFree'' | | Transaktion | ''PGBegin'', ''PGCommit'', ''PGRollback'', ''PGBeginReadOnly'', ''PGBeginSerializable'', ''PGSavepoint'', ''PGRollbackTo'', ''PGReleaseSavepoint'' | | Fehler | ''PGError'', ''PGErrno'', ''PGGetTxStatus'' | Ein Prepared Statement bekommt hier einen **Namen** und lebt auf dem Server: ''PGStmtPrepare(conn, "kunde_nach_id"c, "SELECT name FROM kunden WHERE id = $1"c)''. PostgreSQL nummeriert die Platzhalter (''$1'', ''$2'') statt sie mit ''?'' zu schreiben. ---- ===== 8. MySQL / MariaDB ===== Geprüft gegen MariaDB 10.11 über TCP. import std.io; import std.db.mysql; fn main(): int64 { var conn: MySQLConn := MySQLConnect("127.0.0.1"c, 3306, "maria"c, "geheim"c, "maria"c); if (conn.fd < 0) { PrintLn(MySQLError(conn)); return 1; } var res: MySQLResult := MySQLQuery(conn, "SELECT id, name, umsatz_cent FROM kunden ORDER BY id"c); Print("Zeilen: "); PrintLn(IntToStr(MySQLNumRows(res))); var row: int64 := MySQLFetchRow(res); while (row != 0) { Print(IntToStr(MySQLGetRowInt(res, row, 0))); Print(" | "); var name: pchar := MySQLGetRowStr(res, row, 1); Print(name); Print(" | "); PrintLn(IntToStr(MySQLGetRowInt(res, row, 2))); row := MySQLFetchRow(res); } MySQLFreeResult(res); MySQLClose(conn); return 0; } Ausgabe: Zeilen: 2 1 | Meier GmbH | 125050 2 | Schulze AG | 89000 > **Das zweite Argument von ''MySQLGetRowStr'' ist ein Zeiger, kein Index.** Es erwartet den Rückgabewert von ''MySQLFetchRow''. Wer dort eine Zeilennummer übergibt, bekommt bei ''0'' eine leere Zeichenkette und ab ''1'' einen Speicherzugriffsfehler — die Zahl wird als Adresse gelesen. > > **Der gelieferte Zeiger zeigt auf einen gemeinsamen Puffer** und gilt nur bis zum nächsten Wertzugriff; auch ''MySQLGetRowInt'' überschreibt ihn. Ein vorab geholter Name ist nach dem nächsten Aufruf ein anderer Wert. Entweder sofort verwenden oder wegkopieren. ==== Was gegen MariaDB 10.11 trägt ==== ^ Bereich ^ Zustand ^ | ''MySQLConnect'' (''mysql_native_password''), ''MySQLClose'' | ✅ | | ''MySQLQuery'', ''MySQLNumRows'', ''MySQLNumFields'', ''MySQLFreeResult'' | ✅ | | ''MySQLFetchRow'' + ''MySQLGetRowStr''/''MySQLGetRowInt'' | ✅ | | ''MySQLAffectedRows'', ''MySQLInsertId'' | ✅ | | ''MySQLBegin''/''Commit''/''Rollback'' — **Wirkung** | ✅ | | ''MySQLBegin''/''Commit''/''Rollback'' — **Rückgabewert** | ❌ liefert ''false'', obwohl die Transaktion greift | | ''MySQLStmtPrepare'' und die ganze Prepared-Familie | ❌ Server antwortet ''#08S01 Unknown command'' | | ''MySQLGetFieldName'', ''…Type'', ''…Length'' | ❌ leere Hüllen: ''""'' bzw. ''0'' | > **Prepared Statements arbeiten gegen MySQL/MariaDB nicht.** ''MySQLStmtPrepare'' liefert ein Statement mit ''stmt_id = 0'' und ''param_count = 0'', jedes ''MySQLStmtBind…'' gibt ''false'' zurück, und ''MySQLStmtExecute'' fügt nichts ein — der Server lehnt das Paket mit ''#08S01 Unknown command'' ab. > > **Damit fehlt bei diesem Backend der Schutz aus Abschnitt 4.** Wer Werte aus fremder Quelle verarbeitet, nimmt bis auf Weiteres SQLite oder PostgreSQL — dort arbeiten Prepared Statements. Geht das nicht, muss jede Eingabe vor dem Einsetzen gegen eine enge Positivliste geprüft werden (Ziffern, feste Auswahl); Maskieren allein reicht nicht. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1332|Issue #1332]]) > **Der Host muss eine IPv4-Adresse in Textform sein — ein Name lässt die Verbindung hängen.** ''MySQLConnect'' zerlegt ''host'' mit ''parseIPv4Byte'' und kennt kein DNS; ''"localhost"'' ergibt 0.0.0.0, und ''sys_connect'' wartet daraufhin unbegrenzt, statt einen Fehler zu melden. Namen also selbst auflösen und die Adresse übergeben. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1332|Issue #1332]]) ^ Aufgabe ^ Funktion ^ | Verbindung | ''MySQLConnect'', ''MySQLClose'', ''MySQLError'', ''MySQLErrno'' | | Abfrage | ''MySQLQuery'', ''MySQLFreeResult'', ''MySQLAffectedRows'', ''MySQLInsertId'' | | Ergebnis | ''MySQLNumRows'', ''MySQLNumFields'', ''MySQLFetchRow'', ''MySQLGetRowStr'', ''MySQLGetRowInt'', ''MySQLIsNull'', ''MySQLDataSeek'' | | Transaktion | ''MySQLBegin'', ''MySQLCommit'', ''MySQLRollback'', ''MySQLSetAutoCommit'', ''MySQLInTransaction'' | Die Ergebnismenge liegt vollständig im Speicher — bei großen Abfragen also ''LIMIT'' setzen. ---- ===== 9. Redis ===== Kein SQL, sondern Schlüssel-Wert-Speicher: Cache, Warteschlange, Zähler, Sitzungsdaten. import std.io; import std.db.redis; fn main(): int64 { var conn: RedisConn := RedisConnect("db.example.com"c, 6379); if (conn.fd < 0) { PrintLn("Redis nicht erreichbar"); return 1; } RedisSet(conn, "kunde:1"c, "Meier GmbH"c); var r: RedisReply := RedisGet(conn, "kunde:1"c); if (r.kind == REDIS_REPLY_BULK) { var s: pchar := r.str_val; PrintLn(s); } RedisLPush(conn, "queue"c, "job1"c); Print("Laenge: "); PrintLn(IntToStr(RedisLLen(conn, "queue"c))); RedisClose(conn); return 0; } > **Ein Redis auf demselben Rechner ist nicht erreichbar.** ''RedisConnect'' löst **jeden** Host über Google DNS auf (''GetHostByName'' → ''DNSResolveGoogle''). Damit scheitern sowohl ''"127.0.0.1"'' — eine IP-Adresse wird als Name abgefragt — als auch ''"localhost"'', weil ''/etc/hosts'' nicht gelesen wird. Nur öffentlich auflösbare Namen funktionieren, und jede Verbindung kostet zusätzlich eine externe DNS-Anfrage. > > Zu beachten: ''std.db.postgres'' und ''std.db.mysql'' machen das **Gegenteil** und akzeptieren nur eine literale IPv4. Der ''host''-Parameter bedeutet also je nach Unit etwas anderes. > ([[https://github.com/SEOLizer/LyX-Compiler/issues/1330|Issue #1330]]) ^ Bereich ^ Funktionen ^ | Verbindung | ''RedisConnect'', ''RedisClose'', ''RedisPing'' | | Schlüssel | ''RedisSet'', ''RedisGet'', ''RedisDel'', ''RedisExists'' | | Listen | ''RedisLPush'', ''RedisRPush'', ''RedisLPop'', ''RedisRPop'', ''RedisLRange'', ''RedisLLen'' | | Hashes | ''RedisHSet'', ''RedisHGet'', ''RedisHGetAll'', ''RedisHDel'' | Die Antwort kommt als ''RedisReply'' mit ''kind'' (''REDIS_REPLY_NIL'', ''…_BULK'', ''…_INTEGER'', ''…_ARRAY'', ''…_ERROR'', ''…_STATUS''), ''int_val'', ''str_val'' und ''str_len''. **''kind'' zuerst prüfen** — ein fehlender Schlüssel liefert ''REDIS_REPLY_NIL'', nicht etwa eine leere Zeichenkette. ''std.db.redis_simple'' ist die Sparversion mit ''RedisConnect'', ''RedisClose'', ''RedisPing'', ''RedisSet'', ''RedisGet'' — nützlich, wenn nur ein Wert abgelegt werden soll. ---- ===== 10. Wiederkehrende Muster ===== ==== Alles wieder freigeben ==== ^ Angelegt mit ^ Freigegeben mit ^ | ''SQLiteOpen'' | ''SQLiteClose'' | | ''SQLiteStmtPrepare'' | ''SQLiteStmtFinalize'' — auch im Fehlerfall | | ''PGConnect'' | ''PGClose'' | | ''PGQuery'' | ''PGFreeResult'' | | ''PGStmtPrepare'' | ''PGStmtClose'' (Server) **und** ''PGStmtFree'' (Speicher) | | ''MySQLConnect'' | ''MySQLClose'' | | ''MySQLQuery'' | ''MySQLFreeResult'' | | ''RedisConnect'' | ''RedisClose'' | Ein Dienst, der pro Anfrage eine Verbindung öffnet und nicht schließt, läuft nach einigen tausend Anfragen in das Deskriptorlimit — und zwar erst im Betrieb, nicht im Test. ==== Fehler auf drei Ebenen ==== ^ Ebene ^ Erkennbar an ^ Beispiel ^ | Verbindung | ''0'' bzw. ''fd < 0'' | Server aus, Passwort falsch, Datei nicht schreibbar | | Anweisung | ''Prepare'' gibt 0, ''Exec'' gibt ''false'' | Syntaxfehler, Tabelle fehlt, Constraint verletzt | | Ergebnis | ''SQLITE_DONE'' statt ''SQLITE_ROW'', ''NumRows'' = 0 | die Abfrage lief, fand aber nichts | Die dritte Ebene ist **kein** Fehler. Wer „keine Zeile" wie „Fehler" behandelt, meldet Störungen, wo nur nichts zu finden war. ==== Die Frage nach dem Schema ==== Lyx bringt kein Migrationswerkzeug mit. Bewährt hat sich eine Versionsnummer in der Datenbank selbst: SQLiteExec(db, "CREATE TABLE IF NOT EXISTS schema_version (v INTEGER)"c); // beim Start lesen, mit der erwarteten Version vergleichen, gezielt nachziehen ---- ===== 11. Zustand auf einen Blick ===== ^ Unit ^ Zustand ^ | ''std.db.sqlite'' — Öffnen, Exec, Prepare/Bind/Step, Transaktionen, Verwaltung | ✅ geprüft | | ''std.db.sqlite'' — ''REAL'' (''BindFloat''/''ColumnFloat'') | ❌ Wert geht verloren ([[https://github.com/SEOLizer/LyX-Compiler/issues/1329|#1329]]) | | ''std.db.sqlite'' — unter ''@capabilities'' | ❌ Exit 159, ''fcntl'' fehlt im Filter ([[https://github.com/SEOLizer/LyX-Compiler/issues/1276|#1276]]) | | ''std.db.postgres'' — Protokoll, Abfragen, Prepared, Transaktionen | ✅ aus dem Quelltext; Host nur als IPv4 | | ''std.db.mysql'' — Verbindung, Abfragen, Zeilenzugriff, Transaktionswirkung | ✅ gegen MariaDB 10.11 geprüft | | ''std.db.mysql'' — Prepared Statements, Feld-Metadaten, Transaktions-Rückgabewerte | ❌ ([[https://github.com/SEOLizer/LyX-Compiler/issues/1332|#1332]]) | | ''std.db.redis'' — Befehle | ✅; **Verbindung** nur zu öffentlich auflösbaren Namen ([[https://github.com/SEOLizer/LyX-Compiler/issues/1330|#1330]]) | Die SQLite- und MySQL-Beispiele dieser Seite sind übersetzt und gegen eine echte Datenbank ausgeführt worden (SQLite-Datei bzw. MariaDB 10.11); der PostgreSQL-Abschnitt folgt dem Quelltext der Unit, ohne laufenden Server geprüft zu sein. ---- ===== 12. Weiterführend ===== * [[lyx_-_programmiersprache:units:db|std.db — Unit-Referenz]] · [[lyx_-_programmiersprache:units:db:sqlite|sqlite]] · [[lyx_-_programmiersprache:units:db:postgres|postgres]] · [[lyx_-_programmiersprache:units:db:mysql|mysql]] · [[lyx_-_programmiersprache:units:db:redis|redis]] * [[lyx_-_programmiersprache:units:graphdb|std.graphdb]] — Knoten und Kanten statt Tabellen * [[lyx_-_programmiersprache:units:net:mongo|std.net.mongo]] — MongoDB Wire Protocol * [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]] — Namensauflösung, Capabilities, Timeouts * [[lyx_-_programmiersprache:guides:daten-serialisierung|Daten & Serialisierung]] — was aus der Datenbank herausfällt, weiterverarbeiten * [[lyx_-_programmiersprache:sprache:capabilities|Capabilities]] · [[lyx_-_programmiersprache:sprache:ffi|FFI]] (SQLite bindet libsqlite3 ein)