mirror of
https://github.com/usememos/memos.git
synced 2024-11-23 13:56:22 +03:00
refactor: store migrator
This commit is contained in:
parent
1ae3afc0ba
commit
6e901fc940
@ -58,16 +58,11 @@ var (
|
||||
slog.Error("failed to create db driver", "error", err)
|
||||
return
|
||||
}
|
||||
if err := dbDriver.Migrate(ctx); err != nil {
|
||||
cancel()
|
||||
slog.Error("failed to migrate database", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
storeInstance := store.New(dbDriver, instanceProfile)
|
||||
if err := storeInstance.MigrateManually(ctx); err != nil {
|
||||
if err := storeInstance.Migrate(ctx); err != nil {
|
||||
cancel()
|
||||
slog.Error("failed to migrate manually", "error", err)
|
||||
slog.Error("failed to migrate", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ root = "."
|
||||
tmp_dir = ".air"
|
||||
|
||||
[build]
|
||||
bin = "./.air/memos --mode dev --public true"
|
||||
bin = "./.air/memos --mode demo --public true"
|
||||
cmd = "go build -o ./.air/memos ./bin/memos/main.go"
|
||||
delay = 1000
|
||||
exclude_dir = [".air", "web", "build"]
|
||||
|
@ -1,167 +0,0 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/usememos/memos/server/version"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
//go:embed migration
|
||||
var migrationFS embed.FS
|
||||
|
||||
const (
|
||||
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
||||
)
|
||||
|
||||
func (d *DB) Migrate(ctx context.Context) error {
|
||||
if d.profile.IsDev() {
|
||||
return d.nonProdMigrate(ctx)
|
||||
}
|
||||
|
||||
return d.prodMigrate(ctx)
|
||||
}
|
||||
|
||||
func (d *DB) nonProdMigrate(ctx context.Context) error {
|
||||
rows, err := d.db.QueryContext(ctx, "SHOW TABLES")
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to query database tables: %s", err)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
return errors.Errorf("failed to query database tables: %s", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tables []string
|
||||
for rows.Next() {
|
||||
var table string
|
||||
err := rows.Scan(&table)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to scan table name: %s", err)
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
|
||||
if len(tables) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||
}
|
||||
|
||||
stmt := string(buf)
|
||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) prodMigrate(ctx context.Context) error {
|
||||
currentVersion := version.GetCurrentVersion(d.profile.Mode)
|
||||
migrationHistoryList, err := d.FindMigrationHistoryList(ctx, &store.FindMigrationHistory{})
|
||||
// If there is no migration history, we should apply the latest schema.
|
||||
if err != nil || len(migrationHistoryList) == 0 {
|
||||
buf, err := migrationFS.ReadFile("migration/prod/" + latestSchemaFileName)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||
}
|
||||
if _, err := d.db.ExecContext(ctx, string(buf)); err != nil {
|
||||
return errors.Errorf("failed to exec latest schema: %s", err)
|
||||
}
|
||||
if _, err := d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{
|
||||
Version: currentVersion,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to upsert migration history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
migrationHistoryVersionList := []string{}
|
||||
for _, migrationHistory := range migrationHistoryList {
|
||||
migrationHistoryVersionList = append(migrationHistoryVersionList, migrationHistory.Version)
|
||||
}
|
||||
sort.Sort(version.SortVersion(migrationHistoryVersionList))
|
||||
latestMigrationHistoryVersion := migrationHistoryVersionList[len(migrationHistoryVersionList)-1]
|
||||
if !version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("start to migrate database schema")
|
||||
for _, minorVersion := range getMinorVersionList() {
|
||||
normalizedVersion := minorVersion + ".0"
|
||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||
fmt.Println("applying migration of", normalizedVersion)
|
||||
if err := d.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||
return errors.Wrap(err, "failed to apply minor version migration")
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("end migrate")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("migration/prod/%s/*.sql", minorVersion))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read ddl files")
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
// Loop over all migration files and execute them in order.
|
||||
for _, filename := range filenames {
|
||||
buf, err := migrationFS.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
||||
}
|
||||
for _, stmt := range strings.Split(string(buf), ";") {
|
||||
if strings.TrimSpace(stmt) == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Wrapf(err, "migrate error: %s", stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert the newest version to migration_history.
|
||||
version := minorVersion + ".0"
|
||||
if _, err = d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{Version: version}); err != nil {
|
||||
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// minorDirRegexp is a regular expression for minor version directory.
|
||||
var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`)
|
||||
|
||||
func getMinorVersionList() []string {
|
||||
minorVersionList := []string{}
|
||||
|
||||
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
||||
minorVersionList = append(minorVersionList, file.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Sort(version.SortVersion(minorVersionList))
|
||||
|
||||
return minorVersionList
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log/slog"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/pkg/errors"
|
||||
@ -45,30 +43,8 @@ func (d *DB) GetDB() *sql.DB {
|
||||
return d.db
|
||||
}
|
||||
|
||||
func (d *DB) GetCurrentDBSize(ctx context.Context) (int64, error) {
|
||||
query := "SELECT SUM(`data_length` + `index_length`) AS `size` " +
|
||||
" FROM information_schema.TABLES" +
|
||||
" WHERE `table_schema` = ?" +
|
||||
" GROUP BY `table_schema`"
|
||||
rows, err := d.db.QueryContext(ctx, query, d.config.DBName)
|
||||
if err != nil {
|
||||
slog.Error("Query db size error, make sure you have enough privilege", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var size int64
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&size); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if rows.Err() != nil {
|
||||
return 0, rows.Err()
|
||||
}
|
||||
|
||||
return size, nil
|
||||
func (d *DB) Type() string {
|
||||
return "mysql"
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
|
@ -1,170 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/usememos/memos/server/version"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
//go:embed migration
|
||||
var migrationFS embed.FS
|
||||
|
||||
const (
|
||||
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
||||
)
|
||||
|
||||
func (d *DB) Migrate(ctx context.Context) error {
|
||||
if d.profile.IsDev() {
|
||||
return d.nonProdMigrate(ctx)
|
||||
}
|
||||
|
||||
return d.prodMigrate(ctx)
|
||||
}
|
||||
|
||||
func (d *DB) nonProdMigrate(ctx context.Context) error {
|
||||
rows, err := d.db.QueryContext(ctx, "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema';")
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to query database tables: %s", err)
|
||||
}
|
||||
if rows.Err() != nil {
|
||||
return errors.Errorf("failed to query database tables: %s", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tables []string
|
||||
for rows.Next() {
|
||||
var table string
|
||||
err := rows.Scan(&table)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to scan table name: %s", err)
|
||||
}
|
||||
tables = append(tables, table)
|
||||
}
|
||||
|
||||
if len(tables) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||
}
|
||||
|
||||
stmt := string(buf)
|
||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) prodMigrate(ctx context.Context) error {
|
||||
currentVersion := version.GetCurrentVersion(d.profile.Mode)
|
||||
migrationHistoryList, err := d.FindMigrationHistoryList(ctx, &store.FindMigrationHistory{})
|
||||
// If there is no migration history, we should apply the latest schema.
|
||||
if err != nil || len(migrationHistoryList) == 0 {
|
||||
buf, err := migrationFS.ReadFile("migration/prod/" + latestSchemaFileName)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||
}
|
||||
|
||||
stmt := string(buf)
|
||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
||||
}
|
||||
if _, err := d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{
|
||||
Version: currentVersion,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to upsert migration history")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
migrationHistoryVersionList := []string{}
|
||||
for _, migrationHistory := range migrationHistoryList {
|
||||
migrationHistoryVersionList = append(migrationHistoryVersionList, migrationHistory.Version)
|
||||
}
|
||||
sort.Sort(version.SortVersion(migrationHistoryVersionList))
|
||||
latestMigrationHistoryVersion := migrationHistoryVersionList[len(migrationHistoryVersionList)-1]
|
||||
if !version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println("start migrate")
|
||||
for _, minorVersion := range getMinorVersionList() {
|
||||
normalizedVersion := minorVersion + ".0"
|
||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||
fmt.Println("applying migration for", normalizedVersion)
|
||||
if err := d.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||
return errors.Wrap(err, "failed to apply minor version migration")
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("end migrate")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("migration/prod/%s/*.sql", minorVersion))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read ddl files")
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
// Loop over all migration files and execute them in order.
|
||||
for _, filename := range filenames {
|
||||
buf, err := migrationFS.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
||||
}
|
||||
for _, stmt := range strings.Split(string(buf), ";") {
|
||||
if strings.TrimSpace(stmt) == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Wrapf(err, "migrate error: %s", stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert the newest version to migration_history.
|
||||
version := minorVersion + ".0"
|
||||
if _, err = d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{Version: version}); err != nil {
|
||||
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// minorDirRegexp is a regular expression for minor version directory.
|
||||
var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`)
|
||||
|
||||
func getMinorVersionList() []string {
|
||||
minorVersionList := []string{}
|
||||
|
||||
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
||||
minorVersionList = append(minorVersionList, file.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Sort(version.SortVersion(minorVersionList))
|
||||
|
||||
return minorVersionList
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
@ -44,8 +43,8 @@ func (d *DB) GetDB() *sql.DB {
|
||||
return d.db
|
||||
}
|
||||
|
||||
func (*DB) GetCurrentDBSize(context.Context) (int64, error) {
|
||||
return 0, errors.New("unimplemented")
|
||||
func (d *DB) Type() string {
|
||||
return "postgres"
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
|
@ -1,13 +1,9 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
// Import the SQLite driver.
|
||||
_ "modernc.org/sqlite"
|
||||
@ -58,13 +54,8 @@ func (d *DB) GetDB() *sql.DB {
|
||||
return d.db
|
||||
}
|
||||
|
||||
func (d *DB) GetCurrentDBSize(context.Context) (int64, error) {
|
||||
fi, err := os.Stat(d.profile.DSN)
|
||||
if err != nil {
|
||||
return 0, status.Errorf(codes.Internal, "failed to get file info: %v", err)
|
||||
}
|
||||
|
||||
return fi.Size(), nil
|
||||
func (d *DB) Type() string {
|
||||
return "sqlite"
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
|
@ -11,10 +11,9 @@ type Driver interface {
|
||||
GetDB() *sql.DB
|
||||
Close() error
|
||||
|
||||
Migrate(ctx context.Context) error
|
||||
|
||||
// current file is driver
|
||||
GetCurrentDBSize(ctx context.Context) (int64, error)
|
||||
// Type returns the type of the driver.
|
||||
// Supported types are: sqlite, mysql, postgres.
|
||||
Type() string
|
||||
|
||||
// MigrationHistory model related methods.
|
||||
FindMigrationHistoryList(ctx context.Context, find *FindMigrationHistory) ([]*MigrationHistory, error)
|
||||
|
146
store/migration/sqlite/dev/LATEST__SCHEMA.sql
Normal file
146
store/migration/sqlite/dev/LATEST__SCHEMA.sql
Normal file
@ -0,0 +1,146 @@
|
||||
-- migration_history
|
||||
CREATE TABLE migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
-- system_setting
|
||||
CREATE TABLE system_setting (
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
UNIQUE(name)
|
||||
);
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE INDEX idx_user_username ON user (username);
|
||||
|
||||
-- user_setting
|
||||
CREATE TABLE user_setting (
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
UNIQUE(user_id, key)
|
||||
);
|
||||
|
||||
-- memo
|
||||
CREATE TABLE memo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT NOT NULL UNIQUE,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_memo_creator_id ON memo (creator_id);
|
||||
CREATE INDEX idx_memo_content ON memo (content);
|
||||
CREATE INDEX idx_memo_visibility ON memo (visibility);
|
||||
CREATE INDEX idx_memo_tags ON memo (tags);
|
||||
|
||||
-- memo_organizer
|
||||
CREATE TABLE memo_organizer (
|
||||
memo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
UNIQUE(memo_id, user_id)
|
||||
);
|
||||
|
||||
-- memo_relation
|
||||
CREATE TABLE memo_relation (
|
||||
memo_id INTEGER NOT NULL,
|
||||
related_memo_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
UNIQUE(memo_id, related_memo_id, type)
|
||||
);
|
||||
|
||||
-- resource
|
||||
CREATE TABLE resource (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT NOT NULL UNIQUE,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
filename TEXT NOT NULL DEFAULT '',
|
||||
blob BLOB DEFAULT NULL,
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
memo_id INTEGER,
|
||||
storage_type TEXT NOT NULL DEFAULT '',
|
||||
reference TEXT NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resource_creator_id ON resource (creator_id);
|
||||
|
||||
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||
|
||||
-- activity
|
||||
CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- inbox
|
||||
CREATE TABLE inbox (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
sender_id INTEGER NOT NULL,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- webhook
|
||||
CREATE TABLE webhook (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
creator_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
||||
|
||||
-- reaction
|
||||
CREATE TABLE reaction (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
creator_id INTEGER NOT NULL,
|
||||
content_id TEXT NOT NULL,
|
||||
reaction_type TEXT NOT NULL,
|
||||
UNIQUE(creator_id, content_id, reaction_type)
|
||||
);
|
9
store/migration/sqlite/prod/0.10/00__activity.sql
Normal file
9
store/migration/sqlite/prod/0.10/00__activity.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- activity
|
||||
CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
4
store/migration/sqlite/prod/0.11/00__user_avatar.sql
Normal file
4
store/migration/sqlite/prod/0.11/00__user_avatar.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE
|
||||
user
|
||||
ADD
|
||||
COLUMN avatar_url TEXT NOT NULL DEFAULT '';
|
8
store/migration/sqlite/prod/0.11/01__idp.sql
Normal file
8
store/migration/sqlite/prod/0.11/01__idp.sql
Normal file
@ -0,0 +1,8 @@
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
7
store/migration/sqlite/prod/0.11/02__storage.sql
Normal file
7
store/migration/sqlite/prod/0.11/02__storage.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- storage
|
||||
CREATE TABLE storage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
6
store/migration/sqlite/prod/0.12/00__user_setting.sql
Normal file
6
store/migration/sqlite/prod/0.12/00__user_setting.sql
Normal file
@ -0,0 +1,6 @@
|
||||
UPDATE
|
||||
user_setting
|
||||
SET
|
||||
key = 'memo-visibility'
|
||||
WHERE
|
||||
key = 'memoVisibility';
|
69
store/migration/sqlite/prod/0.12/01__system_setting.sql
Normal file
69
store/migration/sqlite/prod/0.12/01__system_setting.sql
Normal file
@ -0,0 +1,69 @@
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'server-id'
|
||||
WHERE
|
||||
name = 'serverId';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'secret-session'
|
||||
WHERE
|
||||
name = 'secretSessionName';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'allow-signup'
|
||||
WHERE
|
||||
name = 'allowSignUp';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'disable-public-memos'
|
||||
WHERE
|
||||
name = 'disablePublicMemos';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'additional-style'
|
||||
WHERE
|
||||
name = 'additionalStyle';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'additional-script'
|
||||
WHERE
|
||||
name = 'additionalScript';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'customized-profile'
|
||||
WHERE
|
||||
name = 'customizedProfile';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'storage-service-id'
|
||||
WHERE
|
||||
name = 'storageServiceId';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'local-storage-path'
|
||||
WHERE
|
||||
name = 'localStoragePath';
|
||||
|
||||
UPDATE
|
||||
system_setting
|
||||
SET
|
||||
name = 'openai-config'
|
||||
WHERE
|
||||
name = 'openAIConfig';
|
@ -0,0 +1,4 @@
|
||||
ALTER TABLE
|
||||
resource
|
||||
ADD
|
||||
COLUMN internal_path TEXT NOT NULL DEFAULT '';
|
18
store/migration/sqlite/prod/0.12/04__resource_public_id.sql
Normal file
18
store/migration/sqlite/prod/0.12/04__resource_public_id.sql
Normal file
@ -0,0 +1,18 @@
|
||||
ALTER TABLE
|
||||
resource
|
||||
ADD
|
||||
COLUMN public_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
CREATE UNIQUE INDEX resource_id_public_id_unique_index ON resource (id, public_id);
|
||||
|
||||
UPDATE
|
||||
resource
|
||||
SET
|
||||
public_id = printf (
|
||||
'%s-%s-%s-%s-%s',
|
||||
lower(hex(randomblob(4))),
|
||||
lower(hex(randomblob(2))),
|
||||
lower(hex(randomblob(2))),
|
||||
lower(hex(randomblob(2))),
|
||||
lower(hex(randomblob(6)))
|
||||
);
|
7
store/migration/sqlite/prod/0.13/00__memo_relation.sql
Normal file
7
store/migration/sqlite/prod/0.13/00__memo_relation.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- memo_relation
|
||||
CREATE TABLE memo_relation (
|
||||
memo_id INTEGER NOT NULL,
|
||||
related_memo_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
UNIQUE(memo_id, related_memo_id, type)
|
||||
);
|
@ -0,0 +1,22 @@
|
||||
DROP TABLE IF EXISTS memo_organizer_temp;
|
||||
|
||||
CREATE TABLE memo_organizer_temp (
|
||||
memo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
UNIQUE(memo_id, user_id)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo_organizer_temp (memo_id, user_id, pinned)
|
||||
SELECT
|
||||
memo_id,
|
||||
user_id,
|
||||
pinned
|
||||
FROM
|
||||
memo_organizer;
|
||||
|
||||
DROP TABLE memo_organizer;
|
||||
|
||||
ALTER TABLE
|
||||
memo_organizer_temp RENAME TO memo_organizer;
|
@ -0,0 +1,25 @@
|
||||
DROP TABLE IF EXISTS resource_temp;
|
||||
|
||||
CREATE TABLE resource_temp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
filename TEXT NOT NULL DEFAULT '',
|
||||
blob BLOB DEFAULT NULL,
|
||||
external_link TEXT NOT NULL DEFAULT '',
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
internal_path TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
resource_temp (id, creator_id, created_ts, updated_ts, filename, blob, external_link, type, size, internal_path)
|
||||
SELECT
|
||||
id, creator_id, created_ts, updated_ts, filename, blob, external_link, type, size, internal_path
|
||||
FROM
|
||||
resource;
|
||||
|
||||
DROP TABLE resource;
|
||||
|
||||
ALTER TABLE resource_temp RENAME TO resource;
|
5
store/migration/sqlite/prod/0.14/01__create_indexes.sql
Normal file
5
store/migration/sqlite/prod/0.14/01__create_indexes.sql
Normal file
@ -0,0 +1,5 @@
|
||||
CREATE INDEX IF NOT EXISTS idx_user_username ON user (username);
|
||||
CREATE INDEX IF NOT EXISTS idx_memo_creator_id ON memo (creator_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_memo_content ON memo (content);
|
||||
CREATE INDEX IF NOT EXISTS idx_memo_visibility ON memo (visibility);
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_creator_id ON resource (creator_id);
|
25
store/migration/sqlite/prod/0.15/00__drop_user_open_id.sql
Normal file
25
store/migration/sqlite/prod/0.15/00__drop_user_open_id.sql
Normal file
@ -0,0 +1,25 @@
|
||||
DROP TABLE IF EXISTS user_temp;
|
||||
|
||||
CREATE TABLE user_temp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user_temp (id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url)
|
||||
SELECT
|
||||
id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url
|
||||
FROM
|
||||
user;
|
||||
|
||||
DROP TABLE user;
|
||||
|
||||
ALTER TABLE user_temp RENAME TO user;
|
@ -0,0 +1,13 @@
|
||||
ALTER TABLE resource ADD COLUMN memo_id INTEGER;
|
||||
|
||||
UPDATE resource
|
||||
SET memo_id = (
|
||||
SELECT memo_id
|
||||
FROM memo_resource
|
||||
WHERE resource.id = memo_resource.resource_id
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||
|
||||
DROP TABLE IF EXISTS memo_resource;
|
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS shortcut;
|
9
store/migration/sqlite/prod/0.17/00__inbox.sql
Normal file
9
store/migration/sqlite/prod/0.17/00__inbox.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- inbox
|
||||
CREATE TABLE inbox (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
sender_id INTEGER NOT NULL,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
@ -0,0 +1 @@
|
||||
DELETE FROM activity;
|
12
store/migration/sqlite/prod/0.18/00__webhook.sql
Normal file
12
store/migration/sqlite/prod/0.18/00__webhook.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- webhook
|
||||
CREATE TABLE webhook (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
creator_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
4
store/migration/sqlite/prod/0.18/01__user_setting.sql
Normal file
4
store/migration/sqlite/prod/0.18/01__user_setting.sql
Normal file
@ -0,0 +1,4 @@
|
||||
UPDATE user_setting SET key = 'USER_SETTING_LOCALE', value = REPLACE(value, '"', '') WHERE key = 'locale';
|
||||
UPDATE user_setting SET key = 'USER_SETTING_APPEARANCE', value = REPLACE(value, '"', '') WHERE key = 'appearance';
|
||||
UPDATE user_setting SET key = 'USER_SETTING_MEMO_VISIBILITY', value = REPLACE(value, '"', '') WHERE key = 'memo-visibility';
|
||||
UPDATE user_setting SET key = 'USER_SETTING_TELEGRAM_USER_ID', value = REPLACE(value, '"', '') WHERE key = 'telegram-user-id';
|
11
store/migration/sqlite/prod/0.19/00__add_resource_name.sql
Normal file
11
store/migration/sqlite/prod/0.19/00__add_resource_name.sql
Normal file
@ -0,0 +1,11 @@
|
||||
ALTER TABLE memo ADD COLUMN resource_name TEXT NOT NULL DEFAULT "";
|
||||
|
||||
UPDATE memo SET resource_name = lower(hex(randomblob(8)));
|
||||
|
||||
CREATE UNIQUE INDEX idx_memo_resource_name ON memo (resource_name);
|
||||
|
||||
ALTER TABLE resource ADD COLUMN resource_name TEXT NOT NULL DEFAULT "";
|
||||
|
||||
UPDATE resource SET resource_name = lower(hex(randomblob(8)));
|
||||
|
||||
CREATE UNIQUE INDEX idx_resource_resource_name ON resource (resource_name);
|
60
store/migration/sqlite/prod/0.2/00__user_role.sql
Normal file
60
store/migration/sqlite/prod/0.2/00__user_role.sql
Normal file
@ -0,0 +1,60 @@
|
||||
-- change user role field from "OWNER"/"USER" to "HOST"/"USER".
|
||||
PRAGMA foreign_keys = off;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
ALTER TABLE
|
||||
user RENAME TO _user_old;
|
||||
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user (
|
||||
id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
email,
|
||||
name,
|
||||
password_hash,
|
||||
open_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
email,
|
||||
name,
|
||||
password_hash,
|
||||
open_id
|
||||
FROM
|
||||
_user_old;
|
||||
|
||||
UPDATE
|
||||
user
|
||||
SET
|
||||
role = 'HOST'
|
||||
WHERE
|
||||
id IN (
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
_user_old
|
||||
WHERE
|
||||
role = 'OWNER'
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
PRAGMA foreign_keys = on;
|
4
store/migration/sqlite/prod/0.2/01__memo_visibility.sql
Normal file
4
store/migration/sqlite/prod/0.2/01__memo_visibility.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE
|
||||
memo
|
||||
ADD
|
||||
COLUMN visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PRIVATE')) DEFAULT 'PRIVATE';
|
9
store/migration/sqlite/prod/0.20/00__reaction.sql
Normal file
9
store/migration/sqlite/prod/0.20/00__reaction.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- reaction
|
||||
CREATE TABLE reaction (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
creator_id INTEGER NOT NULL,
|
||||
content_id TEXT NOT NULL,
|
||||
reaction_type TEXT NOT NULL,
|
||||
UNIQUE(creator_id, content_id, reaction_type)
|
||||
);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE user ADD COLUMN description TEXT NOT NULL DEFAULT "";
|
3
store/migration/sqlite/prod/0.21/01__rename_uid.sql
Normal file
3
store/migration/sqlite/prod/0.21/01__rename_uid.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE memo RENAME COLUMN resource_name TO uid;
|
||||
|
||||
ALTER TABLE resource RENAME COLUMN resource_name TO uid;
|
@ -0,0 +1,17 @@
|
||||
ALTER TABLE resource ADD COLUMN storage_type TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE resource ADD COLUMN reference TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE resource ADD COLUMN payload TEXT NOT NULL DEFAULT '{}';
|
||||
|
||||
UPDATE resource
|
||||
SET storage_type = 'LOCAL', reference = internal_path
|
||||
WHERE internal_path IS NOT NULL AND internal_path != '';
|
||||
|
||||
UPDATE resource
|
||||
SET storage_type = 'EXTERNAL', reference = external_link
|
||||
WHERE external_link IS NOT NULL AND external_link != '';
|
||||
|
||||
ALTER TABLE resource DROP COLUMN internal_path;
|
||||
|
||||
ALTER TABLE resource DROP COLUMN external_link;
|
3
store/migration/sqlite/prod/0.22/01__memo_tags.sql
Normal file
3
store/migration/sqlite/prod/0.22/01__memo_tags.sql
Normal file
@ -0,0 +1,3 @@
|
||||
ALTER TABLE memo ADD COLUMN tags TEXT NOT NULL DEFAULT '[]';
|
||||
|
||||
CREATE INDEX idx_memo_tags ON memo (tags);
|
1
store/migration/sqlite/prod/0.22/02__memo_payload.sql
Normal file
1
store/migration/sqlite/prod/0.22/02__memo_payload.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE memo ADD COLUMN payload TEXT NOT NULL DEFAULT '{}';
|
1
store/migration/sqlite/prod/0.22/03__drop_tag.sql
Normal file
1
store/migration/sqlite/prod/0.22/03__drop_tag.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE tag;
|
@ -0,0 +1,43 @@
|
||||
-- change memo visibility field from "PRIVATE"/"PUBLIC" to "PRIVATE"/"PROTECTED"/"PUBLIC".
|
||||
PRAGMA foreign_keys = off;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo RENAME TO _memo_old;
|
||||
|
||||
CREATE TABLE memo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo (
|
||||
id,
|
||||
creator_id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
content,
|
||||
visibility
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
creator_id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
content,
|
||||
visibility
|
||||
FROM
|
||||
_memo_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
PRAGMA foreign_keys = on;
|
9
store/migration/sqlite/prod/0.4/00__user_setting.sql
Normal file
9
store/migration/sqlite/prod/0.4/00__user_setting.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- user_setting
|
||||
CREATE TABLE user_setting (
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX user_setting_key_user_id_index ON user_setting(key, user_id);
|
217
store/migration/sqlite/prod/0.5/00__regenerate_foreign_keys.sql
Normal file
217
store/migration/sqlite/prod/0.5/00__regenerate_foreign_keys.sql
Normal file
@ -0,0 +1,217 @@
|
||||
PRAGMA foreign_keys = off;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
ALTER TABLE
|
||||
user RENAME TO _user_old;
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_user_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_user_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `user` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`user`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo RENAME TO _memo_old;
|
||||
|
||||
-- memo
|
||||
CREATE TABLE memo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_memo_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_memo_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `memo` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`memo`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo_organizer RENAME TO _memo_organizer_old;
|
||||
|
||||
-- memo_organizer
|
||||
CREATE TABLE memo_organizer (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
memo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
FOREIGN KEY(memo_id) REFERENCES memo(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
UNIQUE(memo_id, user_id)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo_organizer
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_memo_organizer_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||
|
||||
DROP TABLE IF EXISTS _shortcut_old;
|
||||
|
||||
ALTER TABLE
|
||||
shortcut RENAME TO _shortcut_old;
|
||||
|
||||
-- shortcut
|
||||
CREATE TABLE shortcut (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL DEFAULT '{}',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
shortcut
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_shortcut_old;
|
||||
|
||||
DROP TABLE IF EXISTS _shortcut_old;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_shortcut_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `shortcut` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`shortcut`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TABLE IF EXISTS _resource_old;
|
||||
|
||||
ALTER TABLE
|
||||
resource RENAME TO _resource_old;
|
||||
|
||||
-- resource
|
||||
CREATE TABLE resource (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
filename TEXT NOT NULL DEFAULT '',
|
||||
blob BLOB DEFAULT NULL,
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
resource
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_resource_old;
|
||||
|
||||
DROP TABLE IF EXISTS _resource_old;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_resource_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `resource` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`resource`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TABLE IF EXISTS _user_setting_old;
|
||||
|
||||
ALTER TABLE
|
||||
user_setting RENAME TO _user_setting_old;
|
||||
|
||||
-- user_setting
|
||||
CREATE TABLE user_setting (
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
UNIQUE(user_id, key)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user_setting
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_user_setting_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_setting_old;
|
||||
|
||||
PRAGMA foreign_keys = on;
|
10
store/migration/sqlite/prod/0.5/01__memo_resource.sql
Normal file
10
store/migration/sqlite/prod/0.5/01__memo_resource.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- memo_resource
|
||||
CREATE TABLE memo_resource (
|
||||
memo_id INTEGER NOT NULL,
|
||||
resource_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY(memo_id) REFERENCES memo(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(resource_id) REFERENCES resource(id) ON DELETE CASCADE,
|
||||
UNIQUE(memo_id, resource_id)
|
||||
);
|
7
store/migration/sqlite/prod/0.5/02__system_setting.sql
Normal file
7
store/migration/sqlite/prod/0.5/02__system_setting.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- system_setting
|
||||
CREATE TABLE system_setting (
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
UNIQUE(name)
|
||||
);
|
@ -0,0 +1,4 @@
|
||||
ALTER TABLE
|
||||
resource
|
||||
ADD
|
||||
COLUMN external_link TEXT NOT NULL DEFAULT '';
|
59
store/migration/sqlite/prod/0.6/00__recreate_triggers.sql
Normal file
59
store/migration/sqlite/prod/0.6/00__recreate_triggers.sql
Normal file
@ -0,0 +1,59 @@
|
||||
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_user_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `user` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`user`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_memo_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `memo` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`memo`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_shortcut_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `shortcut` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`shortcut`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_resource_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `resource` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`resource`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
|
||||
END;
|
191
store/migration/sqlite/prod/0.7/00__remove_fk.sql
Normal file
191
store/migration/sqlite/prod/0.7/00__remove_fk.sql
Normal file
@ -0,0 +1,191 @@
|
||||
PRAGMA foreign_keys = off;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
ALTER TABLE
|
||||
user RENAME TO _user_old;
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_user_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo RENAME TO _memo_old;
|
||||
|
||||
-- memo
|
||||
CREATE TABLE memo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_memo_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo_organizer RENAME TO _memo_organizer_old;
|
||||
|
||||
-- memo_organizer
|
||||
CREATE TABLE memo_organizer (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
memo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
UNIQUE(memo_id, user_id)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo_organizer
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_memo_organizer_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||
|
||||
DROP TABLE IF EXISTS _shortcut_old;
|
||||
|
||||
ALTER TABLE
|
||||
shortcut RENAME TO _shortcut_old;
|
||||
|
||||
-- shortcut
|
||||
CREATE TABLE shortcut (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
shortcut
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_shortcut_old;
|
||||
|
||||
DROP TABLE IF EXISTS _shortcut_old;
|
||||
|
||||
DROP TABLE IF EXISTS _resource_old;
|
||||
|
||||
ALTER TABLE
|
||||
resource RENAME TO _resource_old;
|
||||
|
||||
-- resource
|
||||
CREATE TABLE resource (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
filename TEXT NOT NULL DEFAULT '',
|
||||
blob BLOB DEFAULT NULL,
|
||||
external_link TEXT NOT NULL DEFAULT '',
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
size INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
resource (
|
||||
id,
|
||||
creator_id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
filename,
|
||||
blob,
|
||||
external_link,
|
||||
type,
|
||||
size
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
creator_id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
filename,
|
||||
blob,
|
||||
external_link,
|
||||
type,
|
||||
size
|
||||
FROM
|
||||
_resource_old;
|
||||
|
||||
DROP TABLE IF EXISTS _resource_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_setting_old;
|
||||
|
||||
ALTER TABLE
|
||||
user_setting RENAME TO _user_setting_old;
|
||||
|
||||
-- user_setting
|
||||
CREATE TABLE user_setting (
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
UNIQUE(user_id, key)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user_setting
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_user_setting_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_setting_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_resource_old;
|
||||
|
||||
ALTER TABLE
|
||||
memo_resource RENAME TO _memo_resource_old;
|
||||
|
||||
-- memo_resource
|
||||
CREATE TABLE memo_resource (
|
||||
memo_id INTEGER NOT NULL,
|
||||
resource_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
UNIQUE(memo_id, resource_id)
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo_resource
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
_memo_resource_old;
|
||||
|
||||
DROP TABLE IF EXISTS _memo_resource_old;
|
7
store/migration/sqlite/prod/0.7/01__remove_triggers.sql
Normal file
7
store/migration/sqlite/prod/0.7/01__remove_triggers.sql
Normal file
@ -0,0 +1,7 @@
|
||||
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||
|
||||
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
@ -0,0 +1,5 @@
|
||||
-- migration_history
|
||||
CREATE TABLE IF NOT EXISTS migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
50
store/migration/sqlite/prod/0.8/01__user_username.sql
Normal file
50
store/migration/sqlite/prod/0.8/01__user_username.sql
Normal file
@ -0,0 +1,50 @@
|
||||
-- add column username TEXT NOT NULL UNIQUE
|
||||
-- rename column name to nickname
|
||||
-- add role `ADMIN`
|
||||
DROP TABLE IF EXISTS _user_old;
|
||||
|
||||
ALTER TABLE
|
||||
user RENAME TO _user_old;
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user (
|
||||
id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
username,
|
||||
role,
|
||||
email,
|
||||
nickname,
|
||||
password_hash,
|
||||
open_id
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
created_ts,
|
||||
updated_ts,
|
||||
row_status,
|
||||
email,
|
||||
role,
|
||||
email,
|
||||
name,
|
||||
password_hash,
|
||||
open_id
|
||||
FROM
|
||||
_user_old;
|
||||
|
||||
DROP TABLE IF EXISTS _user_old;
|
6
store/migration/sqlite/prod/0.9/00__tag.sql
Normal file
6
store/migration/sqlite/prod/0.9/00__tag.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- tag
|
||||
CREATE TABLE tag (
|
||||
name TEXT NOT NULL,
|
||||
creator_id INTEGER NOT NULL,
|
||||
UNIQUE(name, creator_id)
|
||||
);
|
146
store/migration/sqlite/prod/LATEST__SCHEMA.sql
Normal file
146
store/migration/sqlite/prod/LATEST__SCHEMA.sql
Normal file
@ -0,0 +1,146 @@
|
||||
-- migration_history
|
||||
CREATE TABLE migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
-- system_setting
|
||||
CREATE TABLE system_setting (
|
||||
name TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
UNIQUE(name)
|
||||
);
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||
email TEXT NOT NULL DEFAULT '',
|
||||
nickname TEXT NOT NULL DEFAULT '',
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_url TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE INDEX idx_user_username ON user (username);
|
||||
|
||||
-- user_setting
|
||||
CREATE TABLE user_setting (
|
||||
user_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
UNIQUE(user_id, key)
|
||||
);
|
||||
|
||||
-- memo
|
||||
CREATE TABLE memo (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT NOT NULL UNIQUE,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||
tags TEXT NOT NULL DEFAULT '[]',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_memo_creator_id ON memo (creator_id);
|
||||
CREATE INDEX idx_memo_content ON memo (content);
|
||||
CREATE INDEX idx_memo_visibility ON memo (visibility);
|
||||
CREATE INDEX idx_memo_tags ON memo (tags);
|
||||
|
||||
-- memo_organizer
|
||||
CREATE TABLE memo_organizer (
|
||||
memo_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
UNIQUE(memo_id, user_id)
|
||||
);
|
||||
|
||||
-- memo_relation
|
||||
CREATE TABLE memo_relation (
|
||||
memo_id INTEGER NOT NULL,
|
||||
related_memo_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
UNIQUE(memo_id, related_memo_id, type)
|
||||
);
|
||||
|
||||
-- resource
|
||||
CREATE TABLE resource (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
uid TEXT NOT NULL UNIQUE,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
filename TEXT NOT NULL DEFAULT '',
|
||||
blob BLOB DEFAULT NULL,
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
size INTEGER NOT NULL DEFAULT 0,
|
||||
memo_id INTEGER,
|
||||
storage_type TEXT NOT NULL DEFAULT '',
|
||||
reference TEXT NOT NULL DEFAULT '',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
CREATE INDEX idx_resource_creator_id ON resource (creator_id);
|
||||
|
||||
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||
|
||||
-- activity
|
||||
CREATE TABLE activity (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
creator_id INTEGER NOT NULL,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
type TEXT NOT NULL DEFAULT '',
|
||||
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||
payload TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- idp
|
||||
CREATE TABLE idp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||
config TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- inbox
|
||||
CREATE TABLE inbox (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
sender_id INTEGER NOT NULL,
|
||||
receiver_id INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
message TEXT NOT NULL DEFAULT '{}'
|
||||
);
|
||||
|
||||
-- webhook
|
||||
CREATE TABLE webhook (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||
creator_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
||||
|
||||
-- reaction
|
||||
CREATE TABLE reaction (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
creator_id INTEGER NOT NULL,
|
||||
content_id TEXT NOT NULL,
|
||||
reaction_type TEXT NOT NULL,
|
||||
UNIQUE(creator_id, content_id, reaction_type)
|
||||
);
|
@ -1 +1,198 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"regexp"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/usememos/memos/server/version"
|
||||
)
|
||||
|
||||
//go:embed migration
|
||||
var migrationFS embed.FS
|
||||
|
||||
//go:embed seed
|
||||
var seedFS embed.FS
|
||||
|
||||
// Migrate applies the latest schema to the database.
|
||||
func (s *Store) Migrate(ctx context.Context) error {
|
||||
if err := s.preMigrate(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to pre-migrate")
|
||||
}
|
||||
|
||||
if s.Profile.Mode == "prod" {
|
||||
migrationHistoryList, err := s.driver.FindMigrationHistoryList(ctx, &FindMigrationHistory{})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to find migration history")
|
||||
}
|
||||
if len(migrationHistoryList) == 0 {
|
||||
return errors.Errorf("no migration history found")
|
||||
}
|
||||
|
||||
migrationHistoryVersions := []string{}
|
||||
for _, migrationHistory := range migrationHistoryList {
|
||||
migrationHistoryVersions = append(migrationHistoryVersions, migrationHistory.Version)
|
||||
}
|
||||
sort.Sort(version.SortVersion(migrationHistoryVersions))
|
||||
latestMigrationHistoryVersion := migrationHistoryVersions[len(migrationHistoryVersions)-1]
|
||||
currentVersion := version.GetCurrentVersion(s.Profile.Mode)
|
||||
|
||||
if version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
||||
minorVersionList := s.getMinorVersionList()
|
||||
fmt.Println("start migration")
|
||||
for _, minorVersion := range minorVersionList {
|
||||
normalizedVersion := minorVersion + ".0"
|
||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||
fmt.Println("applying migration for", normalizedVersion)
|
||||
if err := s.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||
return errors.Wrap(err, "failed to apply minor version migration")
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("end migrate")
|
||||
}
|
||||
} else {
|
||||
// In demo mode, we should seed the database.
|
||||
if s.Profile.Mode == "demo" {
|
||||
if err := s.seed(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to seed")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) preMigrate(ctx context.Context) error {
|
||||
migrationHistoryList, err := s.driver.FindMigrationHistoryList(ctx, &FindMigrationHistory{})
|
||||
// If there is no migration history, we should apply the latest schema.
|
||||
if err != nil || len(migrationHistoryList) == 0 {
|
||||
if err != nil {
|
||||
slog.Error("failed to find migration history", "error", err)
|
||||
}
|
||||
fileName := s.getMigrationBasePath() + latestSchemaFileName
|
||||
bytes, err := migrationFS.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||
}
|
||||
if err := s.execute(ctx, string(bytes)); err != nil {
|
||||
return errors.Errorf("failed to exec SQL file %s, err %s", fileName, err)
|
||||
}
|
||||
if _, err := s.driver.UpsertMigrationHistory(ctx, &UpsertMigrationHistory{
|
||||
Version: version.GetCurrentVersion(s.Profile.Mode),
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to upsert migration history")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) getMigrationBasePath() string {
|
||||
mode := "dev"
|
||||
if s.Profile.Mode == "prod" {
|
||||
mode = "prod"
|
||||
}
|
||||
return fmt.Sprintf("migration/%s/%s/", s.driver.Type(), mode)
|
||||
}
|
||||
|
||||
func (s *Store) getSeedBasePath() string {
|
||||
return fmt.Sprintf("seed/%s/", s.driver.Type())
|
||||
}
|
||||
|
||||
const (
|
||||
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
||||
)
|
||||
|
||||
func (s *Store) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("%s%s/*.sql", s.getMigrationBasePath(), minorVersion))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read migration files")
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
migrationStmt := ""
|
||||
// Loop over all migration files and execute them in order.
|
||||
for _, filename := range filenames {
|
||||
buf, err := migrationFS.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
||||
}
|
||||
stmt := string(buf)
|
||||
migrationStmt += stmt
|
||||
if err := s.execute(ctx, stmt); err != nil {
|
||||
return errors.Wrapf(err, "migrate error: %s", stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Upsert the newest version to migration_history.
|
||||
version := minorVersion + ".0"
|
||||
if _, err = s.driver.UpsertMigrationHistory(ctx, &UpsertMigrationHistory{
|
||||
Version: version,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) seed(ctx context.Context) error {
|
||||
// Only seed for SQLite.
|
||||
if s.driver.Type() != "sqlite" {
|
||||
return nil
|
||||
}
|
||||
|
||||
filenames, err := fs.Glob(seedFS, fmt.Sprintf("%s*.sql", s.getSeedBasePath()))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read seed files")
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
// Loop over all seed files and execute them in order.
|
||||
for _, filename := range filenames {
|
||||
bytes, err := seedFS.ReadFile(filename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to read seed file, filename=%s", filename)
|
||||
}
|
||||
if err := s.execute(ctx, string(bytes)); err != nil {
|
||||
return errors.Wrapf(err, "seed error: %s", filename)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// execute runs a single SQL statement within a transaction.
|
||||
func (s *Store) execute(ctx context.Context, stmt string) error {
|
||||
tx, err := s.driver.GetDB().Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
||||
return errors.Wrap(err, "failed to execute statement")
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *Store) getMinorVersionList() []string {
|
||||
var minorDirRegexp = regexp.MustCompile(fmt.Sprintf(`^%s[0-9]+\.[0-9]+$`, s.getMigrationBasePath()))
|
||||
minorVersionList := []string{}
|
||||
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
||||
minorVersionList = append(minorVersionList, file.Name())
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Sort(version.SortVersion(minorVersionList))
|
||||
return minorVersionList
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
|
||||
}
|
||||
return nil
|
||||
}(); err != nil {
|
||||
slog.Warn("Failed to delete s3 object", err)
|
||||
slog.Warn("Failed to delete s3 object", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
|
||||
|
12
store/seed/sqlite/00__reset.sql
Normal file
12
store/seed/sqlite/00__reset.sql
Normal file
@ -0,0 +1,12 @@
|
||||
DELETE FROM system_setting;
|
||||
DELETE FROM user;
|
||||
DELETE FROM user_setting;
|
||||
DELETE FROM memo;
|
||||
DELETE FROM memo_organizer;
|
||||
DELETE FROM memo_relation;
|
||||
DELETE FROM resource;
|
||||
DELETE FROM activity;
|
||||
DELETE FROM idp;
|
||||
DELETE FROM inbox;
|
||||
DELETE FROM webhook;
|
||||
DELETE FROM reaction;
|
10
store/seed/sqlite/01__dump.sql
Normal file
10
store/seed/sqlite/01__dump.sql
Normal file
File diff suppressed because one or more lines are too long
@ -1,7 +1,6 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/usememos/memos/server/profile"
|
||||
@ -25,14 +24,6 @@ func New(driver Driver, profile *profile.Profile) *Store {
|
||||
}
|
||||
}
|
||||
|
||||
func (*Store) MigrateManually(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
return s.driver.Close()
|
||||
}
|
||||
|
||||
func (s *Store) GetCurrentDBSize(ctx context.Context) (int64, error) {
|
||||
return s.driver.GetCurrentDBSize(ctx)
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ func NewTestingStore(ctx context.Context, t *testing.T) *store.Store {
|
||||
fmt.Printf("failed to create db driver, error: %+v\n", err)
|
||||
}
|
||||
resetTestingDB(ctx, profile, dbDriver)
|
||||
if err := dbDriver.Migrate(ctx); err != nil {
|
||||
fmt.Printf("failed to migrate db, error: %+v\n", err)
|
||||
}
|
||||
|
||||
store := store.New(dbDriver, profile)
|
||||
if err := store.Migrate(ctx); err != nil {
|
||||
fmt.Printf("failed to migrate db, error: %+v\n", err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user