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() }