====== std.db.postgres ====== PostgreSQL-Client (Wire-Protokoll v3): Pure-Lyx-Implementierung ohne ''libpq'' — direktes TCP + PostgreSQL Frontend/Backend Protocol 3.0. Unterstützt Simple Query, Prepared Statements mit Parametern, Transaktionen, Connection Pooling, LISTEN/NOTIFY und COPY. Authentifizierung: MD5, Cleartext (kein SASL). → [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:units:db:sqlite|std.db.sqlite]] · [[lyx_-_programmiersprache:units:db:mysql|std.db.mysql]] · [[lyx_-_programmiersprache:units:crypto:md5|std.crypto.md5]] ---- ===== Konstanten ===== ==== Verbindungsstatus ==== Funktionen für Verbindungsaufbau und -verwaltung: ^ Konstante ^ Wert ^ Beschreibung ^ | ''PG_STATUS_DISCONNECTED'' | 0 | Nicht verbunden | | ''PG_STATUS_CONNECTED'' | 1 | Verbunden, bereit für Queries | | ''PG_STATUS_IN_TX'' | 2 | Aktive Transaktion | | ''PG_STATUS_TX_ERROR'' | 3 | Transaktion im Fehlerzustand | ==== Fehlercodes ==== ^ Konstante ^ Wert ^ Beschreibung ^ | ''PG_ERR_NONE'' | 0 | Kein Fehler | | ''PG_ERR_SOCKET'' | 3001 | Socket-Fehler | | ''PG_ERR_CONNECT'' | 3002 | Verbindungsfehler | | ''PG_ERR_OOM'' | 3003 | Kein Speicher | | ''PG_ERR_SEND'' | 3004 | Sende-Fehler | | ''PG_ERR_RECV'' | 3005 | Empfangs-Fehler | | ''PG_ERR_PROTO'' | 3006 | Protokollfehler / Server-Fehlermeldung | ==== Transaktionsstatus (ReadyForQuery) ==== ^ Konstante ^ Wert ^ Beschreibung ^ | ''PG_TX_IDLE'' | 73 (''I'') | Keine aktive Transaktion | | ''PG_TX_ACTIVE'' | 84 (''T'') | Transaktion aktiv | | ''PG_TX_ERROR'' | 69 (''E'') | Transaktion im Fehlerzustand | ==== PostgreSQL OID-Typen ==== ^ Konstante ^ OID ^ Beschreibung ^ | ''PG_OID_BOOL'' | 16 | Boolean | | ''PG_OID_BYTEA'' | 17 | Binärdaten | | ''PG_OID_INT8'' | 20 | BIGINT | | ''PG_OID_INT2'' | 21 | SMALLINT | | ''PG_OID_INT4'' | 23 | INTEGER | | ''PG_OID_TEXT'' | 25 | TEXT | | ''PG_OID_FLOAT4'' | 700 | REAL | | ''PG_OID_FLOAT8'' | 701 | DOUBLE PRECISION | | ''PG_OID_VARCHAR'' | 1043 | VARCHAR | | ''PG_OID_DATE'' | 1082 | DATE | | ''PG_OID_TIMESTAMP'' | 1114 | TIMESTAMP | | ''PG_OID_NUMERIC'' | 1700 | NUMERIC/DECIMAL | ==== Protokoll-Nachrichtentypen ==== Backend → Client (Auswahl): ''PG_MSG_AUTH'' (R), ''PG_MSG_READY'' (Z), ''PG_MSG_ROW_DESC'' (T), ''PG_MSG_DATA_ROW'' (D), ''PG_MSG_CMD_COMPLETE'' (C), ''PG_MSG_ERROR'' (E), ''PG_MSG_NOTICE'' (N), ''PG_MSG_NOTIFY'' (A), ''PG_MSG_COPY_IN'' (G), ''PG_MSG_COPY_OUT'' (H). Frontend → Server (Auswahl): ''PG_FE_QUERY'' (Q), ''PG_FE_PARSE'' (P), ''PG_FE_BIND'' (B), ''PG_FE_EXECUTE'' (E), ''PG_FE_SYNC'' (S), ''PG_FE_TERMINATE'' (X). ---- ===== Funktionen ===== ==== Verbindung ==== Funktionen für Verbindungsaufbau und -verwaltung: ^ Signatur ^ Beschreibung ^ | ''PGConnect(host: pchar, port: int64, user: pchar, password: pchar, database: pchar): int64'' | Öffnet TCP-Verbindung und führt Handshake durch. Gibt PGConn-Pointer zurück; 0 nur bei TCP-Fehler. Bei Auth-Fehler: Pointer gültig, aber ''PGIsConnected''=0 und ''PGError'' gesetzt | | ''PGClose(conn: int64): void'' | Sendet Terminate-Nachricht, schließt TCP, gibt PGConn-Speicher frei | | ''PGIsConnected(conn: int64): int64'' | Gibt 1 zurück wenn verbunden und bereit | | ''PGError(conn: int64): pchar'' | Fehlermeldung der letzten Operation (SQLSTATE oder Text) | | ''PGErrno(conn: int64): int64'' | Fehlercode der letzten Operation (PG_ERR_*) | | ''PGServerVersion(conn: int64): pchar'' | Server-Versionsstring (z. B. ''„16.2"'') | | ''PGBackendPID(conn: int64): int64'' | PID des Server-Prozesses | | ''PGGetHost(conn: int64): pchar'' | Host aus dem Connect-Aufruf | | ''PGGetPort(conn: int64): int64'' | Port aus dem Connect-Aufruf | | ''PGGetUser(conn: int64): pchar'' | Benutzername | | ''PGGetDatabase(conn: int64): pchar'' | Datenbankname | | ''PGGetTxStatus(conn: int64): int64'' | Transaktionsstatus (''PG_TX_IDLE''/''PG_TX_ACTIVE''/''PG_TX_ERROR'') | | ''PGPing(conn: int64): int64'' | Prüft ob die Verbindung noch aktiv ist. Gibt 1 zurück wenn der Server antwortet | ==== Simple Query ==== ^ Signatur ^ Beschreibung ^ | ''PGQuery(conn: int64, sql: pchar): int64'' | Sendet SQL-Statement, liest alle Antworten und gibt PGResult-Pointer zurück; 0 bei Fehler | | ''PGFreeResult(result: int64): void'' | Gibt alle Ressourcen des PGResult frei | ==== Result-Accessoren ==== ^ Signatur ^ Beschreibung ^ | ''PGNumRows(result: int64): int64'' | Anzahl zurückgelieferter Zeilen | | ''PGNumFields(result: int64): int64'' | Anzahl Spalten | | ''PGAffectedRows(result: int64): int64'' | Bei INSERT/UPDATE/DELETE: Anzahl betroffener Zeilen | | ''PGFetchRow(result: int64): int64'' | Bewegt den Cursor zur nächsten Zeile. Gibt 1 wenn eine Zeile verfügbar, 0 wenn fertig | | ''PGGetStr(result: int64, col: int64): pchar'' | Spalte als String (0-basiert). 0 wenn NULL | | ''PGGetInt(result: int64, col: int64): int64'' | Spalte als int64 (Text → int64) | | ''PGGetFloat(result: int64, col: int64): f64'' | Spalte als f64 (Text → f64) | | ''PGGetBool(result: int64, col: int64): int64'' | Spalte als Boolean: ''„t"''/''„T"''/''„1"'' → 1, sonst 0 | | ''PGIsNull(result: int64, row: int64, col: int64): int64'' | 1 wenn Wert NULL ist (direkter Row-Index, nicht Cursor) | | ''PGGetFieldName(result: int64, col: int64): pchar'' | Spaltenname | | ''PGGetFieldTypeOid(result: int64, col: int64): int64'' | PostgreSQL-Typ-OID (vgl. PG_OID_*-Konstanten) | | ''PGDataSeek(result: int64, row: int64)'' | Setzt den Cursor auf Zeile ''row'' (für erneutes Lesen) | ==== Prepared Statements ==== Funktionen für Prepared Statements: ^ Signatur ^ Beschreibung ^ | ''PGStmtPrepare(conn: int64, name: pchar, sql: pchar): int64'' | Kompiliert SQL-Statement auf dem Server. ''name'': eindeutiger Statement-Name (leer = unbenannt). Gibt PGStmt-Pointer zurück; 0 bei Fehler | | ''PGStmtExecute(conn: int64, stmt: int64): int64'' | Sendet Bind + Execute + Sync, liest Ergebnisse. Gibt PGResult zurück | | ''PGStmtClose(conn: int64, name: pchar): void'' | Gibt das benannte Statement auf dem Server frei | | ''PGStmtDescribe(conn: int64, name: pchar): int64'' | Gibt Metadaten (Parameter- und Spaltentypen) zurück | | ''PGStmtReset(stmt: int64): void'' | Setzt Parameter-Bindings auf NULL zurück | | ''PGStmtFree(stmt: int64): void'' | Gibt den lokalen PGStmt-Speicher frei (kein Server-Close) | ==== Parameter-Binding (0-basierter Index) ==== ^ Signatur ^ Beschreibung ^ | ''PGBindInt(stmt: int64, i: int64, v: int64)'' | Bindet ''int64'' an Parameter ''i'' (als Dezimalstring) | | ''PGBindFloat(stmt: int64, i: int64, v: f64)'' | Bindet ''f64'' an Parameter ''i'' | | ''PGBindStr(stmt: int64, i: int64, v: pchar)'' | Bindet String an Parameter ''i'' | | ''PGBindBool(stmt: int64, i: int64, v: int64)'' | Bindet Boolean an Parameter ''i'' (0 → ''„f"'', sonst → ''„t"'') | | ''PGBindNull(stmt: int64, i: int64)'' | Bindet NULL an Parameter ''i'' | ==== Transaktionen ==== Transaktionsverwaltung: ^ Signatur ^ Beschreibung ^ | ''PGBegin(conn: int64): int64'' | BEGIN | | ''PGCommit(conn: int64): int64'' | COMMIT | | ''PGRollback(conn: int64): int64'' | ROLLBACK | | ''PGBeginReadOnly(conn: int64): int64'' | BEGIN READ ONLY | | ''PGBeginSerializable(conn: int64): int64'' | BEGIN ISOLATION LEVEL SERIALIZABLE | | ''PGSavepoint(conn: int64, name: pchar): int64'' | SAVEPOINT name | | ''PGRollbackTo(conn: int64, name: pchar): int64'' | ROLLBACK TO name | | ''PGReleaseSavepoint(conn: int64, name: pchar): int64'' | RELEASE SAVEPOINT name | | ''PGInTransaction(conn: int64): int64'' | 1 wenn eine Transaktion aktiv ist | | ''PGTxFailed(conn: int64): int64'' | 1 wenn die Transaktion im Fehlerzustand ist | | ''PGSetAutoCommit(conn: int64, on: int64): int64'' | Autocommit ein- oder ausschalten | ==== Metadaten & Hilfsfunktionen ==== ^ Signatur ^ Beschreibung ^ | ''PGChanges(conn: int64): int64'' | Betroffene Zeilen des letzten INSERT/UPDATE/DELETE | | ''PGLastInsertId(conn: int64): int64'' | OID der zuletzt eingefügten Zeile (nur bei INSERT … RETURNING OID) | | ''PGEscapeStr(dst: int64, src: pchar): int64'' | Escaped einen String für sichere Einbettung in SQL (''''' → ''''''). Gibt Länge zurück | | ''PGTableExists(conn: int64, table: pchar): int64'' | 1 wenn die Tabelle im Schema existiert | | ''PGColumnExists(conn: int64, table: pchar, column: pchar): int64'' | 1 wenn die Spalte in der Tabelle existiert | | ''PGDropTable(conn: int64, table: pchar): int64'' | DROP TABLE IF EXISTS mit Sicherheits-Check | ==== Connection Pool ==== ^ Signatur ^ Beschreibung ^ | ''PGPoolCreate(size: int64): int64'' | Erstellt einen Pool mit ''size'' Verbindungsslots. Gibt Pool-Pointer zurück | | ''PGPoolDestroy(pg: int64): void'' | Schließt alle Verbindungen und gibt Pool-Speicher frei | | ''PGPoolAcquire(pg: int64, host: pchar, port: int64, user: pchar, password: pchar, database: pchar): int64'' | Gibt eine freie Verbindung zurück (oder öffnet eine neue). Blockiert bis ein Slot frei ist | | ''PGPoolRelease(pg: int64, conn: int64): void'' | Gibt eine Verbindung an den Pool zurück | ==== LISTEN / NOTIFY ==== Asynchrone Benachrichtigungen über LISTEN/NOTIFY: ^ Signatur ^ Beschreibung ^ | ''PGListen(conn: int64, channel: pchar): int64'' | Registriert den Client für Benachrichtigungen auf ''channel'' | | ''PGUnlisten(conn: int64, channel: pchar): int64'' | Deregistriert den Client vom ''channel'' | | ''PGNotify(conn: int64, channel: pchar, payload: pchar): int64'' | Sendet eine Benachrichtigung mit optionalem Payload | | ''PGWaitNotification(conn: int64, timeout_ms: int64): int64'' | Wartet auf eine Benachrichtigung. Gibt Notification-Pointer zurück oder 0 bei Timeout | | ''PGGetNotification(conn: int64): int64'' | Prüft nicht-blockierend ob eine Benachrichtigung vorliegt | | ''PGFreeNotification(notif: int64): void'' | Gibt den Notification-Speicher frei | | ''PGNotifPid(notif: int64): int64'' | PID des sendenden Prozesses | | ''PGNotifChannel(notif: int64): pchar'' | Kanal-Name der Benachrichtigung | | ''PGNotifPayload(notif: int64): pchar'' | Payload der Benachrichtigung (leer wenn kein Payload) | ==== COPY ==== ^ Signatur ^ Beschreibung ^ | ''PGCopyBegin(conn: int64, table: pchar, columns: pchar): int64'' | Startet einen ''COPY … FROM STDIN'' Bulk-Import. Gibt 0 zurück bei Fehler | | ''PGCopyRow(conn: int64, values: int64, count: int64): int64'' | Sendet eine Zeile (CSV-formatiert). ''values'': Zeiger auf Array von ''count'' pchar-Pointern | | ''PGCopyEnd(conn: int64): int64'' | Beendet den COPY-Stream und bestätigt | | ''PGCopyAbort(conn: int64, errmsg: pchar): int64'' | Bricht den COPY-Stream mit einer Fehlermeldung ab | ---- ===== Verwendung ===== ==== Verbindung aufbauen und Query ausführen ==== Funktionen für Verbindungsaufbau und -verwaltung: import std.db.postgres; import std.io; fn Main(): void { var conn := PGConnect("127.0.0.1", 5432, "app_user", "secret", "mydb"); if (conn == 0 || !PGIsConnected(conn)) { PrintLn("Verbindung fehlgeschlagen: " + PGError(conn)); if (conn != 0) { PGClose(conn); } return; } PrintLn("Verbunden mit PostgreSQL " + PGServerVersion(conn)); var result := PGQuery(conn, "SELECT id, name, email FROM users LIMIT 10"); if (result == 0) { PrintLn("Query-Fehler: " + PGError(conn)); PGClose(conn); return; } while (PGFetchRow(result) != 0) { PrintLn(""); PrintLn(IntToStr(PGGetInt(result, 0))); + " | " + PGGetStr(result, 1) + " | " + PGGetStr(result, 2 } PGFreeResult(result); PGClose(conn); } ==== Prepared Statement mit Parametern ==== import std.db.postgres; import std.io; fn InsertUser(conn: int64, name: pchar, email: pchar): int64 { var stmt := PGStmtPrepare(conn, "ins_user", "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"); if (stmt == 0) { return -1; } PGBindStr(stmt, 0, name); PGBindStr(stmt, 1, email); var result := PGStmtExecute(conn, stmt); var newId: int64 := -1; if (result != 0) { PGFetchRow(result); newId := PGGetInt(result, 0); PGFreeResult(result); } PGStmtClose(conn, "ins_user"); PGStmtFree(stmt); return newId; } ==== Transaktion mit Savepoint ==== import std.db.postgres; fn TransferFunds(conn: int64, fromId: int64, toId: int64, amount: int64): bool { PGBegin(conn); var r1 := PGQuery(conn, "UPDATE accounts SET balance = balance - 100 WHERE id = 1"); if (r1 == 0) { PGRollback(conn); return false; } PGFreeResult(r1); PGSavepoint(conn, "after_debit"); var r2 := PGQuery(conn, "UPDATE accounts SET balance = balance + 100 WHERE id = 2"); if (r2 == 0) { PGRollbackTo(conn, "after_debit"); PGRollback(conn); return false; } PGFreeResult(r2); PGCommit(conn); return true; } ==== LISTEN / NOTIFY ==== Asynchrone Benachrichtigungen über LISTEN/NOTIFY: import std.db.postgres; import std.io; fn WatchChannel(conn: int64): void { PGListen(conn, "job_queue"); var notif := PGWaitNotification(conn, 5000); // 5 Sekunden Timeout if (notif != 0) { PrintLn("Benachrichtigung von PID " + IntToStr(PGNotifPid(notif)) + " auf '" + PGNotifChannel(notif) + "': " + PGNotifPayload(notif)); PGFreeNotification(notif); } PGUnlisten(conn, "job_queue"); } ==== COPY: Bulk-Import ==== import std.db.postgres; import std.alloc; fn BulkLoad(conn: int64): bool { if (PGCopyBegin(conn, "products", "name,price,stock") == 0) { return false; } // Jede Zeile als Array von String-Pointern übergeben var row: int64 := alloc(24); // 3 × 8 Bytes var c0: pchar := "Widget A"; var c1: pchar := "9.99"; var c2: pchar := "100"; poke64(row, c0 as int64); poke64(row + 8, c1 as int64); poke64(row + 16, c2 as int64); PGCopyRow(conn, row, 3); free(row, 24); return PGCopyEnd(conn) == 0; } ---- ===== Hinweise ===== * ''PGConnect'' gibt bei TCP-Fehler 0 zurück, bei Auth-Fehler aber einen gültigen Pointer mit ''PGIsConnected''=0 — deshalb beide Fälle prüfen. * Das Passwort wird nach dem Handshake im Speicher mit Null überschrieben. * Parameter-Indizes bei ''PGBind*'' sind **0-basiert** (im Gegensatz zu PostgreSQL-Platzhaltern ''$1'', ''$2'' die 1-basiert sind). * ''PGQuery'' puffert das komplette Ergebnis im RAM — für sehr große Result-Sets ''PGStmtPrepare'' + ''PGStmtExecute'' verwenden. * SASL-Authentifizierung (SCRAM-SHA-256) wird nicht unterstützt — für diese Server ''password_encryption = md5'' in der PostgreSQL-Konfiguration setzen. * Der Connection Pool ist nicht thread-safe — für Multi-Thread-Zugriff pro Thread eine eigene Verbindung verwenden. ---- ===== Verwandte Units ===== * ''[[lyx_-_programmiersprache:units:db:sqlite|std.db.sqlite]]'' — SQLite3 (embedded, kein Server) * ''[[lyx_-_programmiersprache:units:db:mysql|std.db.mysql]]'' — MySQL/MariaDB * ''[[lyx_-_programmiersprache:units:crypto:md5|std.crypto.md5]]'' — wird für MD5-Authentifizierung verwendet * ''[[lyx_-_programmiersprache:units:net:socket|std.net.socket]]'' — TCP-Basis Letzte Aktualisierung: 2026-06-05