118 lines
4 KiB
Go
118 lines
4 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// dialekt beschreibt, worin sich SQLite und PostgreSQL unterscheiden.
|
|
//
|
|
// # Warum so klein?
|
|
//
|
|
// Alle Abfragen im Store sind für beide Datenbanken dieselben (siehe
|
|
// AUSBAUSTUFEN.md 4.10, „ein Store mit Dialekt, keine zwei Stores“). Hier steht
|
|
// nur, was sich wirklich unterscheidet. Jede Methode, die hier dazukommt, muss
|
|
// für beide Datenbanken umgesetzt werden — fehlt eine, baut das Programm nicht.
|
|
// Der Compiler passt also auf, dass keine Seite vergessen wird.
|
|
type dialekt interface {
|
|
// rebind wandelt die Platzhalter einer Abfrage in die Schreibweise der
|
|
// Datenbank um. Geschrieben werden alle Abfragen mit "?" (SQLite);
|
|
// PostgreSQL erwartet "$1, $2, …".
|
|
rebind(abfrage string) string
|
|
|
|
// migrationsUnterordner nennt den Ordner innerhalb von migrations/ bzw.
|
|
// migrations_control/, aus dem die Migrationen dieser Datenbank kommen.
|
|
migrationsUnterordner() string
|
|
|
|
// belegung misst, wie viel Platz ein Workspace belegt: bei SQLite die
|
|
// Datei samt WAL, bei PostgreSQL die Tabellen des Schemas. Die
|
|
// Umsetzungen stehen in systemstand.go.
|
|
belegung(ctx context.Context, p pool) (Belegung, error)
|
|
}
|
|
|
|
// ── SQLite ───────────────────────────────────────────────────────────────────
|
|
|
|
type sqliteDialekt struct{}
|
|
|
|
// rebind ist bei SQLite nichts zu tun: Die Abfragen sind bereits in der
|
|
// SQLite-Schreibweise geschrieben.
|
|
func (sqliteDialekt) rebind(abfrage string) string { return abfrage }
|
|
|
|
func (sqliteDialekt) migrationsUnterordner() string { return "sqlite" }
|
|
|
|
// ── PostgreSQL ───────────────────────────────────────────────────────────────
|
|
|
|
type postgresDialekt struct{}
|
|
|
|
func (postgresDialekt) rebind(abfrage string) string { return platzhalterNummerieren(abfrage) }
|
|
|
|
func (postgresDialekt) migrationsUnterordner() string { return "postgres" }
|
|
|
|
// platzhalterNummerieren ersetzt jedes "?" durch "$1", "$2", … in der
|
|
// Reihenfolge des Auftretens.
|
|
//
|
|
// Ein "?" innerhalb eines Textes in Hochkommas ('Wie geht das?'), eines
|
|
// Bezeichners in Anführungszeichen oder eines Kommentars ist kein Platzhalter
|
|
// und bleibt stehen. Deshalb geht die Funktion Zeichen für Zeichen durch und
|
|
// merkt sich, wo sie gerade steht, statt blind zu ersetzen.
|
|
//
|
|
// Ein verdoppeltes Hochkomma (so schreibt SQL ein Hochkomma innerhalb eines
|
|
// Textes) braucht keine Sonderbehandlung: Das erste beendet den Text, das
|
|
// zweite beginnt sofort einen neuen — das Ergebnis ist dasselbe.
|
|
func platzhalterNummerieren(abfrage string) string {
|
|
// Schneller Weg: Die meisten Abfragen sind kurz, aber ohne "?" gibt es
|
|
// gar nichts zu tun.
|
|
if !strings.Contains(abfrage, "?") {
|
|
return abfrage
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.Grow(len(abfrage) + 16)
|
|
|
|
nummer := 0
|
|
for i := 0; i < len(abfrage); i++ {
|
|
c := abfrage[i]
|
|
switch {
|
|
case c == '\'' || c == '"':
|
|
// Text bzw. Bezeichner: bis zum passenden Gegenstück unverändert
|
|
// übernehmen.
|
|
ende := strings.IndexByte(abfrage[i+1:], c)
|
|
if ende < 0 {
|
|
b.WriteString(abfrage[i:])
|
|
return b.String()
|
|
}
|
|
b.WriteString(abfrage[i : i+1+ende+1])
|
|
i += ende + 1
|
|
|
|
case c == '-' && i+1 < len(abfrage) && abfrage[i+1] == '-':
|
|
// Kommentar bis zum Zeilenende.
|
|
ende := strings.IndexByte(abfrage[i:], '\n')
|
|
if ende < 0 {
|
|
b.WriteString(abfrage[i:])
|
|
return b.String()
|
|
}
|
|
b.WriteString(abfrage[i : i+ende])
|
|
i += ende - 1
|
|
|
|
case c == '/' && i+1 < len(abfrage) && abfrage[i+1] == '*':
|
|
// Blockkommentar bis "*/".
|
|
ende := strings.Index(abfrage[i+2:], "*/")
|
|
if ende < 0 {
|
|
b.WriteString(abfrage[i:])
|
|
return b.String()
|
|
}
|
|
b.WriteString(abfrage[i : i+2+ende+2])
|
|
i += 2 + ende + 1
|
|
|
|
case c == '?':
|
|
nummer++
|
|
b.WriteByte('$')
|
|
b.WriteString(strconv.Itoa(nummer))
|
|
|
|
default:
|
|
b.WriteByte(c)
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|