Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/mysql-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ jobs:
databases="$(
docker compose --env-file .docker.env -p "${COMPOSE_PROJECT_NAME}" \
exec -T mysql sh -c \
'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql --user=root --batch --skip-column-names --execute="SELECT GROUP_CONCAT(SCHEMA_NAME ORDER BY SCHEMA_NAME) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME IN (\"demo\", \"world\", \"sakila\");"' \
'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql --user=root --batch --skip-column-names --execute="SELECT GROUP_CONCAT(SCHEMA_NAME ORDER BY SCHEMA_NAME) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME IN (\"demo\", \"sakila\", \"chinook\");"' \
| tr -d '\r'
)"
test "${databases}" = "demo,sakila,world"
test "${databases}" = "chinook,demo,sakila"

- name: Check MySQL training access
run: make check-mysql-access
Expand Down
25 changes: 16 additions & 9 deletions .github/workflows/postgres-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Validate Compose configuration
run: make config

- name: Prepare PostgreSQL sample
- name: Prepare PostgreSQL samples
run: make samples-postgres

- name: Start only PostgreSQL
Expand All @@ -47,12 +47,18 @@ jobs:
set +a

databases="$(
docker compose --env-file .docker.env -p "${COMPOSE_PROJECT_NAME}" \
exec -T postgres sh -c \
'PGPASSWORD="$POSTGRES_PASSWORD" psql --host=127.0.0.1 --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --tuples-only --no-align --command="SELECT string_agg(datname, chr(44) ORDER BY datname) FROM pg_catalog.pg_database WHERE datname IN (current_database(), chr(112) || chr(97) || chr(103) || chr(105) || chr(108) || chr(97));"' \
| tr -d '\r'
PGPASSWORD="${POSTGRES_SUPERUSER_PASSWORD}" \
docker compose --env-file .docker.env -p "${COMPOSE_PROJECT_NAME}" \
exec -T -e PGPASSWORD postgres \
psql --host=127.0.0.1 --username="${POSTGRES_SUPERUSER}" \
--dbname="${POSTGRES_DATABASE}" --tuples-only --no-align \
<<'SQL' | tr -d '\r'
SELECT string_agg(datname, ',' ORDER BY datname)
FROM pg_catalog.pg_database
WHERE datname IN (current_database(), 'pagila', 'chinook');
SQL
)"
test "${databases}" = "demo,pagila"
test "${databases}" = "chinook,demo,pagila"

- name: Check PostgreSQL training access
run: make check-postgres-access
Expand All @@ -78,7 +84,7 @@ jobs:
;;
esac

- name: Verify Pagila loader idempotency
- name: Verify Pagila and Chinook loader idempotency
run: |
set -Eeuo pipefail
set -a
Expand All @@ -90,8 +96,9 @@ jobs:
exec -T postgres /docker-entrypoint-initdb.d/050_load_optional_samples.sh
)"
echo "${loader_output}"
grep -Fq 'already complete' <<< "${loader_output}"
grep -Fq 'skipping reload' <<< "${loader_output}"
grep -Fq 'Optional PostgreSQL sample Pagila is already complete' <<< "${loader_output}"
grep -Fq 'Optional PostgreSQL sample Chinook is already complete' <<< "${loader_output}"
test "$(grep -Fc 'skipping reload.' <<< "${loader_output}")" = 2

- name: Restart only PostgreSQL
run: |
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/sql-lab-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ jobs:
grep -F 'DB_USER' "${html_file}"
grep -F 'DB_PASSWORD' "${html_file}"
grep -F 'demo' "${html_file}"
grep -F 'chinook' "${html_file}"
if grep -Fi 'world' "${html_file}"; then
echo 'Adminer exposes World as an available training database' >&2
exit 1
fi
if grep -E "value=['\"]db['\"]" "${html_file}"; then
echo 'Adminer exposes invalid server db' >&2
exit 1
Expand Down
152 changes: 129 additions & 23 deletions Makefile

Large diffs are not rendered by default.

136 changes: 96 additions & 40 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions adminer/plugins-enabled/002-login-help.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public function loginForm(): void
<legend>Как войти</legend>
<p>Выберите MySQL (mysql) или PostgreSQL (postgres).</p>
<p>Имя пользователя и пароль — значения <code>DB_USER</code> и <code>DB_PASSWORD</code> из файла <code>.docker.env</code>.</p>
<p>Для PostgreSQL всегда доступна <code>demo</code>, а после отдельной подготовки Pagila и чистой инициализации — <code>pagila</code>.</p>
<p>Для MySQL также доступны <code>world</code> и <code>sakila</code>, если установлены optional samples.</p>
<p>Для PostgreSQL всегда доступна <code>demo</code>, а после отдельной подготовки и чистой инициализации — optional <code>pagila</code> и <code>chinook</code>.</p>
<p>Для MySQL всегда доступна <code>demo</code>, а после отдельной подготовки и чистой инициализации — optional <code>sakila</code> и <code>chinook</code>.</p>
<p>Административные пользователи для обычной учебной работы не требуются.</p>
</fieldset>
</div>
Expand Down
86 changes: 79 additions & 7 deletions initdb/mysql/050_load_optional_samples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,100 @@ set -Eeuo pipefail
: "${MYSQL_ROOT_PASSWORD:?environment variable MYSQL_ROOT_PASSWORD is required}"

samples_dir=/opt/mysql-samples
world_file="${samples_dir}/010_world.sql"
chinook_file="${samples_dir}/010_chinook.sql"
sakila_schema_file="${samples_dir}/020_sakila_schema.sql"
sakila_data_file="${samples_dir}/021_sakila_data.sql"

mysql_root() {
MYSQL_PWD="${MYSQL_ROOT_PASSWORD}" mysql \
--protocol=socket \
--user=root \
--batch \
--skip-column-names \
"$@"
}

if [[ ! -d "${samples_dir}" ]]; then
echo "Optional MySQL samples directory is not mounted; skipping World and Sakila."
echo "Optional MySQL samples directory is not mounted; skipping Chinook and Sakila."
exit 0
fi

if [[ -f "${world_file}" ]]; then
echo "Loading optional MySQL sample: World..."
mysql_root < "${world_file}"
echo "Loaded optional MySQL sample: World."
chinook_is_complete() {
local key_table_count key_tables_have_data join_has_data

key_table_count=$(mysql_root --execute="
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'chinook'
AND table_type = 'BASE TABLE'
AND BINARY table_name IN ('Artist', 'Album', 'Track', 'Customer', 'Invoice');
")
[[ "${key_table_count}" == "5" ]] || return 1

key_tables_have_data=$(mysql_root --execute="
SELECT EXISTS (SELECT 1 FROM chinook.Artist)
AND EXISTS (SELECT 1 FROM chinook.Album)
AND EXISTS (SELECT 1 FROM chinook.Track)
AND EXISTS (SELECT 1 FROM chinook.Customer)
AND EXISTS (SELECT 1 FROM chinook.Invoice);
")
[[ "${key_tables_have_data}" == "1" ]] || return 1

join_has_data=$(mysql_root --execute="
SELECT EXISTS (
SELECT 1
FROM chinook.Artist AS artist
JOIN chinook.Album AS album ON album.ArtistId = artist.ArtistId
JOIN chinook.Track AS track ON track.AlbumId = album.AlbumId
);
")
[[ "${join_has_data}" == "1" ]]
}

chinook_file_has_database_setup() {
awk '
BEGIN { in_comment = 0; found = 0 }
{ upper = toupper($0) }
/^[[:space:]]*\/\*/ { in_comment = 1 }
!in_comment && upper ~ /^[[:space:]]*((DROP|CREATE)[[:space:]]+DATABASE|USE[[:space:]])/ { found = 1 }
/\*\// { in_comment = 0 }
END { exit(found ? 0 : 1) }
' "${chinook_file}"
}

if [[ -f "${chinook_file}" ]]; then
if chinook_file_has_database_setup; then
echo "ERROR: prepared Chinook MySQL SQL contains forbidden database-level statements" >&2
exit 1
fi

chinook_exists=$(mysql_root --execute="
SELECT COUNT(*)
FROM information_schema.schemata
WHERE schema_name = 'chinook';
")

if [[ "${chinook_exists}" == "1" ]]; then
if chinook_is_complete; then
echo "Optional MySQL sample Chinook is already complete; skipping reload."
else
echo "ERROR: database chinook already exists but is incomplete or unexpected" >&2
echo "Recreate MySQL explicitly with: make reinit-mysql CONFIRM=1" >&2
exit 1
fi
else
echo "Creating optional MySQL database chinook..."
mysql_root --execute='CREATE DATABASE `chinook` CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;'
echo "Loading optional MySQL sample: Chinook..."
mysql_root --database=chinook < "${chinook_file}"
if ! chinook_is_complete; then
echo "ERROR: Chinook load finished but required tables, data, or join verification failed" >&2
exit 1
fi
echo "Loaded and verified optional MySQL sample Chinook."
fi
else
echo "Optional MySQL sample World is not present; skipping it."
echo "Optional MySQL sample Chinook is not present; skipping it."
fi

if [[ -f "${sakila_schema_file}" && ! -f "${sakila_data_file}" ]]; then
Expand Down
51 changes: 47 additions & 4 deletions initdb/mysql/099_check_training_access.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,54 @@ SQL

echo "Verified required MySQL table, all five seed emails and user-row INSERT access."

if [[ " ${expected_databases[*]} " =~ [[:space:]]world[[:space:]] ]]; then
mysql_training --execute="SELECT COUNT(*) FROM world.city;" >/dev/null
echo "Found and verified optional MySQL sample world.city."
if [[ " ${expected_databases[*]} " =~ [[:space:]]chinook[[:space:]] ]]; then
chinook_key_table_count=$(mysql_training --execute="
SELECT COUNT(*)
FROM information_schema.tables
WHERE table_schema = 'chinook'
AND table_type = 'BASE TABLE'
AND BINARY table_name IN ('Artist', 'Album', 'Track', 'Customer', 'Invoice');
")
if [[ "${chinook_key_table_count}" != "5" ]]; then
echo "ERROR: Chinook does not contain all required case-sensitive tables" >&2
exit 1
fi

chinook_key_tables_have_data=$(mysql_training --execute="
SELECT EXISTS (SELECT 1 FROM chinook.Artist)
AND EXISTS (SELECT 1 FROM chinook.Album)
AND EXISTS (SELECT 1 FROM chinook.Track)
AND EXISTS (SELECT 1 FROM chinook.Customer)
AND EXISTS (SELECT 1 FROM chinook.Invoice);
")
if [[ "${chinook_key_tables_have_data}" != "1" ]]; then
echo "ERROR: one or more required Chinook tables contain no data" >&2
exit 1
fi

chinook_join_has_data=$(mysql_training --execute="
SELECT EXISTS (
SELECT 1
FROM chinook.Artist AS artist
JOIN chinook.Album AS album ON album.ArtistId = artist.ArtistId
JOIN chinook.Track AS track ON track.AlbumId = album.AlbumId
);
")
if [[ "${chinook_join_has_data}" != "1" ]]; then
echo "ERROR: Chinook Artist-Album-Track join returned no rows" >&2
exit 1
fi

mysql_training <<'SQL'
START TRANSACTION;
INSERT INTO chinook.Artist (ArtistId, Name)
VALUES (-2147483648, 'SQL Lab access check');
SELECT Name FROM chinook.Artist WHERE ArtistId = -2147483648;
ROLLBACK;
SQL
echo "Found and verified optional MySQL sample Chinook tables, data, join and reversible write access."
else
echo "Optional MySQL sample World is not installed; skipped."
echo "Optional MySQL sample Chinook is not installed; skipped."
fi

if [[ " ${expected_databases[*]} " =~ [[:space:]]sakila[[:space:]] ]]; then
Expand Down
Loading