539 lines
17 KiB
Go
539 lines
17 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
// Migration represents a database migration
|
|
type Migration struct {
|
|
Version int
|
|
Description string
|
|
PostgreSQLSQL string
|
|
MySQLSQL string
|
|
}
|
|
|
|
// MigrationRecord represents a record of an applied migration
|
|
type MigrationRecord struct {
|
|
Version int
|
|
Description string
|
|
AppliedAt time.Time
|
|
}
|
|
|
|
// EnsureMigrationsTable creates the migrations table if it doesn't exist
|
|
func EnsureMigrationsTable(db *sql.DB, dbType string) error {
|
|
log.Println("Creating GpodderSyncMigrations table if it doesn't exist...")
|
|
|
|
var query string
|
|
if dbType == "postgresql" {
|
|
query = `
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncMigrations" (
|
|
Version INT PRIMARY KEY,
|
|
Description TEXT NOT NULL,
|
|
AppliedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`
|
|
} else {
|
|
query = `
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncMigrations (
|
|
Version INT PRIMARY KEY,
|
|
Description TEXT NOT NULL,
|
|
AppliedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`
|
|
}
|
|
|
|
_, err := db.Exec(query)
|
|
if err != nil {
|
|
log.Printf("Error creating migrations table: %v", err)
|
|
return err
|
|
}
|
|
log.Println("GpodderSyncMigrations table is ready")
|
|
return nil
|
|
}
|
|
|
|
// GetAppliedMigrations returns a list of already applied migrations
|
|
func GetAppliedMigrations(db *sql.DB, dbType string) ([]MigrationRecord, error) {
|
|
log.Println("Checking previously applied migrations...")
|
|
|
|
var query string
|
|
if dbType == "postgresql" {
|
|
query = `
|
|
SELECT Version, Description, AppliedAt
|
|
FROM "GpodderSyncMigrations"
|
|
ORDER BY Version ASC
|
|
`
|
|
} else {
|
|
query = `
|
|
SELECT Version, Description, AppliedAt
|
|
FROM GpodderSyncMigrations
|
|
ORDER BY Version ASC
|
|
`
|
|
}
|
|
|
|
rows, err := db.Query(query)
|
|
if err != nil {
|
|
log.Printf("Error checking applied migrations: %v", err)
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var migrations []MigrationRecord
|
|
for rows.Next() {
|
|
var m MigrationRecord
|
|
if err := rows.Scan(&m.Version, &m.Description, &m.AppliedAt); err != nil {
|
|
log.Printf("Error scanning migration record: %v", err)
|
|
return nil, err
|
|
}
|
|
migrations = append(migrations, m)
|
|
}
|
|
|
|
if len(migrations) > 0 {
|
|
log.Printf("Found %d previously applied migrations", len(migrations))
|
|
} else {
|
|
log.Println("No previously applied migrations found")
|
|
}
|
|
return migrations, nil
|
|
}
|
|
|
|
// ApplyMigration applies a single migration
|
|
func ApplyMigration(db *sql.DB, migration Migration, dbType string) error {
|
|
log.Printf("Applying migration %d: %s", migration.Version, migration.Description)
|
|
|
|
// Select the appropriate SQL based on database type
|
|
var sql string
|
|
if dbType == "postgresql" {
|
|
sql = migration.PostgreSQLSQL
|
|
} else {
|
|
sql = migration.MySQLSQL
|
|
}
|
|
|
|
// Begin transaction
|
|
tx, err := db.Begin()
|
|
if err != nil {
|
|
log.Printf("Error beginning transaction for migration %d: %v", migration.Version, err)
|
|
return err
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
log.Printf("Rolling back migration %d due to error", migration.Version)
|
|
tx.Rollback()
|
|
return
|
|
}
|
|
}()
|
|
|
|
// Execute the migration SQL
|
|
_, err = tx.Exec(sql)
|
|
if err != nil {
|
|
log.Printf("Failed to apply migration %d: %v", migration.Version, err)
|
|
return fmt.Errorf("failed to apply migration %d: %w", migration.Version, err)
|
|
}
|
|
|
|
// Record the migration
|
|
var insertQuery string
|
|
if dbType == "postgresql" {
|
|
insertQuery = `
|
|
INSERT INTO "GpodderSyncMigrations" (Version, Description)
|
|
VALUES ($1, $2)
|
|
`
|
|
} else {
|
|
insertQuery = `
|
|
INSERT INTO GpodderSyncMigrations (Version, Description)
|
|
VALUES (?, ?)
|
|
`
|
|
}
|
|
|
|
_, err = tx.Exec(insertQuery, migration.Version, migration.Description)
|
|
if err != nil {
|
|
log.Printf("Failed to record migration %d: %v", migration.Version, err)
|
|
return fmt.Errorf("failed to record migration %d: %w", migration.Version, err)
|
|
}
|
|
|
|
// Commit the transaction
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
log.Printf("Failed to commit migration %d: %v", migration.Version, err)
|
|
return err
|
|
}
|
|
|
|
log.Printf("Successfully applied migration %d", migration.Version)
|
|
return nil
|
|
}
|
|
|
|
// checkRequiredTables verifies that required PinePods tables exist before running migrations
|
|
func checkRequiredTables(db *sql.DB, dbType string) error {
|
|
log.Println("Checking for required PinePods tables...")
|
|
|
|
requiredTables := []string{"Users", "GpodderDevices"}
|
|
|
|
for _, table := range requiredTables {
|
|
var query string
|
|
if dbType == "postgresql" {
|
|
query = `SELECT 1 FROM "` + table + `" LIMIT 1`
|
|
} else {
|
|
query = `SELECT 1 FROM ` + table + ` LIMIT 1`
|
|
}
|
|
|
|
_, err := db.Exec(query)
|
|
if err != nil {
|
|
log.Printf("Required table %s does not exist or is not accessible: %v", table, err)
|
|
return fmt.Errorf("required table %s does not exist - please ensure PinePods main migrations have run first", table)
|
|
}
|
|
log.Printf("Required table %s exists", table)
|
|
}
|
|
|
|
log.Println("All required tables found")
|
|
return nil
|
|
}
|
|
|
|
// RunMigrations runs all pending migrations
|
|
func RunMigrations(db *sql.DB, dbType string) error {
|
|
log.Println("Starting gpodder API migrations...")
|
|
|
|
// Check that required PinePods tables exist first
|
|
if err := checkRequiredTables(db, dbType); err != nil {
|
|
return fmt.Errorf("prerequisite check failed: %w", err)
|
|
}
|
|
|
|
// Ensure migrations table exists
|
|
if err := EnsureMigrationsTable(db, dbType); err != nil {
|
|
return fmt.Errorf("failed to create migrations table: %w", err)
|
|
}
|
|
|
|
// Get applied migrations
|
|
appliedMigrations, err := GetAppliedMigrations(db, dbType)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get applied migrations: %w", err)
|
|
}
|
|
|
|
// Build a map of applied migration versions for quick lookup
|
|
appliedVersions := make(map[int]bool)
|
|
for _, m := range appliedMigrations {
|
|
appliedVersions[m.Version] = true
|
|
}
|
|
|
|
// Get all migrations
|
|
migrations := GetMigrations()
|
|
log.Printf("Found %d total migrations to check", len(migrations))
|
|
|
|
// Apply pending migrations
|
|
appliedCount := 0
|
|
for _, migration := range migrations {
|
|
if appliedVersions[migration.Version] {
|
|
// Migration already applied, skip
|
|
log.Printf("Migration %d already applied, skipping", migration.Version)
|
|
continue
|
|
}
|
|
|
|
log.Printf("Applying migration %d: %s", migration.Version, migration.Description)
|
|
if err := ApplyMigration(db, migration, dbType); err != nil {
|
|
return err
|
|
}
|
|
appliedCount++
|
|
}
|
|
|
|
if appliedCount > 0 {
|
|
log.Printf("Successfully applied %d new migrations", appliedCount)
|
|
} else {
|
|
log.Println("No new migrations to apply")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetMigrations returns all migrations with SQL variants for both database types
|
|
func GetMigrations() []Migration {
|
|
return []Migration{
|
|
{
|
|
Version: 1,
|
|
Description: "Initial schema creation",
|
|
PostgreSQLSQL: `
|
|
-- Device sync state for the API
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncDeviceState" (
|
|
DeviceStateID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
SubscriptionCount INT DEFAULT 0,
|
|
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID)
|
|
);
|
|
|
|
-- Subscription changes
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncSubscriptions" (
|
|
SubscriptionID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
PodcastURL TEXT NOT NULL,
|
|
Action VARCHAR(10) NOT NULL,
|
|
Timestamp BIGINT NOT NULL,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Episode actions
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncEpisodeActions" (
|
|
ActionID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT,
|
|
PodcastURL TEXT NOT NULL,
|
|
EpisodeURL TEXT NOT NULL,
|
|
Action VARCHAR(20) NOT NULL,
|
|
Timestamp BIGINT NOT NULL,
|
|
Started INT,
|
|
Position INT,
|
|
Total INT,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Podcast lists
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncPodcastLists" (
|
|
ListID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
Name VARCHAR(255) NOT NULL,
|
|
Title VARCHAR(255) NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, Name)
|
|
);
|
|
|
|
-- Podcast list entries
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncPodcastListEntries" (
|
|
EntryID SERIAL PRIMARY KEY,
|
|
ListID INT NOT NULL,
|
|
PodcastURL TEXT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (ListID) REFERENCES "GpodderSyncPodcastLists"(ListID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Synchronization relationships between devices
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncDevicePairs" (
|
|
PairID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID1 INT NOT NULL,
|
|
DeviceID2 INT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID1) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID2) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID1, DeviceID2)
|
|
);
|
|
|
|
-- Settings storage
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncSettings" (
|
|
SettingID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
Scope VARCHAR(20) NOT NULL,
|
|
DeviceID INT,
|
|
PodcastURL TEXT,
|
|
EpisodeURL TEXT,
|
|
SettingKey VARCHAR(255) NOT NULL,
|
|
SettingValue TEXT,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Create indexes for faster queries
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_subscriptions_userid ON "GpodderSyncSubscriptions"(UserID);
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_subscriptions_deviceid ON "GpodderSyncSubscriptions"(DeviceID);
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_episode_actions_userid ON "GpodderSyncEpisodeActions"(UserID);
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sync_podcast_lists_userid ON "GpodderSyncPodcastLists"(UserID);
|
|
`,
|
|
MySQLSQL: `
|
|
-- Device sync state for the API
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncDeviceState (
|
|
DeviceStateID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
SubscriptionCount INT DEFAULT 0,
|
|
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID)
|
|
);
|
|
|
|
-- Subscription changes
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncSubscriptions (
|
|
SubscriptionID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
PodcastURL TEXT NOT NULL,
|
|
Action VARCHAR(10) NOT NULL,
|
|
Timestamp BIGINT NOT NULL,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Episode actions
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncEpisodeActions (
|
|
ActionID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT,
|
|
PodcastURL TEXT NOT NULL,
|
|
EpisodeURL TEXT NOT NULL,
|
|
Action VARCHAR(20) NOT NULL,
|
|
Timestamp BIGINT NOT NULL,
|
|
Started INT,
|
|
Position INT,
|
|
Total INT,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Podcast lists
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncPodcastLists (
|
|
ListID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
Name VARCHAR(255) NOT NULL,
|
|
Title VARCHAR(255) NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, Name)
|
|
);
|
|
|
|
-- Podcast list entries
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncPodcastListEntries (
|
|
EntryID INT AUTO_INCREMENT PRIMARY KEY,
|
|
ListID INT NOT NULL,
|
|
PodcastURL TEXT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (ListID) REFERENCES GpodderSyncPodcastLists(ListID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Synchronization relationships between devices
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncDevicePairs (
|
|
PairID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID1 INT NOT NULL,
|
|
DeviceID2 INT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID1) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID2) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID1, DeviceID2)
|
|
);
|
|
|
|
-- Settings storage
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncSettings (
|
|
SettingID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
Scope VARCHAR(20) NOT NULL,
|
|
DeviceID INT,
|
|
PodcastURL TEXT,
|
|
EpisodeURL TEXT,
|
|
SettingKey VARCHAR(255) NOT NULL,
|
|
SettingValue TEXT,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
LastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Create indexes for faster queries
|
|
CREATE INDEX idx_gpodder_sync_subscriptions_userid ON GpodderSyncSubscriptions(UserID);
|
|
CREATE INDEX idx_gpodder_sync_subscriptions_deviceid ON GpodderSyncSubscriptions(DeviceID);
|
|
CREATE INDEX idx_gpodder_sync_episode_actions_userid ON GpodderSyncEpisodeActions(UserID);
|
|
CREATE INDEX idx_gpodder_sync_podcast_lists_userid ON GpodderSyncPodcastLists(UserID);
|
|
`,
|
|
},
|
|
{
|
|
Version: 2,
|
|
Description: "Add API version column to GpodderSyncSettings",
|
|
PostgreSQLSQL: `
|
|
ALTER TABLE "GpodderSyncSettings"
|
|
ADD COLUMN IF NOT EXISTS APIVersion VARCHAR(10) DEFAULT '2.0';
|
|
`,
|
|
MySQLSQL: `
|
|
-- Check if column exists first
|
|
SET @s = (SELECT IF(
|
|
COUNT(*) = 0,
|
|
'ALTER TABLE GpodderSyncSettings ADD COLUMN APIVersion VARCHAR(10) DEFAULT "2.0"',
|
|
'SELECT 1'
|
|
) FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'GpodderSyncSettings'
|
|
AND COLUMN_NAME = 'APIVersion');
|
|
|
|
PREPARE stmt FROM @s;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
`,
|
|
},
|
|
{
|
|
Version: 3,
|
|
Description: "Create GpodderSessions table for API sessions",
|
|
PostgreSQLSQL: `
|
|
CREATE TABLE IF NOT EXISTS "GpodderSessions" (
|
|
SessionID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
SessionToken TEXT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
ExpiresAt TIMESTAMP NOT NULL,
|
|
LastActive TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UserAgent TEXT,
|
|
ClientIP TEXT,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
UNIQUE(SessionToken)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_token ON "GpodderSessions"(SessionToken);
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_userid ON "GpodderSessions"(UserID);
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_sessions_expires ON "GpodderSessions"(ExpiresAt);
|
|
`,
|
|
MySQLSQL: `
|
|
CREATE TABLE IF NOT EXISTS GpodderSessions (
|
|
SessionID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
SessionToken TEXT NOT NULL,
|
|
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
ExpiresAt TIMESTAMP NOT NULL,
|
|
LastActive TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UserAgent TEXT,
|
|
ClientIP TEXT,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX idx_gpodder_sessions_userid ON GpodderSessions(UserID);
|
|
CREATE INDEX idx_gpodder_sessions_expires ON GpodderSessions(ExpiresAt);
|
|
`,
|
|
},
|
|
{
|
|
Version: 4,
|
|
Description: "Add sync state table for tracking device sync status",
|
|
PostgreSQLSQL: `
|
|
CREATE TABLE IF NOT EXISTS "GpodderSyncState" (
|
|
SyncStateID SERIAL PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
LastTimestamp BIGINT DEFAULT 0,
|
|
LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_gpodder_syncstate_userid_deviceid ON "GpodderSyncState"(UserID, DeviceID);
|
|
`,
|
|
MySQLSQL: `
|
|
CREATE TABLE IF NOT EXISTS GpodderSyncState (
|
|
SyncStateID INT AUTO_INCREMENT PRIMARY KEY,
|
|
UserID INT NOT NULL,
|
|
DeviceID INT NOT NULL,
|
|
LastTimestamp BIGINT DEFAULT 0,
|
|
LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (UserID) REFERENCES Users(UserID) ON DELETE CASCADE,
|
|
FOREIGN KEY (DeviceID) REFERENCES GpodderDevices(DeviceID) ON DELETE CASCADE,
|
|
UNIQUE(UserID, DeviceID)
|
|
);
|
|
|
|
CREATE INDEX idx_gpodder_syncstate_userid_deviceid ON GpodderSyncState(UserID, DeviceID);
|
|
`,
|
|
},
|
|
}
|
|
}
|