Automatically upgrade databases on startup (#1529)
* Support auto-upgrading accounts DB * Auto-upgrade device DB deltas * Support up/downgrading from cmd/goose * Linting * Create tables then do migrations then prepare statements To avoid failing due to some things not existing * Linting
This commit is contained in:
parent
3e5d38e284
commit
4a7fb9c045
|
@ -8,19 +8,38 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
// Example complex Go migration import:
|
pgaccounts "github.com/matrix-org/dendrite/userapi/storage/accounts/postgres/deltas"
|
||||||
// _ "github.com/matrix-org/dendrite/serverkeyapi/storage/postgres/deltas"
|
slaccounts "github.com/matrix-org/dendrite/userapi/storage/accounts/sqlite3/deltas"
|
||||||
|
pgdevices "github.com/matrix-org/dendrite/userapi/storage/devices/postgres/deltas"
|
||||||
|
sldevices "github.com/matrix-org/dendrite/userapi/storage/devices/sqlite3/deltas"
|
||||||
"github.com/pressly/goose"
|
"github.com/pressly/goose"
|
||||||
|
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
flags = flag.NewFlagSet("goose", flag.ExitOnError)
|
AppService = "appservice"
|
||||||
dir = flags.String("dir", ".", "directory with migration files")
|
FederationSender = "federationsender"
|
||||||
|
KeyServer = "keyserver"
|
||||||
|
MediaAPI = "mediaapi"
|
||||||
|
RoomServer = "roomserver"
|
||||||
|
SigningKeyServer = "signingkeyserver"
|
||||||
|
SyncAPI = "syncapi"
|
||||||
|
UserAPIAccounts = "userapi_accounts"
|
||||||
|
UserAPIDevices = "userapi_devices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dir = flags.String("dir", "", "directory with migration files")
|
||||||
|
flags = flag.NewFlagSet("goose", flag.ExitOnError)
|
||||||
|
component = flags.String("component", "", "dendrite component name")
|
||||||
|
knownDBs = []string{
|
||||||
|
AppService, FederationSender, KeyServer, MediaAPI, RoomServer, SigningKeyServer, SyncAPI, UserAPIAccounts, UserAPIDevices,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint: gocyclo
|
||||||
func main() {
|
func main() {
|
||||||
err := flags.Parse(os.Args[1:])
|
err := flags.Parse(os.Args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,19 +56,20 @@ Drivers:
|
||||||
sqlite3
|
sqlite3
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
goose -d roomserver/storage/sqlite3/deltas sqlite3 ./roomserver.db status
|
goose -component roomserver sqlite3 ./roomserver.db status
|
||||||
goose -d roomserver/storage/sqlite3/deltas sqlite3 ./roomserver.db up
|
goose -component roomserver sqlite3 ./roomserver.db up
|
||||||
|
|
||||||
goose -d roomserver/storage/postgres/deltas postgres "user=dendrite dbname=dendrite sslmode=disable" status
|
goose -component roomserver postgres "user=dendrite dbname=dendrite sslmode=disable" status
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
-component string
|
||||||
-dir string
|
Dendrite component name e.g roomserver, signingkeyserver, clientapi, syncapi
|
||||||
directory with migration files (default ".")
|
|
||||||
-table string
|
-table string
|
||||||
migrations table name (default "goose_db_version")
|
migrations table name (default "goose_db_version")
|
||||||
-h print help
|
-h print help
|
||||||
-v enable verbose mode
|
-v enable verbose mode
|
||||||
|
-dir string
|
||||||
|
directory with migration files, only relevant when creating new migrations.
|
||||||
-version
|
-version
|
||||||
print version
|
print version
|
||||||
|
|
||||||
|
@ -74,6 +94,25 @@ Commands:
|
||||||
fmt.Println("engine must be one of 'sqlite3' or 'postgres'")
|
fmt.Println("engine must be one of 'sqlite3' or 'postgres'")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
knownComponent := false
|
||||||
|
for _, c := range knownDBs {
|
||||||
|
if c == *component {
|
||||||
|
knownComponent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !knownComponent {
|
||||||
|
fmt.Printf("component must be one of %v\n", knownDBs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if engine == "sqlite3" {
|
||||||
|
loadSQLiteDeltas(*component)
|
||||||
|
} else {
|
||||||
|
loadPostgresDeltas(*component)
|
||||||
|
}
|
||||||
|
|
||||||
dbstring, command := args[1], args[2]
|
dbstring, command := args[1], args[2]
|
||||||
|
|
||||||
db, err := goose.OpenDBWithDriver(engine, dbstring)
|
db, err := goose.OpenDBWithDriver(engine, dbstring)
|
||||||
|
@ -92,7 +131,30 @@ Commands:
|
||||||
arguments = append(arguments, args[3:]...)
|
arguments = append(arguments, args[3:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := goose.Run(command, db, *dir, arguments...); err != nil {
|
// goose demands a directory even though we don't use it for upgrades
|
||||||
|
d := *dir
|
||||||
|
if d == "" {
|
||||||
|
d = os.TempDir()
|
||||||
|
}
|
||||||
|
if err := goose.Run(command, db, d, arguments...); err != nil {
|
||||||
log.Fatalf("goose %v: %v", command, err)
|
log.Fatalf("goose %v: %v", command, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadSQLiteDeltas(component string) {
|
||||||
|
switch component {
|
||||||
|
case UserAPIAccounts:
|
||||||
|
slaccounts.LoadFromGoose()
|
||||||
|
case UserAPIDevices:
|
||||||
|
sldevices.LoadFromGoose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPostgresDeltas(component string) {
|
||||||
|
switch component {
|
||||||
|
case UserAPIAccounts:
|
||||||
|
pgaccounts.LoadFromGoose()
|
||||||
|
case UserAPIDevices:
|
||||||
|
pgdevices.LoadFromGoose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
130
internal/sqlutil/migrate.go
Normal file
130
internal/sqlutil/migrate.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package sqlutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/config"
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Migrations struct {
|
||||||
|
registeredGoMigrations map[int64]*goose.Migration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMigrations() *Migrations {
|
||||||
|
return &Migrations{
|
||||||
|
registeredGoMigrations: make(map[int64]*goose.Migration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy-pasted from goose directly to store migrations into a map we control
|
||||||
|
|
||||||
|
// AddMigration adds a migration.
|
||||||
|
func (m *Migrations) AddMigration(up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||||
|
_, filename, _, _ := runtime.Caller(1)
|
||||||
|
m.AddNamedMigration(filename, up, down)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddNamedMigration : Add a named migration.
|
||||||
|
func (m *Migrations) AddNamedMigration(filename string, up func(*sql.Tx) error, down func(*sql.Tx) error) {
|
||||||
|
v, _ := goose.NumericComponent(filename)
|
||||||
|
migration := &goose.Migration{Version: v, Next: -1, Previous: -1, Registered: true, UpFn: up, DownFn: down, Source: filename}
|
||||||
|
|
||||||
|
if existing, ok := m.registeredGoMigrations[v]; ok {
|
||||||
|
panic(fmt.Sprintf("failed to add migration %q: version conflicts with %q", filename, existing.Source))
|
||||||
|
}
|
||||||
|
|
||||||
|
m.registeredGoMigrations[v] = migration
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunDeltas up to the latest version.
|
||||||
|
func (m *Migrations) RunDeltas(db *sql.DB, props *config.DatabaseOptions) error {
|
||||||
|
maxVer := goose.MaxVersion
|
||||||
|
minVer := int64(0)
|
||||||
|
migrations, err := m.collect(minVer, maxVer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("RunDeltas: Failed to collect migrations: %w", err)
|
||||||
|
}
|
||||||
|
if props.ConnectionString.IsPostgres() {
|
||||||
|
if err = goose.SetDialect("postgres"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if props.ConnectionString.IsSQLite() {
|
||||||
|
if err = goose.SetDialect("sqlite3"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unknown connection string: %s", props.ConnectionString)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
current, err := goose.EnsureDBVersion(db)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("RunDeltas: Failed to EnsureDBVersion: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
next, err := migrations.Next(current)
|
||||||
|
if err != nil {
|
||||||
|
if err == goose.ErrNoNextVersion {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("RunDeltas: Failed to load next migration to %+v : %w", next, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = next.Up(db); err != nil {
|
||||||
|
return fmt.Errorf("RunDeltas: Failed run migration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Migrations) collect(current, target int64) (goose.Migrations, error) {
|
||||||
|
var migrations goose.Migrations
|
||||||
|
|
||||||
|
// Go migrations registered via goose.AddMigration().
|
||||||
|
for _, migration := range m.registeredGoMigrations {
|
||||||
|
v, err := goose.NumericComponent(migration.Source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if versionFilter(v, current, target) {
|
||||||
|
migrations = append(migrations, migration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations = sortAndConnectMigrations(migrations)
|
||||||
|
|
||||||
|
return migrations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortAndConnectMigrations(migrations goose.Migrations) goose.Migrations {
|
||||||
|
sort.Sort(migrations)
|
||||||
|
|
||||||
|
// now that we're sorted in the appropriate direction,
|
||||||
|
// populate next and previous for each migration
|
||||||
|
for i, m := range migrations {
|
||||||
|
prev := int64(-1)
|
||||||
|
if i > 0 {
|
||||||
|
prev = migrations[i-1].Version
|
||||||
|
migrations[i-1].Next = m.Version
|
||||||
|
}
|
||||||
|
migrations[i].Previous = prev
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrations
|
||||||
|
}
|
||||||
|
|
||||||
|
func versionFilter(v, current, target int64) bool {
|
||||||
|
|
||||||
|
if target > current {
|
||||||
|
return v > current && v <= target
|
||||||
|
}
|
||||||
|
|
||||||
|
if target < current {
|
||||||
|
return v <= current && v > target
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -75,11 +75,12 @@ type accountsStatements struct {
|
||||||
serverName gomatrixserverlib.ServerName
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) execSchema(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(accountsSchema)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
_, err = db.Exec(accountsSchema)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
|
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package deltas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadFromGoose() {
|
||||||
|
goose.AddMigration(UpIsActive, DownIsActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadIsActive(m *sqlutil.Migrations) {
|
||||||
|
m.AddMigration(UpIsActive, DownIsActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpIsActive(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS is_deactivated BOOLEAN DEFAULT FALSE;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownIsActive(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec("ALTER TABLE account_accounts DROP COLUMN is_deactivated;")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute downgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE account_accounts ADD COLUMN IF NOT EXISTS is_deactivated BOOLEAN DEFAULT FALSE;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE account_accounts DROP COLUMN is_deactivated;
|
|
||||||
-- +goose StatementEnd
|
|
|
@ -25,6 +25,8 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/config"
|
"github.com/matrix-org/dendrite/internal/config"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/storage/accounts/postgres/deltas"
|
||||||
|
_ "github.com/matrix-org/dendrite/userapi/storage/accounts/postgres/deltas"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
@ -55,6 +57,18 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
db: db,
|
db: db,
|
||||||
writer: sqlutil.NewDummyWriter(),
|
writer: sqlutil.NewDummyWriter(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||||
|
// and THEN prepare statements so we don't fail due to referencing new columns
|
||||||
|
if err = d.accounts.execSchema(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := sqlutil.NewMigrations()
|
||||||
|
deltas.LoadIsActive(m)
|
||||||
|
if err = m.RunDeltas(db, dbProperties); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err = d.PartitionOffsetStatements.Prepare(db, d.writer, "account"); err != nil {
|
if err = d.PartitionOffsetStatements.Prepare(db, d.writer, "account"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,6 +84,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
if err = d.threepids.prepare(db); err != nil {
|
if err = d.threepids.prepare(db); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,13 +74,13 @@ type accountsStatements struct {
|
||||||
serverName gomatrixserverlib.ServerName
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *accountsStatements) execSchema(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(accountsSchema)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
s.db = db
|
s.db = db
|
||||||
|
|
||||||
_, err = db.Exec(accountsSchema)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
|
if s.insertAccountStmt, err = db.Prepare(insertAccountSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package deltas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadFromGoose() {
|
||||||
|
goose.AddMigration(UpIsActive, DownIsActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadIsActive(m *sqlutil.Migrations) {
|
||||||
|
m.AddMigration(UpIsActive, DownIsActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpIsActive(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
|
||||||
|
CREATE TABLE account_accounts (
|
||||||
|
localpart TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_ts BIGINT NOT NULL,
|
||||||
|
password_hash TEXT,
|
||||||
|
appservice_id TEXT,
|
||||||
|
is_deactivated BOOLEAN DEFAULT 0
|
||||||
|
);
|
||||||
|
INSERT
|
||||||
|
INTO account_accounts (
|
||||||
|
localpart, created_ts, password_hash, appservice_id
|
||||||
|
) SELECT
|
||||||
|
localpart, created_ts, password_hash, appservice_id
|
||||||
|
FROM account_accounts_tmp
|
||||||
|
;
|
||||||
|
DROP TABLE account_accounts_tmp;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownIsActive(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
|
||||||
|
CREATE TABLE account_accounts (
|
||||||
|
localpart TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_ts BIGINT NOT NULL,
|
||||||
|
password_hash TEXT,
|
||||||
|
appservice_id TEXT
|
||||||
|
);
|
||||||
|
INSERT
|
||||||
|
INTO account_accounts (
|
||||||
|
localpart, created_ts, password_hash, appservice_id
|
||||||
|
) SELECT
|
||||||
|
localpart, created_ts, password_hash, appservice_id
|
||||||
|
FROM account_accounts_tmp
|
||||||
|
;
|
||||||
|
DROP TABLE account_accounts_tmp;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute downgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
|
|
||||||
CREATE TABLE account_accounts (
|
|
||||||
localpart TEXT NOT NULL PRIMARY KEY,
|
|
||||||
created_ts BIGINT NOT NULL,
|
|
||||||
password_hash TEXT,
|
|
||||||
appservice_id TEXT,
|
|
||||||
is_deactivated BOOLEAN DEFAULT 0
|
|
||||||
);
|
|
||||||
INSERT
|
|
||||||
INTO account_accounts (
|
|
||||||
localpart, created_ts, password_hash, appservice_id
|
|
||||||
) SELECT
|
|
||||||
localpart, created_ts, password_hash, appservice_id
|
|
||||||
FROM account_accounts_tmp
|
|
||||||
;
|
|
||||||
DROP TABLE account_accounts_tmp;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE account_accounts RENAME TO account_accounts_tmp;
|
|
||||||
CREATE TABLE account_accounts (
|
|
||||||
localpart TEXT NOT NULL PRIMARY KEY,
|
|
||||||
created_ts BIGINT NOT NULL,
|
|
||||||
password_hash TEXT,
|
|
||||||
appservice_id TEXT
|
|
||||||
);
|
|
||||||
INSERT
|
|
||||||
INTO account_accounts (
|
|
||||||
localpart, created_ts, password_hash, appservice_id
|
|
||||||
) SELECT
|
|
||||||
localpart, created_ts, password_hash, appservice_id
|
|
||||||
FROM account_accounts_tmp
|
|
||||||
;
|
|
||||||
DROP TABLE account_accounts_tmp;
|
|
||||||
-- +goose StatementEnd
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/config"
|
"github.com/matrix-org/dendrite/internal/config"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/storage/accounts/sqlite3/deltas"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
// Import the sqlite3 database driver.
|
// Import the sqlite3 database driver.
|
||||||
|
@ -60,6 +61,18 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
db: db,
|
db: db,
|
||||||
writer: sqlutil.NewExclusiveWriter(),
|
writer: sqlutil.NewExclusiveWriter(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||||
|
// and THEN prepare statements so we don't fail due to referencing new columns
|
||||||
|
if err = d.accounts.execSchema(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := sqlutil.NewMigrations()
|
||||||
|
deltas.LoadIsActive(m)
|
||||||
|
if err = m.RunDeltas(db, dbProperties); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
partitions := sqlutil.PartitionOffsetStatements{}
|
partitions := sqlutil.PartitionOffsetStatements{}
|
||||||
if err = partitions.Prepare(db, d.writer, "account"); err != nil {
|
if err = partitions.Prepare(db, d.writer, "account"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -76,6 +89,7 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
if err = d.threepids.prepare(db); err != nil {
|
if err = d.threepids.prepare(db); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package deltas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadFromGoose() {
|
||||||
|
goose.AddMigration(UpLastSeenTSIP, DownLastSeenTSIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadLastSeenTSIP(m *sqlutil.Migrations) {
|
||||||
|
m.AddMigration(UpLastSeenTSIP, DownLastSeenTSIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpLastSeenTSIP(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS last_seen_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)*1000;
|
||||||
|
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS ip TEXT;
|
||||||
|
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS user_agent TEXT;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownLastSeenTSIP(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE device_devices DROP COLUMN last_seen_ts;
|
||||||
|
ALTER TABLE device_devices DROP COLUMN ip;
|
||||||
|
ALTER TABLE device_devices DROP COLUMN user_agent;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute downgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS last_seen_ts BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)*1000;
|
|
||||||
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS ip TEXT;
|
|
||||||
ALTER TABLE device_devices ADD COLUMN IF NOT EXISTS user_agent TEXT;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE device_devices DROP COLUMN last_seen_ts;
|
|
||||||
ALTER TABLE device_devices DROP COLUMN ip;
|
|
||||||
ALTER TABLE device_devices DROP COLUMN user_agent;
|
|
||||||
-- +goose StatementEnd
|
|
|
@ -111,11 +111,12 @@ type devicesStatements struct {
|
||||||
serverName gomatrixserverlib.ServerName
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) execSchema(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(devicesSchema)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) {
|
||||||
_, err = db.Exec(devicesSchema)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.insertDeviceStmt, err = db.Prepare(insertDeviceSQL); err != nil {
|
if s.insertDeviceStmt, err = db.Prepare(insertDeviceSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/config"
|
"github.com/matrix-org/dendrite/internal/config"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/storage/devices/postgres/deltas"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,9 +43,22 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d := devicesStatements{}
|
d := devicesStatements{}
|
||||||
|
|
||||||
|
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||||
|
// and THEN prepare statements so we don't fail due to referencing new columns
|
||||||
|
if err = d.execSchema(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := sqlutil.NewMigrations()
|
||||||
|
deltas.LoadLastSeenTSIP(m)
|
||||||
|
if err = m.RunDeltas(db, dbProperties); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err = d.prepare(db, serverName); err != nil {
|
if err = d.prepare(db, serverName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Database{db, d}, nil
|
return &Database{db, d}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package deltas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
|
"github.com/pressly/goose"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadFromGoose() {
|
||||||
|
goose.AddMigration(UpLastSeenTSIP, DownLastSeenTSIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadLastSeenTSIP(m *sqlutil.Migrations) {
|
||||||
|
m.AddMigration(UpLastSeenTSIP, DownLastSeenTSIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpLastSeenTSIP(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE device_devices RENAME TO device_devices_tmp;
|
||||||
|
CREATE TABLE device_devices (
|
||||||
|
access_token TEXT PRIMARY KEY,
|
||||||
|
session_id INTEGER,
|
||||||
|
device_id TEXT ,
|
||||||
|
localpart TEXT ,
|
||||||
|
created_ts BIGINT,
|
||||||
|
display_name TEXT,
|
||||||
|
last_seen_ts BIGINT,
|
||||||
|
ip TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
UNIQUE (localpart, device_id)
|
||||||
|
);
|
||||||
|
INSERT
|
||||||
|
INTO device_devices (
|
||||||
|
access_token, session_id, device_id, localpart, created_ts, display_name, last_seen_ts, ip, user_agent
|
||||||
|
) SELECT
|
||||||
|
access_token, session_id, device_id, localpart, created_ts, display_name, created_ts, '', ''
|
||||||
|
FROM device_devices_tmp;
|
||||||
|
DROP TABLE device_devices_tmp;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownLastSeenTSIP(tx *sql.Tx) error {
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
ALTER TABLE device_devices RENAME TO device_devices_tmp;
|
||||||
|
CREATE TABLE IF NOT EXISTS device_devices (
|
||||||
|
access_token TEXT PRIMARY KEY,
|
||||||
|
session_id INTEGER,
|
||||||
|
device_id TEXT ,
|
||||||
|
localpart TEXT ,
|
||||||
|
created_ts BIGINT,
|
||||||
|
display_name TEXT,
|
||||||
|
UNIQUE (localpart, device_id)
|
||||||
|
);
|
||||||
|
INSERT
|
||||||
|
INTO device_devices (
|
||||||
|
access_token, session_id, device_id, localpart, created_ts, display_name
|
||||||
|
) SELECT
|
||||||
|
access_token, session_id, device_id, localpart, created_ts, display_name
|
||||||
|
FROM device_devices_tmp;
|
||||||
|
DROP TABLE device_devices_tmp;`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute downgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
-- +goose Up
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE device_devices RENAME TO device_devices_tmp;
|
|
||||||
CREATE TABLE device_devices (
|
|
||||||
access_token TEXT PRIMARY KEY,
|
|
||||||
session_id INTEGER,
|
|
||||||
device_id TEXT ,
|
|
||||||
localpart TEXT ,
|
|
||||||
created_ts BIGINT,
|
|
||||||
display_name TEXT,
|
|
||||||
last_seen_ts BIGINT,
|
|
||||||
ip TEXT,
|
|
||||||
user_agent TEXT,
|
|
||||||
UNIQUE (localpart, device_id)
|
|
||||||
);
|
|
||||||
INSERT
|
|
||||||
INTO device_devices (
|
|
||||||
access_token, session_id, device_id, localpart, created_ts, display_name, last_seen_ts, ip, user_agent
|
|
||||||
) SELECT
|
|
||||||
access_token, session_id, device_id, localpart, created_ts, display_name, created_ts, '', ''
|
|
||||||
FROM device_devices_tmp;
|
|
||||||
DROP TABLE device_devices_tmp;
|
|
||||||
-- +goose StatementEnd
|
|
||||||
|
|
||||||
-- +goose Down
|
|
||||||
-- +goose StatementBegin
|
|
||||||
ALTER TABLE device_devices RENAME TO device_devices_tmp;
|
|
||||||
CREATE TABLE IF NOT EXISTS device_devices (
|
|
||||||
access_token TEXT PRIMARY KEY,
|
|
||||||
session_id INTEGER,
|
|
||||||
device_id TEXT ,
|
|
||||||
localpart TEXT ,
|
|
||||||
created_ts BIGINT,
|
|
||||||
display_name TEXT,
|
|
||||||
UNIQUE (localpart, device_id)
|
|
||||||
);
|
|
||||||
INSERT
|
|
||||||
INTO device_devices (
|
|
||||||
access_token, session_id, device_id, localpart, created_ts, display_name
|
|
||||||
) SELECT
|
|
||||||
access_token, session_id, device_id, localpart, created_ts, display_name
|
|
||||||
FROM device_devices_tmp;
|
|
||||||
DROP TABLE device_devices_tmp;
|
|
||||||
-- +goose StatementEnd
|
|
|
@ -98,13 +98,14 @@ type devicesStatements struct {
|
||||||
serverName gomatrixserverlib.ServerName
|
serverName gomatrixserverlib.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *devicesStatements) execSchema(db *sql.DB) error {
|
||||||
|
_, err := db.Exec(devicesSchema)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *devicesStatements) prepare(db *sql.DB, writer sqlutil.Writer, server gomatrixserverlib.ServerName) (err error) {
|
func (s *devicesStatements) prepare(db *sql.DB, writer sqlutil.Writer, server gomatrixserverlib.ServerName) (err error) {
|
||||||
s.db = db
|
s.db = db
|
||||||
s.writer = writer
|
s.writer = writer
|
||||||
_, err = db.Exec(devicesSchema)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.insertDeviceStmt, err = db.Prepare(insertDeviceSQL); err != nil {
|
if s.insertDeviceStmt, err = db.Prepare(insertDeviceSQL); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/matrix-org/dendrite/internal/config"
|
"github.com/matrix-org/dendrite/internal/config"
|
||||||
"github.com/matrix-org/dendrite/internal/sqlutil"
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
|
"github.com/matrix-org/dendrite/userapi/storage/devices/sqlite3/deltas"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
@ -46,6 +47,17 @@ func NewDatabase(dbProperties *config.DatabaseOptions, serverName gomatrixserver
|
||||||
}
|
}
|
||||||
writer := sqlutil.NewExclusiveWriter()
|
writer := sqlutil.NewExclusiveWriter()
|
||||||
d := devicesStatements{}
|
d := devicesStatements{}
|
||||||
|
|
||||||
|
// Create tables before executing migrations so we don't fail if the table is missing,
|
||||||
|
// and THEN prepare statements so we don't fail due to referencing new columns
|
||||||
|
if err = d.execSchema(db); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := sqlutil.NewMigrations()
|
||||||
|
deltas.LoadLastSeenTSIP(m)
|
||||||
|
if err = m.RunDeltas(db, dbProperties); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err = d.prepare(db, writer, serverName); err != nil {
|
if err = d.prepare(db, writer, serverName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue