90 lines
3.2 KiB
Go
90 lines
3.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
// pool ist ein Verbindungspool samt dem Dialekt seiner Datenbank.
|
|
//
|
|
// # Warum nicht direkt *sql.DB?
|
|
//
|
|
// Jede Abfrage muss durch dialekt.rebind, bevor sie an die Datenbank geht —
|
|
// sonst bekommt PostgreSQL ein "?", mit dem es nichts anfangen kann. Würde der
|
|
// Store *sql.DB direkt benutzen, wäre das bei jeder der rund 100 Abfragen eine
|
|
// Stelle, an der man es vergessen kann.
|
|
//
|
|
// pool bietet deshalb nur die Methoden an, die der Store braucht, und jede
|
|
// davon wandelt um. An *sql.DB selbst kommt außerhalb dieser Datei niemand
|
|
// heran — eine Abfrage am Dialekt vorbei lässt sich gar nicht erst schreiben.
|
|
type pool struct {
|
|
db *sql.DB
|
|
d dialekt
|
|
}
|
|
|
|
func (p pool) ExecContext(ctx context.Context, abfrage string, args ...any) (sql.Result, error) {
|
|
return p.db.ExecContext(ctx, p.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
func (p pool) QueryContext(ctx context.Context, abfrage string, args ...any) (*sql.Rows, error) {
|
|
return p.db.QueryContext(ctx, p.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
func (p pool) QueryRowContext(ctx context.Context, abfrage string, args ...any) *sql.Row {
|
|
return p.db.QueryRowContext(ctx, p.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
// beginTx startet eine Transaktion, deren Abfragen ebenfalls umgewandelt
|
|
// werden.
|
|
func (p pool) beginTx(ctx context.Context) (*transaktion, error) {
|
|
tx, err := p.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &transaktion{tx: tx, d: p.d}, nil
|
|
}
|
|
|
|
func (p pool) Close() error { return p.db.Close() }
|
|
|
|
// Verbindungen sind die Zahlen eines Lese- und eines Schreibpools, wie
|
|
// database/sql sie führt — für die Systemseite (AUSBAUSTUFEN.md 5.12).
|
|
type Verbindungen struct {
|
|
Lesen, Schreiben sql.DBStats
|
|
}
|
|
|
|
// Offen zählt die offenen Verbindungen beider Pools zusammen. Genau so viele
|
|
// belegt Munin gerade beim Datenbankserver.
|
|
func (v Verbindungen) Offen() int { return v.Lesen.OpenConnections + v.Schreiben.OpenConnections }
|
|
|
|
// InBenutzung zählt die Verbindungen, die gerade eine Abfrage ausführen.
|
|
func (v Verbindungen) InBenutzung() int { return v.Lesen.InUse + v.Schreiben.InUse }
|
|
|
|
// Gewartet zählt, wie oft eine Anfrage auf eine freie Verbindung warten
|
|
// musste, seit der Pool geöffnet wurde. Wächst die Zahl stetig, sind die
|
|
// Pools zu klein für die Last.
|
|
func (v Verbindungen) Gewartet() int64 { return v.Lesen.WaitCount + v.Schreiben.WaitCount }
|
|
|
|
// transaktion ist das Gegenstück zu pool für *sql.Tx: dieselben Methoden,
|
|
// dieselbe Umwandlung.
|
|
type transaktion struct {
|
|
tx *sql.Tx
|
|
d dialekt
|
|
}
|
|
|
|
func (t *transaktion) ExecContext(ctx context.Context, abfrage string, args ...any) (sql.Result, error) {
|
|
return t.tx.ExecContext(ctx, t.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
func (t *transaktion) QueryContext(ctx context.Context, abfrage string, args ...any) (*sql.Rows, error) {
|
|
return t.tx.QueryContext(ctx, t.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
func (t *transaktion) QueryRowContext(ctx context.Context, abfrage string, args ...any) *sql.Row {
|
|
return t.tx.QueryRowContext(ctx, t.d.rebind(abfrage), args...)
|
|
}
|
|
|
|
func (t *transaktion) Commit() error { return t.tx.Commit() }
|
|
|
|
// Rollback nach einem erfolgreichen Commit ist wirkungslos — deshalb kann es
|
|
// bedenkenlos per defer stehen.
|
|
func (t *transaktion) Rollback() error { return t.tx.Rollback() }
|