feat: migrate from MySQL to Postgres with pgschema (#114)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
This commit is contained in:
Alec Thomas
2026-03-20 03:35:22 +11:00
committed by GitHub
parent 3eee3f738a
commit 6093fc6066
75 changed files with 1828 additions and 2542 deletions

View File

@@ -20,16 +20,16 @@ BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[dev-reset]${NC} $*"; }
success(){ echo -e "${GREEN}[dev-reset]${NC} ✅ $*"; }
warn() { echo -e "${YELLOW}[dev-reset]${NC} ⚠️ $*"; }
error() { echo -e "${RED}[dev-reset]${NC} ❌ $*" >&2; }
success(){ echo -e "${GREEN}[dev-reset]${NC} $*"; }
warn() { echo -e "${YELLOW}[dev-reset]${NC} $*"; }
error() { echo -e "${RED}[dev-reset]${NC} $*" >&2; }
cd "${REPO_ROOT}"
# ---- Confirm ----------------------------------------------------------------
if [[ "${1:-}" != "--yes" ]]; then
echo -e "${YELLOW}⚠️ WARNING: This will DELETE all local data (mysql, typesense volumes).${NC}"
echo -e "${YELLOW}WARNING: This will DELETE all local data (postgres, typesense volumes).${NC}"
echo -e " Redis data is ephemeral and always wiped on restart."
echo ""
read -r -p "Are you sure? [y/N] " confirm

View File

@@ -21,9 +21,9 @@ BLUE='\033[0;34m'
NC='\033[0m' # No Color
log() { echo -e "${BLUE}[dev-setup]${NC} $*"; }
success(){ echo -e "${GREEN}[dev-setup]${NC} ✅ $*"; }
warn() { echo -e "${YELLOW}[dev-setup]${NC} ⚠️ $*"; }
error() { echo -e "${RED}[dev-setup]${NC} ❌ $*" >&2; }
success(){ echo -e "${GREEN}[dev-setup]${NC} $*"; }
warn() { echo -e "${YELLOW}[dev-setup]${NC} $*"; }
error() { echo -e "${RED}[dev-setup]${NC} $*" >&2; }
# ---- Preflight checks -------------------------------------------------------
@@ -84,7 +84,7 @@ wait_healthy() {
}
echo ""
wait_healthy "MySQL" "sprout-mysql"
wait_healthy "Postgres" "sprout-postgres"
wait_healthy "Redis" "sprout-redis"
wait_healthy "Typesense" "sprout-typesense"
echo ""
@@ -93,31 +93,28 @@ echo ""
log "Running database migrations..."
MIGRATION_DIR="${REPO_ROOT}/migrations"
PGSCHEMA="${REPO_ROOT}/bin/pgschema"
SCHEMA_FILE="${REPO_ROOT}/schema/schema.sql"
if [[ ! -d "${MIGRATION_DIR}" ]]; then
warn "No migrations directory found at ${MIGRATION_DIR}. Skipping."
if [[ ! -f "${SCHEMA_FILE}" ]]; then
warn "No schema.sql found at ${SCHEMA_FILE}. Skipping."
else
# Check if sqlx CLI is available (preferred)
if command -v sqlx &>/dev/null; then
log "Using sqlx CLI for migrations..."
# MySQL may need a moment after healthcheck before accepting connections
if [[ -x "${PGSCHEMA}" ]]; then
log "Using pgschema for migrations..."
attempts=0
max_attempts=10
until DATABASE_URL="mysql://sprout:sprout_dev@localhost:3306/sprout" \
sqlx migrate run --source "${MIGRATION_DIR}" 2>/dev/null; do
until "${PGSCHEMA}" apply --file "${SCHEMA_FILE}" --auto-approve 2>/dev/null; do
attempts=$((attempts + 1))
if [[ ${attempts} -ge ${max_attempts} ]]; then
error "Failed to run migrations after ${max_attempts} attempts"
exit 1
fi
log "MySQL not ready for connections yet, retrying in 2s... (${attempts}/${max_attempts})"
log "Postgres not ready for connections yet, retrying in 2s... (${attempts}/${max_attempts})"
sleep 2
done
success "Migrations applied via sqlx"
success "Migrations applied via pgschema"
else
error "sqlx CLI not found. Install it with: cargo install sqlx-cli --no-default-features --features mysql"
error "Running migrations directly via mysql bypasses migration tracking and causes errors."
error "pgschema not found at ${PGSCHEMA}. Run: ./bin/hermit install pgschema"
exit 1
fi
fi
@@ -125,11 +122,11 @@ fi
# ---- Print connection info --------------------------------------------------
echo ""
echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Sprout dev environment is ready! 🌱${NC}"
echo -e "${GREEN}═══════════════════════════════════════════════════════${NC}"
echo -e "${GREEN}=======================================================${NC}"
echo -e "${GREEN} Sprout dev environment is ready!${NC}"
echo -e "${GREEN}=======================================================${NC}"
echo ""
echo -e " ${BLUE}MySQL${NC} mysql://sprout:sprout_dev@localhost:3306/sprout"
echo -e " ${BLUE}Postgres${NC} postgres://sprout:sprout_dev@localhost:5432/sprout"
echo -e " ${BLUE}Redis${NC} redis://localhost:6379"
echo -e " ${BLUE}Typesense${NC} http://localhost:8108 (key: sprout_dev_key)"
echo -e " ${BLUE}Adminer${NC} http://localhost:8082 (DB browser)"

View File

@@ -14,12 +14,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
MODE="${1:-all}"
TIMEOUT=60 # seconds to wait for services if starting them
DB_USER="${SPROUT_DB_USER:-sprout}"
DB_PASS="${SPROUT_DB_PASS:-sprout_dev}"
DB_NAME="${SPROUT_DB_NAME:-sprout}"
DOCKER_DB_HOST="${SPROUT_DOCKER_DB_HOST:-mysql}"
DOCKER_NETWORK="${SPROUT_DOCKER_NETWORK:-sprout-net}"
MYSQL_CLIENT_IMAGE="${SPROUT_DB_CLIENT_IMAGE:-mysql:8.0}"
# Colors
RED='\033[0;31m'
@@ -30,20 +24,13 @@ CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${BLUE}[run-tests]${NC} $*"; }
success(){ echo -e "${GREEN}[run-tests]${NC} ✅ $*"; }
warn() { echo -e "${YELLOW}[run-tests]${NC} ⚠️ $*"; }
error() { echo -e "${RED}[run-tests]${NC} ❌ $*" >&2; }
success(){ echo -e "${GREEN}[run-tests]${NC} $*"; }
warn() { echo -e "${YELLOW}[run-tests]${NC} $*"; }
error() { echo -e "${RED}[run-tests]${NC} $*" >&2; }
section(){ echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; }
cd "${REPO_ROOT}"
run_mysql_in_container() {
docker run --rm -i --network "${DOCKER_NETWORK}" \
-e MYSQL_PWD="${DB_PASS}" \
"${MYSQL_CLIENT_IMAGE}" \
mysql -h"${DOCKER_DB_HOST}" -u"${DB_USER}" "${DB_NAME}" "$@"
}
# ---- Load .env if present ---------------------------------------------------
if [[ -f ".env" ]]; then
@@ -54,7 +41,12 @@ if [[ -f ".env" ]]; then
set +o allexport
else
# Use defaults matching docker-compose.yml
export DATABASE_URL="mysql://sprout:sprout_dev@localhost:3306/sprout"
export DATABASE_URL="postgres://sprout:sprout_dev@localhost:5432/sprout"
export PGHOST=localhost
export PGPORT=5432
export PGUSER=sprout
export PGPASSWORD=sprout_dev
export PGDATABASE=sprout
export REDIS_URL="redis://localhost:6379"
export TYPESENSE_API_KEY="sprout_dev_key"
export TYPESENSE_URL="http://localhost:8108"
@@ -81,10 +73,10 @@ run_test_step() {
# ---- Check / start infra (for integration tests) ----------------------------
services_healthy() {
local mysql_ok redis_ok
mysql_ok=$(docker inspect --format='{{.State.Health.Status}}' sprout-mysql 2>/dev/null || echo "not_found")
local pg_ok redis_ok
pg_ok=$(docker inspect --format='{{.State.Health.Status}}' sprout-postgres 2>/dev/null || echo "not_found")
redis_ok=$(docker inspect --format='{{.State.Health.Status}}' sprout-redis 2>/dev/null || echo "not_found")
[[ "${mysql_ok}" == "healthy" && "${redis_ok}" == "healthy" ]]
[[ "${pg_ok}" == "healthy" && "${redis_ok}" == "healthy" ]]
}
ensure_services() {
@@ -116,26 +108,20 @@ ensure_services() {
ensure_migrations() {
log "Ensuring migrations are current..."
local migration_dir="${REPO_ROOT}/migrations"
local pgschema="${REPO_ROOT}/bin/pgschema"
local schema_file="${REPO_ROOT}/schema/schema.sql"
if [[ ! -d "${migration_dir}" ]]; then
warn "No migrations directory. Skipping."
if [[ ! -f "${schema_file}" ]]; then
warn "No schema.sql. Skipping."
return 0
fi
if command -v sqlx &>/dev/null; then
DATABASE_URL="${DATABASE_URL}" sqlx migrate run --source "${migration_dir}" 2>/dev/null \
if [[ -x "${pgschema}" ]]; then
"${pgschema}" apply --file "${schema_file}" --auto-approve 2>/dev/null \
&& success "Migrations current" \
|| warn "sqlx migrate run failed — DB may be out of date"
|| warn "pgschema apply failed — DB may be out of date"
else
# Fallback: apply all SQL files (idempotent if schema uses IF NOT EXISTS)
shopt -s nullglob
local sql_files=("${migration_dir}"/*.sql)
shopt -u nullglob
for sql_file in "${sql_files[@]}"; do
run_mysql_in_container < "${sql_file}" &>/dev/null || true
done
success "Migrations applied (mysql fallback)"
warn "pgschema not found at ${pgschema}. Schema may be out of date."
fi
}
@@ -200,7 +186,7 @@ echo ""
if [[ ${#PASSED[@]} -gt 0 ]]; then
echo -e " ${GREEN}Passed (${#PASSED[@]}):${NC}"
for t in "${PASSED[@]}"; do
echo -e " ${GREEN}✅${NC} ${t}"
echo -e " ${GREEN}pass${NC} ${t}"
done
fi
@@ -208,12 +194,12 @@ if [[ ${#FAILED[@]} -gt 0 ]]; then
echo ""
echo -e " ${RED}Failed (${#FAILED[@]}):${NC}"
for t in "${FAILED[@]}"; do
echo -e " ${RED}❌${NC} ${t}"
echo -e " ${RED}fail${NC} ${t}"
done
echo ""
exit 1
fi
echo ""
success "All tests passed! 🎉"
success "All tests passed!"
exit 0

View File

@@ -3,13 +3,10 @@
set -euo pipefail
DB_HOST="${SPROUT_DB_HOST:-127.0.0.1}"
DB_PORT="${SPROUT_DB_PORT:-3306}"
DB_PORT="${SPROUT_DB_PORT:-5432}"
DB_USER="${SPROUT_DB_USER:-sprout}"
DB_PASS="${SPROUT_DB_PASS:-sprout_dev}"
DB_NAME="${SPROUT_DB_NAME:-sprout}"
DOCKER_DB_HOST="${SPROUT_DOCKER_DB_HOST:-mysql}"
DOCKER_NETWORK="${SPROUT_DOCKER_NETWORK:-sprout-net}"
MYSQL_CLIENT_IMAGE="${SPROUT_DB_CLIENT_IMAGE:-mysql:8.0}"
SYSTEM_PUBKEY="0000000000000000000000000000000000000000000000000000000000000000"
ALICE_PUBKEY="953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f"
@@ -18,29 +15,28 @@ CHARLIE_PUBKEY="554cef57437abac34522ac2c9f0490d685b72c80478cf9f7ed6f9570ee8624ea
TYLER_PUBKEY="e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34"
AGENT_PUBKEY="db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36"
if command -v mysql >/dev/null 2>&1; then
run_mysql() { MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" "$DB_NAME" "$@"; }
elif docker exec sprout-mysql mysql --version >/dev/null 2>&1; then
run_mysql() {
docker run --rm -i --network "$DOCKER_NETWORK" \
-e MYSQL_PWD="$DB_PASS" \
"$MYSQL_CLIENT_IMAGE" \
mysql -h"$DOCKER_DB_HOST" -u"$DB_USER" "$DB_NAME" "$@"
if command -v psql >/dev/null 2>&1; then
run_psql() { PGPASSWORD="$DB_PASS" psql -h"$DB_HOST" -p"$DB_PORT" -U"$DB_USER" -d"$DB_NAME" -qtA "$@"; }
elif docker exec sprout-postgres psql --version >/dev/null 2>&1; then
run_psql() {
docker exec -e PGPASSWORD="$DB_PASS" sprout-postgres \
psql -U"$DB_USER" -d"$DB_NAME" -qtA "$@"
}
else
echo "No mysql client available. Start docker compose or install mysql." >&2
echo "No psql client available. Start docker compose or install postgresql-client." >&2
exit 1
fi
run_sql() {
run_mysql --silent --skip-column-names -e "$1"
run_psql -c "$1"
}
uuid5_hex() {
local slug="$1"
python3 - "$slug" <<'PYEOF'
import sys, uuid
print(uuid.uuid5(uuid.NAMESPACE_DNS, sys.argv[1]).hex)
# Format as UUID with hyphens for Postgres
print(str(uuid.uuid5(uuid.NAMESPACE_DNS, sys.argv[1])))
PYEOF
}
@@ -57,47 +53,47 @@ UUID_DM_ALICE_TYLER=$(uuid5_hex "sprout.channel.dm.alice-tyler")
UUID_DM_BOB_TYLER=$(uuid5_hex "sprout.channel.dm.bob-tyler")
UUID_DM_BOB_CHARLIE_TYLER=$(uuid5_hex "sprout.channel.dm.bob-charlie-tyler")
SYSTEM_HEX="UNHEX('${SYSTEM_PUBKEY}')"
run_sql "
INSERT IGNORE INTO channels
INSERT INTO channels
(id, name, channel_type, visibility, description, created_by, topic_required)
VALUES
(UNHEX('${UUID_GENERAL}'), 'general', 'stream', 'open', 'General discussion for everyone', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_RANDOM}'), 'random', 'stream', 'open', 'Off-topic, fun stuff', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_ENGINEERING}'), 'engineering', 'stream', 'open', 'Engineering discussions', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_AGENTS}'), 'agents', 'stream', 'open', 'AI agent testing and collaboration', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_WATERCOOLER}'), 'watercooler', 'forum', 'open', 'Casual forum for async discussions', ${SYSTEM_HEX}, 1),
(UNHEX('${UUID_ANNOUNCEMENTS}'), 'announcements', 'forum', 'open', 'Company announcements', ${SYSTEM_HEX}, 1),
(UNHEX('${UUID_DM_ALICE_TYLER}'), 'alice-tyler', 'dm', 'private', 'DM between alice and tyler', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_DM_BOB_TYLER}'), 'bob-tyler', 'dm', 'private', 'DM between bob and tyler', ${SYSTEM_HEX}, 0),
(UNHEX('${UUID_DM_BOB_CHARLIE_TYLER}'), 'bob-charlie-tyler', 'dm', 'private', 'Group DM: bob, charlie, tyler', ${SYSTEM_HEX}, 0)
('${UUID_GENERAL}', 'general', 'stream', 'open', 'General discussion for everyone', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_RANDOM}', 'random', 'stream', 'open', 'Off-topic, fun stuff', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_ENGINEERING}', 'engineering', 'stream', 'open', 'Engineering discussions', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_AGENTS}', 'agents', 'stream', 'open', 'AI agent testing and collaboration', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_WATERCOOLER}', 'watercooler', 'forum', 'open', 'Casual forum for async discussions', decode('${SYSTEM_PUBKEY}','hex'), true),
('${UUID_ANNOUNCEMENTS}', 'announcements', 'forum', 'open', 'Company announcements', decode('${SYSTEM_PUBKEY}','hex'), true),
('${UUID_DM_ALICE_TYLER}', 'alice-tyler', 'dm', 'private', 'DM between alice and tyler', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_DM_BOB_TYLER}', 'bob-tyler', 'dm', 'private', 'DM between bob and tyler', decode('${SYSTEM_PUBKEY}','hex'), false),
('${UUID_DM_BOB_CHARLIE_TYLER}', 'bob-charlie-tyler', 'dm', 'private', 'Group DM: bob, charlie, tyler', decode('${SYSTEM_PUBKEY}','hex'), false)
ON CONFLICT DO NOTHING
;
"
run_sql "
INSERT IGNORE INTO channel_members
INSERT INTO channel_members
(channel_id, pubkey, role, invited_by)
VALUES
(UNHEX('${UUID_GENERAL}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_GENERAL}'), UNHEX('${ALICE_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_GENERAL}'), UNHEX('${BOB_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_RANDOM}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_ENGINEERING}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_AGENTS}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_WATERCOOLER}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_ANNOUNCEMENTS}'), UNHEX('${TYLER_PUBKEY}'), 'guest', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_ALICE_TYLER}'), UNHEX('${ALICE_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_ALICE_TYLER}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_BOB_TYLER}'), UNHEX('${BOB_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_BOB_TYLER}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_BOB_CHARLIE_TYLER}'), UNHEX('${BOB_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_BOB_CHARLIE_TYLER}'), UNHEX('${CHARLIE_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_DM_BOB_CHARLIE_TYLER}'), UNHEX('${TYLER_PUBKEY}'), 'member', ${SYSTEM_HEX}),
(UNHEX('${UUID_GENERAL}'), UNHEX('${AGENT_PUBKEY}'), 'bot', ${SYSTEM_HEX}),
(UNHEX('${UUID_RANDOM}'), UNHEX('${AGENT_PUBKEY}'), 'bot', ${SYSTEM_HEX}),
(UNHEX('${UUID_ENGINEERING}'), UNHEX('${AGENT_PUBKEY}'), 'bot', ${SYSTEM_HEX}),
(UNHEX('${UUID_AGENTS}'), UNHEX('${AGENT_PUBKEY}'), 'bot', ${SYSTEM_HEX})
('${UUID_GENERAL}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_GENERAL}', decode('${ALICE_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_GENERAL}', decode('${BOB_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_RANDOM}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_ENGINEERING}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_AGENTS}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_WATERCOOLER}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_ANNOUNCEMENTS}', decode('${TYLER_PUBKEY}','hex'), 'guest', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_ALICE_TYLER}', decode('${ALICE_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_ALICE_TYLER}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_BOB_TYLER}', decode('${BOB_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_BOB_TYLER}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_BOB_CHARLIE_TYLER}', decode('${BOB_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_BOB_CHARLIE_TYLER}', decode('${CHARLIE_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_DM_BOB_CHARLIE_TYLER}', decode('${TYLER_PUBKEY}','hex'), 'member', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_GENERAL}', decode('${AGENT_PUBKEY}','hex'), 'bot', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_RANDOM}', decode('${AGENT_PUBKEY}','hex'), 'bot', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_ENGINEERING}', decode('${AGENT_PUBKEY}','hex'), 'bot', decode('${SYSTEM_PUBKEY}','hex')),
('${UUID_AGENTS}', decode('${AGENT_PUBKEY}','hex'), 'bot', decode('${SYSTEM_PUBKEY}','hex'))
ON CONFLICT DO NOTHING
;
"