252 lines
7.3 KiB
Bash
Executable File
252 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Merge one player's rows from a source SQLite DB into a target SQLite DB.
|
|
#
|
|
# Usage (sourced or executed):
|
|
# ./scripts/merge-player-db.sh <source.db> <target.db> <username>
|
|
#
|
|
# Replaces any existing target user with the same username.
|
|
# Copies (when present in both DBs): User, InventoryItem, Transaction,
|
|
# PaymentOrder, CaseKeyProgress, CatalogEntry, PrestigeLog, UserAchievement.
|
|
# Skips multiplayer state (Jackpot*/Duel*).
|
|
# Tolerates older backups missing newer tables/columns (e.g. PaymentOrder).
|
|
#
|
|
# Requires matching Item / Case / Achievement ids in both DBs (same catalog lineage).
|
|
# Requires sqlite3.
|
|
set -euo pipefail
|
|
|
|
SRC_DB="${1:-}"
|
|
DST_DB="${2:-}"
|
|
USERNAME="${3:-}"
|
|
|
|
if [[ -z "$SRC_DB" || -z "$DST_DB" || -z "$USERNAME" ]]; then
|
|
echo "Usage: $0 <source.db> <target.db> <username>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$SRC_DB" ]]; then
|
|
echo "Error: source DB not found: $SRC_DB" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$DST_DB" ]]; then
|
|
echo "Error: target DB not found: $DST_DB" >&2
|
|
echo "Tip: run a full import once first, or start the app so the DB exists." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v sqlite3 >/dev/null 2>&1; then
|
|
echo "Error: sqlite3 is required for player-only import" >&2
|
|
exit 1
|
|
fi
|
|
|
|
sql_quote() {
|
|
local s="$1"
|
|
s="${s//\'/\'\'}"
|
|
printf "'%s'" "$s"
|
|
}
|
|
|
|
table_exists() {
|
|
local db="$1"
|
|
local table="$2"
|
|
local n
|
|
n="$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=$(sql_quote "$table");")"
|
|
[[ "$n" -gt 0 ]]
|
|
}
|
|
|
|
# Column names for a table (space-separated), excluding names passed after $2
|
|
table_cols() {
|
|
local db="$1"
|
|
local table="$2"
|
|
shift 2
|
|
local -A skip=()
|
|
local e col
|
|
for e in "$@"; do
|
|
skip["$e"]=1
|
|
done
|
|
while IFS='|' read -r _ col _; do
|
|
[[ -n "$col" && -z "${skip[$col]:-}" ]] && printf '%s\n' "$col"
|
|
done < <(sqlite3 -separator '|' "$db" "PRAGMA table_info($(sql_quote "$table"));")
|
|
}
|
|
|
|
# Intersection of columns present in BOTH DBs, preserving destination order.
|
|
common_cols() {
|
|
local table="$1"
|
|
shift
|
|
local -A src_set=()
|
|
local col
|
|
while IFS= read -r col; do
|
|
[[ -n "$col" ]] && src_set["$col"]=1
|
|
done < <(table_cols "$SRC_DB" "$table" "$@")
|
|
|
|
local -a out=()
|
|
while IFS= read -r col; do
|
|
[[ -n "$col" && -n "${src_set[$col]:-}" ]] && out+=("$col")
|
|
done < <(table_cols "$DST_DB" "$table" "$@")
|
|
|
|
local IFS=', '
|
|
echo "${out[*]}"
|
|
}
|
|
|
|
count_rows() {
|
|
local table="$1"
|
|
local qtable="$1"
|
|
[[ "$table" == "Transaction" ]] && qtable='"Transaction"'
|
|
if table_exists "$SRC_DB" "$table"; then
|
|
sqlite3 "$SRC_DB" "SELECT COUNT(*) FROM $qtable WHERE userId = $SRC_UID;"
|
|
else
|
|
echo "skip"
|
|
fi
|
|
}
|
|
|
|
SRC_UID="$(sqlite3 "$SRC_DB" "SELECT id FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;")"
|
|
if [[ -z "$SRC_UID" ]]; then
|
|
echo "Error: no user with username '$USERNAME' in source DB" >&2
|
|
exit 2
|
|
fi
|
|
|
|
echo "==> Merging player '$USERNAME' (source id=$SRC_UID)"
|
|
echo " source: $SRC_DB"
|
|
echo " target: $DST_DB"
|
|
|
|
N_INV="$(count_rows InventoryItem)"
|
|
N_TX="$(count_rows Transaction)"
|
|
N_PAY="$(count_rows PaymentOrder)"
|
|
N_KEY="$(count_rows CaseKeyProgress)"
|
|
N_CAT="$(count_rows CatalogEntry)"
|
|
N_PRE="$(count_rows PrestigeLog)"
|
|
N_ACH="$(count_rows UserAchievement)"
|
|
echo " rows: inventory=$N_INV tx=$N_TX payments=$N_PAY keys=$N_KEY catalog=$N_CAT prestige=$N_PRE achievements=$N_ACH"
|
|
|
|
USER_COLS="$(common_cols User id)"
|
|
if [[ -z "$USER_COLS" ]]; then
|
|
echo "Error: no common User columns between source and target" >&2
|
|
exit 1
|
|
fi
|
|
|
|
INV_COLS="$(common_cols InventoryItem id userId)"
|
|
TX_COLS="$(common_cols Transaction id userId)"
|
|
if [[ -z "$INV_COLS" || -z "$TX_COLS" ]]; then
|
|
echo "Error: InventoryItem / Transaction schema mismatch" >&2
|
|
exit 1
|
|
fi
|
|
|
|
PAY_COLS=""
|
|
if table_exists "$SRC_DB" PaymentOrder && table_exists "$DST_DB" PaymentOrder; then
|
|
PAY_COLS="$(common_cols PaymentOrder userId)"
|
|
fi
|
|
KEY_COLS=""
|
|
table_exists "$SRC_DB" CaseKeyProgress && table_exists "$DST_DB" CaseKeyProgress && KEY_COLS="$(common_cols CaseKeyProgress id userId)"
|
|
CAT_COLS=""
|
|
table_exists "$SRC_DB" CatalogEntry && table_exists "$DST_DB" CatalogEntry && CAT_COLS="$(common_cols CatalogEntry id userId)"
|
|
PRE_COLS=""
|
|
table_exists "$SRC_DB" PrestigeLog && table_exists "$DST_DB" PrestigeLog && PRE_COLS="$(common_cols PrestigeLog id userId)"
|
|
ACH_COLS=""
|
|
table_exists "$SRC_DB" UserAchievement && table_exists "$DST_DB" UserAchievement && ACH_COLS="$(common_cols UserAchievement id userId)"
|
|
|
|
SQL_EXTRA_DELETE=""
|
|
SQL_EXTRA_INSERT=""
|
|
|
|
if [[ -n "$PAY_COLS" ]]; then
|
|
SQL_EXTRA_DELETE+="
|
|
DELETE FROM PaymentOrder
|
|
WHERE id IN (SELECT id FROM src.PaymentOrder WHERE userId = $SRC_UID);
|
|
"
|
|
SQL_EXTRA_INSERT+="
|
|
INSERT INTO PaymentOrder ($PAY_COLS, userId)
|
|
SELECT $PAY_COLS, (SELECT newId FROM _merge_uid)
|
|
FROM src.PaymentOrder WHERE userId = $SRC_UID;
|
|
"
|
|
else
|
|
echo " note: PaymentOrder absent in backup — skipped (osu top-up orders)"
|
|
fi
|
|
|
|
if [[ -n "$KEY_COLS" ]]; then
|
|
SQL_EXTRA_INSERT+="
|
|
INSERT INTO CaseKeyProgress (userId, $KEY_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $KEY_COLS
|
|
FROM src.CaseKeyProgress WHERE userId = $SRC_UID;
|
|
"
|
|
fi
|
|
|
|
if [[ -n "$CAT_COLS" ]]; then
|
|
SQL_EXTRA_INSERT+="
|
|
INSERT INTO CatalogEntry (userId, $CAT_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $CAT_COLS
|
|
FROM src.CatalogEntry WHERE userId = $SRC_UID;
|
|
"
|
|
fi
|
|
|
|
if [[ -n "$PRE_COLS" ]]; then
|
|
SQL_EXTRA_INSERT+="
|
|
INSERT INTO PrestigeLog (userId, $PRE_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $PRE_COLS
|
|
FROM src.PrestigeLog WHERE userId = $SRC_UID;
|
|
"
|
|
fi
|
|
|
|
if [[ -n "$ACH_COLS" ]]; then
|
|
SQL_EXTRA_INSERT+="
|
|
INSERT INTO UserAchievement (userId, $ACH_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $ACH_COLS
|
|
FROM src.UserAchievement WHERE userId = $SRC_UID;
|
|
"
|
|
fi
|
|
|
|
SQL_CLEANUP=""
|
|
table_exists "$DST_DB" JackpotRound && SQL_CLEANUP+="
|
|
UPDATE JackpotRound SET winnerUserId = NULL
|
|
WHERE winnerUserId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
|
|
"
|
|
table_exists "$DST_DB" DuelRoom && SQL_CLEANUP+="
|
|
UPDATE DuelRoom SET winnerUserId = NULL
|
|
WHERE winnerUserId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
|
|
DELETE FROM DuelRoom
|
|
WHERE creatorId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
|
|
"
|
|
table_exists "$DST_DB" JackpotDeposit && SQL_CLEANUP+="
|
|
DELETE FROM JackpotDeposit
|
|
WHERE userId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
|
|
"
|
|
table_exists "$DST_DB" DuelDeposit && SQL_CLEANUP+="
|
|
DELETE FROM DuelDeposit
|
|
WHERE userId IN (SELECT id FROM User WHERE username = $(sql_quote "$USERNAME"));
|
|
"
|
|
|
|
sqlite3 "$DST_DB" <<SQL
|
|
PRAGMA foreign_keys = ON;
|
|
ATTACH DATABASE $(sql_quote "$SRC_DB") AS src;
|
|
|
|
BEGIN;
|
|
|
|
$SQL_CLEANUP
|
|
DELETE FROM User WHERE username = $(sql_quote "$USERNAME");
|
|
|
|
$SQL_EXTRA_DELETE
|
|
|
|
INSERT INTO User ($USER_COLS)
|
|
SELECT $USER_COLS
|
|
FROM src.User WHERE id = $SRC_UID;
|
|
|
|
CREATE TEMP TABLE _merge_uid AS
|
|
SELECT id AS newId FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;
|
|
|
|
INSERT INTO InventoryItem (userId, $INV_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $INV_COLS
|
|
FROM src.InventoryItem WHERE userId = $SRC_UID;
|
|
|
|
INSERT INTO "Transaction" (userId, $TX_COLS)
|
|
SELECT (SELECT newId FROM _merge_uid), $TX_COLS
|
|
FROM src."Transaction" WHERE userId = $SRC_UID;
|
|
|
|
$SQL_EXTRA_INSERT
|
|
|
|
DROP TABLE _merge_uid;
|
|
|
|
COMMIT;
|
|
DETACH DATABASE src;
|
|
SQL
|
|
|
|
NEW_UID="$(sqlite3 "$DST_DB" "SELECT id FROM User WHERE username = $(sql_quote "$USERNAME") LIMIT 1;")"
|
|
echo "==> Player merged as target id=$NEW_UID"
|
|
echo " (jackpot/duel deposits & rooms not imported)"
|