diff --git a/.github/workflows/ci-mariadb.yml b/.github/workflows/ci-mariadb.yml new file mode 100644 index 00000000..40c2555e --- /dev/null +++ b/.github/workflows/ci-mariadb.yml @@ -0,0 +1,117 @@ +name: CI MariaDB + +on: + push: + pull_request: + schedule: + # Weekly on Monday at 3:07 AM UTC + - cron: '7 3 * * 1' + workflow_dispatch: + +jobs: + employees-mariadb: + name: Employees DB (${{ matrix.mariadb-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + mariadb-version: + - '10.11.9' + - '11.4.5' + - '12.1.2' + env: + SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql + MARIADB_VERSION: ${{ matrix.mariadb-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y libaio1 libnuma1 libncurses5 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Cache MariaDB tarball + uses: actions/cache@v4 + with: + path: /tmp/mariadb-tarball + key: mariadb-${{ matrix.mariadb-version }}-linux-x86_64-v1 + + - name: Download MariaDB + run: | + TARBALL="mariadb-${MARIADB_VERSION}-linux-systemd-x86_64.tar.gz" + URL="https://archive.mariadb.org/mariadb-${MARIADB_VERSION}/bintar-linux-systemd-x86_64/${TARBALL}" + mkdir -p /tmp/mariadb-tarball + if [ ! -f "/tmp/mariadb-tarball/$TARBALL" ]; then + echo "Downloading MariaDB ${MARIADB_VERSION}..." + curl -L -f -o "/tmp/mariadb-tarball/$TARBALL" "$URL" + fi + ls -lh "/tmp/mariadb-tarball/$TARBALL" + + - name: Unpack MariaDB + run: | + mkdir -p "$SANDBOX_BINARY" + TARBALL="mariadb-${MARIADB_VERSION}-linux-systemd-x86_64.tar.gz" + dbdeployer unpack "/tmp/mariadb-tarball/$TARBALL" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Deploy sandbox + run: | + dbdeployer deploy single "$MARIADB_VERSION" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Load employees database + run: | + ~/sandboxes/msb_*/use < employees.sql + + - name: Test MD5 integrity + run: | + ~/sandboxes/msb_*/use -t < test_employees_md5.sql > /tmp/test_md5.txt + cat /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + if [ "$md5_ok" != "8" ]; then + echo "MD5 FAIL - expected 8 OK - found $md5_ok" + exit 1 + fi + echo "MD5 OK ($md5_ok matches)" + + - name: Test SHA integrity + run: | + ~/sandboxes/msb_*/use -t < test_employees_sha.sql > /tmp/test_sha.txt + cat /tmp/test_sha.txt + sha_ok=$(grep -iw ok /tmp/test_sha.txt | wc -l | tr -d ' \t') + if [ "$sha_ok" != "8" ]; then + echo "SHA FAIL - expected 8 OK - found $sha_ok" + exit 1 + fi + echo "SHA OK ($sha_ok matches)" + + - name: Load objects (stored procedures/functions) + run: | + ~/sandboxes/msb_*/use < objects.sql + + - name: Test stored procedures + run: | + SBDIR=$(ls -d ~/sandboxes/msb_*) + # Test functions + $SBDIR/use -BN -e "SELECT emp_name(10001);" employees + $SBDIR/use -BN -e "SELECT emp_dept_name(10001);" employees + $SBDIR/use -BN -e "SELECT current_manager('d001');" employees + # Test procedure + $SBDIR/use -t -e "CALL show_departments();" employees + + - name: Verify MariaDB version + run: | + ~/sandboxes/msb_*/use -BN -e "SELECT VERSION();" | grep -i mariadb + echo "OK: MariaDB detected" + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml new file mode 100644 index 00000000..05b65b4a --- /dev/null +++ b/.github/workflows/ci-mysql.yml @@ -0,0 +1,452 @@ +name: CI MySQL + +on: + push: + pull_request: + schedule: + # Weekly on Monday at 3:07 AM UTC + - cron: '7 3 * * 1' + workflow_dispatch: + +jobs: + employees-mysql: + name: Employees DB (${{ matrix.mysql-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + mysql-version: + - '5.6.51' + - '5.7.44' + - '8.0.42' + - '8.4.8' + - '9.0.1' + - '9.2.0' + - '9.5.0' + - '9.6.0' + env: + SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql + MYSQL_VERSION: ${{ matrix.mysql-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y libaio1 libnuma1 libncurses5 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Cache MySQL tarball + uses: actions/cache@v4 + with: + path: /tmp/mysql-tarball + key: mysql-${{ matrix.mysql-version }}-linux-x86_64-v3 + + - name: Download MySQL + run: | + SHORT_VER="${MYSQL_VERSION%.*}" + mkdir -p /tmp/mysql-tarball + CACHED=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* 2>/dev/null | head -1) + if [ -n "$CACHED" ]; then + echo "Using cached tarball: $CACHED" + ls -lh "$CACHED" + exit 0 + fi + # MySQL 5.x uses .tar.gz / glibc2.12; 8.x+ uses .tar.xz / glibc2.17; 9.6+ uses glibc2.28 + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + if [ "$MAJOR" -lt 8 ]; then + GLIBCS="glibc2.12" + EXT="tar.gz" + else + GLIBCS="glibc2.17 glibc2.28" + EXT="tar.xz" + fi + for GLIBC in $GLIBCS; do + TARBALL="mysql-${MYSQL_VERSION}-linux-${GLIBC}-x86_64.${EXT}" + URL="https://dev.mysql.com/get/Downloads/MySQL-${SHORT_VER}/${TARBALL}" + echo "Trying ${GLIBC} (${EXT})..." + if curl -L -f -o "/tmp/mysql-tarball/$TARBALL" "$URL"; then + ls -lh "/tmp/mysql-tarball/$TARBALL" + exit 0 + fi + done + echo "ERROR: Could not download MySQL ${MYSQL_VERSION}" + exit 1 + + - name: Unpack MySQL + run: | + mkdir -p "$SANDBOX_BINARY" + TARBALL=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* | head -1) + dbdeployer unpack "$TARBALL" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Deploy sandbox + run: | + dbdeployer deploy single "$MYSQL_VERSION" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Detect MySQL features + id: features + run: | + # --commands flag only exists in MySQL 9.5+ (SOURCE default changed to FALSE) + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + MINOR=$(echo "$MYSQL_VERSION" | cut -d. -f2) + NEEDS_COMMANDS="false" + if [ "$MAJOR" -gt 9 ] || { [ "$MAJOR" -eq 9 ] && [ "$MINOR" -ge 5 ]; }; then + NEEDS_COMMANDS="true" + fi + # Check if MD5()/SHA() are available (both removed in MySQL 9.6+) + HAS_MD5="true" + HAS_SHA="true" + if [ "$MAJOR" -gt 9 ] || { [ "$MAJOR" -eq 9 ] && [ "$MINOR" -ge 6 ]; }; then + HAS_MD5="false" + HAS_SHA="false" + fi + echo "needs_commands=$NEEDS_COMMANDS" >> "$GITHUB_OUTPUT" + echo "has_md5=$HAS_MD5" >> "$GITHUB_OUTPUT" + echo "has_sha=$HAS_SHA" >> "$GITHUB_OUTPUT" + echo "MySQL $MYSQL_VERSION: needs_commands=$NEEDS_COMMANDS, has_md5=$HAS_MD5, has_sha=$HAS_SHA" + + - name: Load employees database + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS < employees.sql + + - name: Test MD5 integrity + if: steps.features.outputs.has_md5 == 'true' + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS -t < test_employees_md5.sql > /tmp/test_md5.txt + cat /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + if [ "$md5_ok" != "8" ]; then + echo "MD5 FAIL - expected 8 OK - found $md5_ok" + exit 1 + fi + echo "MD5 OK ($md5_ok matches)" + + - name: Test SHA integrity + if: steps.features.outputs.has_sha == 'true' + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS -t < test_employees_sha.sql > /tmp/test_sha.txt + cat /tmp/test_sha.txt + sha_ok=$(grep -iw ok /tmp/test_sha.txt | wc -l | tr -d ' \t') + if [ "$sha_ok" != "8" ]; then + echo "SHA FAIL - expected 8 OK - found $sha_ok" + exit 1 + fi + echo "SHA OK ($sha_ok matches)" + + - name: Test SHA2 integrity + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS -t < test_employees_sha2.sql > /tmp/test_sha2.txt + cat /tmp/test_sha2.txt + sha2_ok=$(grep -iw ok /tmp/test_sha2.txt | wc -l | tr -d ' \t') + if [ "$sha2_ok" != "8" ]; then + echo "SHA2 FAIL - expected 8 OK - found $sha2_ok" + exit 1 + fi + echo "SHA2 OK ($sha2_ok matches)" + + - name: Load objects (stored procedures/functions) + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS < objects.sql + + - name: Test stored procedures + run: | + SBDIR=$(ls -d ~/sandboxes/msb_*) + $SBDIR/use -BN -e "SELECT emp_name(10001);" employees + $SBDIR/use -BN -e "SELECT emp_dept_name(10001);" employees + $SBDIR/use -BN -e "SELECT current_manager('d001');" employees + $SBDIR/use -t -e "CALL show_departments();" employees + $SBDIR/use -BN -e "SELECT COUNT(*) FROM v_full_employees;" employees + $SBDIR/use -BN -e "SELECT COUNT(*) FROM v_full_departments;" employees + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true + + partitioned-mysql: + name: Employees Partitioned (${{ matrix.mysql-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + mysql-version: + - '5.6.51' + - '5.7.44' + - '8.0.42' + - '8.4.8' + - '9.0.1' + - '9.2.0' + - '9.5.0' + - '9.6.0' + env: + SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql + MYSQL_VERSION: ${{ matrix.mysql-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y libaio1 libnuma1 libncurses5 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Cache MySQL tarball + uses: actions/cache@v4 + with: + path: /tmp/mysql-tarball + key: mysql-${{ matrix.mysql-version }}-linux-x86_64-v3 + + - name: Download MySQL + run: | + SHORT_VER="${MYSQL_VERSION%.*}" + mkdir -p /tmp/mysql-tarball + CACHED=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* 2>/dev/null | head -1) + if [ -n "$CACHED" ]; then + echo "Using cached tarball: $CACHED" + ls -lh "$CACHED" + exit 0 + fi + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + if [ "$MAJOR" -lt 8 ]; then + GLIBCS="glibc2.12" + EXT="tar.gz" + else + GLIBCS="glibc2.17 glibc2.28" + EXT="tar.xz" + fi + for GLIBC in $GLIBCS; do + TARBALL="mysql-${MYSQL_VERSION}-linux-${GLIBC}-x86_64.${EXT}" + URL="https://dev.mysql.com/get/Downloads/MySQL-${SHORT_VER}/${TARBALL}" + echo "Trying ${GLIBC}..." + if curl -L -f -o "/tmp/mysql-tarball/$TARBALL" "$URL"; then + ls -lh "/tmp/mysql-tarball/$TARBALL" + exit 0 + fi + done + echo "ERROR: Could not download MySQL ${MYSQL_VERSION}" + exit 1 + + - name: Unpack MySQL + run: | + mkdir -p "$SANDBOX_BINARY" + TARBALL=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* | head -1) + dbdeployer unpack "$TARBALL" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Deploy sandbox + run: | + dbdeployer deploy single "$MYSQL_VERSION" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Detect MySQL features + id: features + run: | + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + MINOR=$(echo "$MYSQL_VERSION" | cut -d. -f2) + NEEDS_COMMANDS="false" + if [ "$MAJOR" -gt 9 ] || { [ "$MAJOR" -eq 9 ] && [ "$MINOR" -ge 5 ]; }; then + NEEDS_COMMANDS="true" + fi + HAS_MD5="true" + if [ "$MAJOR" -gt 9 ] || { [ "$MAJOR" -eq 9 ] && [ "$MINOR" -ge 6 ]; }; then + HAS_MD5="false" + fi + echo "needs_commands=$NEEDS_COMMANDS" >> "$GITHUB_OUTPUT" + echo "has_md5=$HAS_MD5" >> "$GITHUB_OUTPUT" + + - name: Load partitioned employees database + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS < employees_partitioned.sql + + - name: Verify partitioning + run: | + SBDIR=$(ls -d ~/sandboxes/msb_*) + PART_COUNT=$($SBDIR/use -BN -e \ + "SELECT COUNT(*) FROM information_schema.partitions WHERE table_schema='employees' AND table_name='titles';" \ + employees) + echo "titles partitions: $PART_COUNT" + [ "$PART_COUNT" -ge 18 ] || { echo "FAIL: expected >= 18 partitions on titles"; exit 1; } + PART_COUNT=$($SBDIR/use -BN -e \ + "SELECT COUNT(*) FROM information_schema.partitions WHERE table_schema='employees' AND table_name='salaries';" \ + employees) + echo "salaries partitions: $PART_COUNT" + [ "$PART_COUNT" -ge 18 ] || { echo "FAIL: expected >= 18 partitions on salaries"; exit 1; } + + - name: Test MD5 integrity + if: steps.features.outputs.has_md5 == 'true' + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS -t < test_employees_md5.sql > /tmp/test_md5.txt + cat /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + if [ "$md5_ok" != "8" ]; then + echo "MD5 FAIL - expected 8 OK - found $md5_ok" + exit 1 + fi + echo "MD5 OK ($md5_ok matches)" + + - name: Test SHA2 integrity + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS -t < test_employees_sha2.sql > /tmp/test_sha2.txt + cat /tmp/test_sha2.txt + sha2_ok=$(grep -iw ok /tmp/test_sha2.txt | wc -l | tr -d ' \t') + if [ "$sha2_ok" != "8" ]; then + echo "SHA2 FAIL - expected 8 OK - found $sha2_ok" + exit 1 + fi + echo "SHA2 OK ($sha2_ok matches)" + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true + + sakila-mysql: + name: Sakila DB (${{ matrix.mysql-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + mysql-version: + - '5.6.51' + - '5.7.44' + - '8.0.42' + - '8.4.8' + - '9.0.1' + - '9.2.0' + - '9.5.0' + - '9.6.0' + env: + SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql + MYSQL_VERSION: ${{ matrix.mysql-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y libaio1 libnuma1 libncurses5 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Cache MySQL tarball + uses: actions/cache@v4 + with: + path: /tmp/mysql-tarball + key: mysql-${{ matrix.mysql-version }}-linux-x86_64-v3 + + - name: Download MySQL + run: | + SHORT_VER="${MYSQL_VERSION%.*}" + mkdir -p /tmp/mysql-tarball + CACHED=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* 2>/dev/null | head -1) + if [ -n "$CACHED" ]; then + echo "Using cached tarball: $CACHED" + ls -lh "$CACHED" + exit 0 + fi + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + if [ "$MAJOR" -lt 8 ]; then + GLIBCS="glibc2.12" + EXT="tar.gz" + else + GLIBCS="glibc2.17 glibc2.28" + EXT="tar.xz" + fi + for GLIBC in $GLIBCS; do + TARBALL="mysql-${MYSQL_VERSION}-linux-${GLIBC}-x86_64.${EXT}" + URL="https://dev.mysql.com/get/Downloads/MySQL-${SHORT_VER}/${TARBALL}" + echo "Trying ${GLIBC}..." + if curl -L -f -o "/tmp/mysql-tarball/$TARBALL" "$URL"; then + ls -lh "/tmp/mysql-tarball/$TARBALL" + exit 0 + fi + done + echo "ERROR: Could not download MySQL ${MYSQL_VERSION}" + exit 1 + + - name: Unpack MySQL + run: | + mkdir -p "$SANDBOX_BINARY" + TARBALL=$(ls /tmp/mysql-tarball/mysql-${MYSQL_VERSION}-linux-*.tar.* | head -1) + dbdeployer unpack "$TARBALL" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Deploy sandbox + run: | + dbdeployer deploy single "$MYSQL_VERSION" \ + --sandbox-binary="$SANDBOX_BINARY" + + - name: Detect MySQL features + id: features + run: | + MAJOR=$(echo "$MYSQL_VERSION" | cut -d. -f1) + MINOR=$(echo "$MYSQL_VERSION" | cut -d. -f2) + NEEDS_COMMANDS="false" + if [ "$MAJOR" -gt 9 ] || { [ "$MAJOR" -eq 9 ] && [ "$MINOR" -ge 5 ]; }; then + NEEDS_COMMANDS="true" + fi + echo "needs_commands=$NEEDS_COMMANDS" >> "$GITHUB_OUTPUT" + echo "MySQL $MYSQL_VERSION: needs_commands=$NEEDS_COMMANDS" + + - name: Load Sakila schema + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS < sakila/sakila-mv-schema.sql + + - name: Load Sakila data + run: | + EXTRA_ARGS="" + [ "${{ steps.features.outputs.needs_commands }}" == "true" ] && EXTRA_ARGS="--commands" + ~/sandboxes/msb_*/use $EXTRA_ARGS < sakila/sakila-mv-data.sql + + - name: Verify Sakila data + run: | + SBDIR=$(ls -d ~/sandboxes/msb_*) + for table in actor film customer rental payment staff store; do + COUNT=$($SBDIR/use -BN -e "SELECT COUNT(*) FROM $table;" sakila) + echo "$table: $COUNT rows" + [ "$COUNT" -gt 0 ] || { echo "FAIL: $table is empty"; exit 1; } + done + $SBDIR/use -BN -e "SELECT COUNT(*) FROM film_list;" sakila + $SBDIR/use -BN -e "SELECT COUNT(*) FROM customer_list;" sakila + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true diff --git a/.github/workflows/ci-percona.yml b/.github/workflows/ci-percona.yml new file mode 100644 index 00000000..6c9c326a --- /dev/null +++ b/.github/workflows/ci-percona.yml @@ -0,0 +1,112 @@ +name: CI Percona + +on: + push: + pull_request: + schedule: + # Weekly on Monday at 3:07 AM UTC + - cron: '7 3 * * 1' + workflow_dispatch: + +jobs: + employees-percona: + name: Employees DB (${{ matrix.percona-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + percona-version: + - '8.0.36-28' + - '8.4.2-2' + env: + SANDBOX_BINARY: ${{ github.workspace }}/opt/mysql + PERCONA_VERSION: ${{ matrix.percona-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install system libraries + run: | + sudo apt-get update + sudo apt-get install -y libaio1 libnuma1 libncurses5 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Cache Percona tarball + uses: actions/cache@v4 + with: + path: /tmp/percona-tarball + key: percona-${{ matrix.percona-version }}-linux-x86_64-v1 + + - name: Download Percona Server + run: | + MAJOR_MINOR=$(echo "$PERCONA_VERSION" | grep -oP '^\d+\.\d+') + GLIBC="glibc2.35" + TARBALL="Percona-Server-${PERCONA_VERSION}-Linux.x86_64.${GLIBC}-minimal.tar.gz" + URL="https://downloads.percona.com/downloads/Percona-Server-${MAJOR_MINOR}/Percona-Server-${PERCONA_VERSION}/binary/tarball/${TARBALL}" + mkdir -p /tmp/percona-tarball + if [ ! -f "/tmp/percona-tarball/$TARBALL" ]; then + echo "Downloading Percona Server ${PERCONA_VERSION} (${GLIBC})..." + curl -L -f -o "/tmp/percona-tarball/$TARBALL" "$URL" || { + GLIBC="glibc2.17" + TARBALL="Percona-Server-${PERCONA_VERSION}-Linux.x86_64.${GLIBC}-minimal.tar.gz" + URL="https://downloads.percona.com/downloads/Percona-Server-${MAJOR_MINOR}/Percona-Server-${PERCONA_VERSION}/binary/tarball/${TARBALL}" + echo "Retrying with glibc2.17..." + curl -L -f -o "/tmp/percona-tarball/$TARBALL" "$URL" + } + fi + ls -lh "/tmp/percona-tarball/"*.tar.gz + + - name: Unpack Percona Server + run: | + mkdir -p "$SANDBOX_BINARY" + TARBALL=$(ls /tmp/percona-tarball/Percona-Server-*.tar.gz | head -1) + dbdeployer unpack "$TARBALL" --sandbox-binary="$SANDBOX_BINARY" + + - name: Deploy sandbox + run: | + VERSION=$(ls "$SANDBOX_BINARY" | head -1) + echo "Deploying Percona Server $VERSION..." + dbdeployer deploy single "$VERSION" --sandbox-binary="$SANDBOX_BINARY" + + - name: Load employees database + run: | + ~/sandboxes/msb_*/use < employees.sql + + - name: Test MD5 integrity + run: | + ~/sandboxes/msb_*/use -t < test_employees_md5.sql > /tmp/test_md5.txt + cat /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + if [ "$md5_ok" != "8" ]; then + echo "MD5 FAIL - expected 8 OK - found $md5_ok" + exit 1 + fi + echo "MD5 OK ($md5_ok matches)" + + - name: Test SHA integrity + run: | + ~/sandboxes/msb_*/use -t < test_employees_sha.sql > /tmp/test_sha.txt + cat /tmp/test_sha.txt + sha_ok=$(grep -iw ok /tmp/test_sha.txt | wc -l | tr -d ' \t') + if [ "$sha_ok" != "8" ]; then + echo "SHA FAIL - expected 8 OK - found $sha_ok" + exit 1 + fi + echo "SHA OK ($sha_ok matches)" + + - name: Verify Percona Server version + run: | + VERSION=$(~/sandboxes/msb_*/use -BN -e "SELECT VERSION();") + echo "Server version: $VERSION" + echo "$VERSION" | grep -iq percona || echo "Note: version string does not contain 'percona'" + echo "OK: Server running" + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" mysqld 2>/dev/null || true diff --git a/.github/workflows/ci-postgresql.yml b/.github/workflows/ci-postgresql.yml new file mode 100644 index 00000000..e4699966 --- /dev/null +++ b/.github/workflows/ci-postgresql.yml @@ -0,0 +1,128 @@ +name: CI PostgreSQL + +on: + push: + pull_request: + schedule: + # Weekly on Monday at 3:07 AM UTC + - cron: '7 3 * * 1' + workflow_dispatch: + +jobs: + employees-postgresql: + name: Employees DB (PostgreSQL ${{ matrix.pg-version }}) + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + pg-version: + - '16' + - '17' + env: + PG_VERSION: ${{ matrix.pg-version }} + + steps: + - uses: actions/checkout@v4 + + - name: Install dbdeployer + run: | + curl -s https://raw.githubusercontent.com/ProxySQL/dbdeployer/master/scripts/dbdeployer-install.sh | bash + sudo mv dbdeployer /usr/local/bin/dbdeployer + + - name: Install PostgreSQL + run: | + sudo apt-get update + sudo apt-get install -y curl ca-certificates + sudo install -d /usr/share/postgresql-common/pgdg + sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \ + --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc + sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] \ + http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list' + sudo apt-get update + sudo apt-get install -y postgresql-${PG_VERSION} postgresql-client-${PG_VERSION} + + - name: Set up PostgreSQL binaries for dbdeployer + run: | + sudo systemctl stop postgresql || true + PG_FULL=$(dpkg -s postgresql-${PG_VERSION} | grep '^Version:' | sed 's/Version: //' | cut -d'-' -f1) + echo "PostgreSQL version: ${PG_FULL}" + mkdir -p ~/opt/postgresql/${PG_FULL}/{bin,lib,share} + cp -a /usr/lib/postgresql/${PG_VERSION}/bin/. ~/opt/postgresql/${PG_FULL}/bin/ + cp -a /usr/lib/postgresql/${PG_VERSION}/lib/. ~/opt/postgresql/${PG_FULL}/lib/ + cp -a /usr/share/postgresql/${PG_VERSION}/. ~/opt/postgresql/${PG_FULL}/share/ + + - name: Deploy sandbox + run: | + PG_FULL=$(ls ~/opt/postgresql/ | head -1) + echo "Deploying PostgreSQL ${PG_FULL}..." + dbdeployer deploy postgresql "${PG_FULL}" + + - name: Verify sandbox + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use -c "SELECT version();" + echo "OK: PostgreSQL sandbox running" + + - name: Load employees database + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + export PSQL="$SBDIR/use" + bash postgresql/load_employees_db.sh + + - name: Test MD5 integrity + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use < postgresql/test_employees_md5.sql > /tmp/test_md5.txt 2>&1 + cat /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + if [ "$md5_ok" != "8" ]; then + echo "MD5 FAIL - expected 8 OK - found $md5_ok" + exit 1 + fi + echo "MD5 OK ($md5_ok matches)" + + - name: Test SHA integrity + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use < postgresql/test_employees_sha.sql > /tmp/test_sha.txt 2>&1 + cat /tmp/test_sha.txt + sha_ok=$(grep -iw ok /tmp/test_sha.txt | wc -l | tr -d ' \t') + if [ "$sha_ok" != "8" ]; then + echo "SHA FAIL - expected 8 OK - found $sha_ok" + exit 1 + fi + echo "SHA OK ($sha_ok matches)" + + - name: Test SHA2 integrity + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use < postgresql/test_employees_sha2.sql > /tmp/test_sha2.txt 2>&1 + cat /tmp/test_sha2.txt + sha2_ok=$(grep -iw ok /tmp/test_sha2.txt | wc -l | tr -d ' \t') + if [ "$sha2_ok" != "8" ]; then + echo "SHA2 FAIL - expected 8 OK - found $sha2_ok" + exit 1 + fi + echo "SHA2 OK ($sha2_ok matches)" + + - name: Load objects (stored procedures/functions) + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use < postgresql/objects.sql + + - name: Test stored procedures + run: | + SBDIR=$(ls -d ~/sandboxes/pg_sandbox_*) + $SBDIR/use -d employees -t -c "SELECT emp_name(10001);" + $SBDIR/use -d employees -t -c "SELECT emp_dept_name(10001);" + $SBDIR/use -d employees -t -c "SELECT current_manager('d001');" + $SBDIR/use -d employees -c "SELECT * FROM show_departments();" + $SBDIR/use -d employees -t -c "SELECT COUNT(*) FROM v_full_employees;" + $SBDIR/use -d employees -t -c "SELECT COUNT(*) FROM v_full_departments;" + + - name: Cleanup + if: always() + run: | + dbdeployer delete all --skip-confirm 2>/dev/null || true + pkill -9 -u "$USER" postgres 2>/dev/null || true diff --git a/README.md b/README.md index eaf21f7b..d08ebb65 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,64 @@ This repository was migrated from [Launchpad](https://launchpad.net/test-db). See usage in the [MySQL docs](https://dev.mysql.com/doc/employee/en/index.html) +[![CI MySQL](https://github.com/datacharmer/test_db/actions/workflows/ci-mysql.yml/badge.svg)](https://github.com/datacharmer/test_db/actions/workflows/ci-mysql.yml) +[![CI Percona](https://github.com/datacharmer/test_db/actions/workflows/ci-percona.yml/badge.svg)](https://github.com/datacharmer/test_db/actions/workflows/ci-percona.yml) +[![CI MariaDB](https://github.com/datacharmer/test_db/actions/workflows/ci-mariadb.yml/badge.svg)](https://github.com/datacharmer/test_db/actions/workflows/ci-mariadb.yml) +[![CI PostgreSQL](https://github.com/datacharmer/test_db/actions/workflows/ci-postgresql.yml/badge.svg)](https://github.com/datacharmer/test_db/actions/workflows/ci-postgresql.yml) + + +## Tested Versions + +The database requires MySQL 5.0+ or PostgreSQL 12+. The following versions are tested in CI +using [ProxySQL/dbdeployer](https://github.com/ProxySQL/dbdeployer) on a weekly schedule: + +| Vendor | Versions | +|--------|----------| +| MySQL | 5.6, 5.7, 8.0, 8.4, 9.0, 9.2, 9.5, 9.6 | +| Percona Server | 8.0, 8.4 | +| MariaDB | 10.11, 11.4, 12.1 | +| PostgreSQL | 16, 17 | + +### MySQL 9.x Notes + +Starting with MySQL 9.5, the `SOURCE` command requires the `--commands` flag on the client: + + mysql --commands < employees.sql + +Starting with MySQL 9.6, the `MD5()` and `SHA()` functions have been removed from the server. +The integrity test files `test_employees_md5.sql` and `test_employees_sha.sql` will not work on 9.6+. +Use `test_employees_sha2.sql` instead, which uses `SHA2(..., 256)` and is compatible with all versions: + + mysql -t < test_employees_sha2.sql + +The SHA-256 checksums are identical across all supported MySQL, Percona, MariaDB, and PostgreSQL versions. + + +## Tested Versions + +The database requires MySQL 5.0+ or compatible server. The following versions are tested in CI +using [ProxySQL/dbdeployer](https://github.com/ProxySQL/dbdeployer) on a weekly schedule: + +| Vendor | Versions | +|--------|----------| +| MySQL | 5.6, 5.7, 8.0, 8.4, 9.0, 9.2, 9.5, 9.6 | +| Percona Server | 8.0, 8.4 | +| MariaDB | 10.11, 11.4, 12.1 | + +### MySQL 9.x Notes + +Starting with MySQL 9.5, the `SOURCE` command requires the `--commands` flag on the client: + + mysql --commands < employees.sql + +Starting with MySQL 9.6, the `MD5()` and `SHA()` functions have been removed from the server. +The integrity test files `test_employees_md5.sql` and `test_employees_sha.sql` will not work on 9.6+. +Use `test_employees_sha2.sql` instead, which uses `SHA2(..., 256)` and is compatible with all versions: + + mysql -t < test_employees_sha2.sql + +The SHA-256 checksums are identical across all supported MySQL, Percona, and MariaDB versions. + ## Where it comes from @@ -23,12 +81,26 @@ The data was generated, and as such there are inconsistencies and subtle problems. Rather than removing them, we decided to leave the contents untouched, and use these issues as data cleaning exercises. +## Prerequisites + +You need a MySQL database server (5.0+) and run the commands below through a +user that has the following privileges: + + SELECT, INSERT, UPDATE, DELETE, + CREATE, DROP, RELOAD, REFERENCES, + INDEX, ALTER, SHOW DATABASES, + CREATE TEMPORARY TABLES, + LOCK TABLES, EXECUTE, CREATE VIEW ## Installation: 1. Download the repository 2. Change directory to the repository -3. run + +> [!IMPORTANT] +> If you are using MySQL client >= 9.x, the new default value of the [`--commands`](https://dev.mysql.com/doc/refman/9.6/en/mysql-command-options.html#option_mysql_commands) parameter is set to `FALSE`. Therefore, the `SOURCE` command won't work. You need to happen `--commands` to each `mysql` command line call. + +Then run mysql < employees.sql @@ -40,11 +112,11 @@ If you want to install with two large partitioned tables, run ## Testing the installation -After installing, you can run one of the following +After installing, you can run one of the following integrity tests: - mysql -t < test_employees_md5.sql - # OR - mysql -t < test_employees_sha.sql + mysql -t < test_employees_sha2.sql # SHA-256 (works on all versions including 9.6+) + mysql -t < test_employees_md5.sql # MD5 (MySQL 8.0–9.5 only) + mysql -t < test_employees_sha.sql # SHA-1 (MySQL 8.0–9.5 only) For example: @@ -86,9 +158,56 @@ For example: +--------------+---------------+-----------+ +## PostgreSQL Installation + +The database is also available for PostgreSQL 12+. The schema and data are identical +to the MySQL version. All files are in the `postgresql/` directory. + +### Differences from the MySQL version + +- **ENUM type**: MySQL `ENUM('M','F')` is replaced with `CHAR(1) CHECK (gender IN ('M','F'))` +- **Stored procedures**: MySQL's `delimiter //` syntax is replaced with PostgreSQL dollar-quoting (`$...$ LANGUAGE plpgsql`) +- **`show_departments()`**: Implemented as a function returning TABLE (use `SELECT * FROM show_departments();` instead of `CALL show_departments();`) +- **User variables**: MySQL's `@var := value` pattern is replaced with PL/pgSQL local variables +- **Integrity tests**: Use the same incremental hashing approach but via PL/pgSQL helper functions instead of MySQL user variables + +### Data integrity across databases + +The SHA-256 checksums are **identical** between MySQL and PostgreSQL. This is verified in CI: +the same expected values in `test_employees_sha2.sql` and `postgresql/test_employees_sha2.sql` +produce matching results on both databases. The MySQL version uses `SHA2(..., 256)` while +PostgreSQL uses `encode(digest(..., 'sha256'), 'hex')` from the `pgcrypto` extension. + +### Installation + +1. Download the repository +2. Install PostgreSQL (12+) +3. Run the loading script: + + cd postgresql + bash load_employees_db.sh + + To use a custom psql command (e.g., from a dbdeployer sandbox): + + PSQL=/path/to/psql bash load_employees_db.sh + +### Testing the PostgreSQL installation + + psql -d employees < postgresql/test_employees_sha2.sql # SHA-256 (recommended) + psql -d employees < postgresql/test_employees_md5.sql # MD5 + psql -d employees < postgresql/test_employees_sha.sql # SHA-1 (requires pgcrypto) + +### Optional: load stored procedures and functions + + psql -d employees < postgresql/objects.sql + +Available functions: `emp_name()`, `emp_dept_name()`, `emp_dept_id()`, `current_manager()`, +`show_departments()` (use `SELECT * FROM show_departments();`), `employees_help()`. + + ## DISCLAIMER -To the best of my knowledge, this data is fabricated, and +To the best of my knowledge, this data is fabricated and it does not correspond to real people. Any similarity to existing people is purely coincidental. diff --git a/postgresql/employees.sql b/postgresql/employees.sql new file mode 100644 index 00000000..2bd18864 --- /dev/null +++ b/postgresql/employees.sql @@ -0,0 +1,91 @@ +-- Sample employee database +-- PostgreSQL version +-- +-- Original data created by Fusheng Wang and Carlo Zaniolo +-- Current schema by Giuseppe Maxia +-- PostgreSQL port for test_db +-- +-- This work is licensed under the +-- Creative Commons Attribution-Share Alike 3.0 Unported License. + +DROP DATABASE IF EXISTS employees; +CREATE DATABASE employees; +\connect employees + +SELECT 'CREATING DATABASE STRUCTURE' AS info; + +DROP TABLE IF EXISTS dept_emp, + dept_manager, + titles, + salaries, + employees, + departments CASCADE; + +CREATE TABLE employees ( + emp_no INT NOT NULL, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender CHAR(1) NOT NULL CHECK (gender IN ('M','F')), + hire_date DATE NOT NULL, + PRIMARY KEY (emp_no) +); + +CREATE TABLE departments ( + dept_no CHAR(4) NOT NULL, + dept_name VARCHAR(40) NOT NULL, + PRIMARY KEY (dept_no), + UNIQUE (dept_name) +); + +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,dept_no) +); + +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,dept_no) +); + +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,title, from_date) +); + +CREATE TABLE salaries ( + emp_no INT NOT NULL, + salary INT NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no, from_date) +); + +CREATE OR REPLACE VIEW dept_emp_latest_date AS + SELECT emp_no, MAX(from_date) AS from_date, MAX(to_date) AS to_date + FROM dept_emp + GROUP BY emp_no; + +-- shows only the current department for each employee +CREATE OR REPLACE VIEW current_dept_emp AS + SELECT l.emp_no, dept_no, l.from_date, l.to_date + FROM dept_emp d + INNER JOIN dept_emp_latest_date l + ON d.emp_no=l.emp_no AND d.from_date=l.from_date AND l.to_date = d.to_date; + +SELECT 'SCHEMA CREATED' AS info; diff --git a/postgresql/load_employees_db.sh b/postgresql/load_employees_db.sh new file mode 100755 index 00000000..22f2e025 --- /dev/null +++ b/postgresql/load_employees_db.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +DUMP_DIR="$(dirname "$SCRIPT_DIR")" +PSQL="${PSQL:-psql}" + +echo "Creating database schema..." +"$PSQL" -f "$SCRIPT_DIR/employees.sql" + +echo "LOADING departments" +sed 's/`//g' "$DUMP_DIR/load_departments.dump" | "$PSQL" -d employees -q + +echo "LOADING employees" +sed 's/`//g' "$DUMP_DIR/load_employees.dump" | "$PSQL" -d employees -q + +echo "LOADING dept_emp" +sed 's/`//g' "$DUMP_DIR/load_dept_emp.dump" | "$PSQL" -d employees -q + +echo "LOADING dept_manager" +sed 's/`//g' "$DUMP_DIR/load_dept_manager.dump" | "$PSQL" -d employees -q + +echo "LOADING titles" +sed 's/`//g' "$DUMP_DIR/load_titles.dump" | "$PSQL" -d employees -q + +echo "LOADING salaries" +sed 's/`//g' "$DUMP_DIR/load_salaries1.dump" | "$PSQL" -d employees -q +sed 's/`//g' "$DUMP_DIR/load_salaries2.dump" | "$PSQL" -d employees -q +sed 's/`//g' "$DUMP_DIR/load_salaries3.dump" | "$PSQL" -d employees -q + +echo "Done loading employees database." diff --git a/postgresql/objects.sql b/postgresql/objects.sql new file mode 100644 index 00000000..db07ed02 --- /dev/null +++ b/postgresql/objects.sql @@ -0,0 +1,217 @@ +-- Additional objects for employees database +-- PostgreSQL version (PL/pgSQL) + +\connect employees + +DROP FUNCTION IF EXISTS emp_dept_id(INT); +DROP FUNCTION IF EXISTS emp_dept_name(INT); +DROP FUNCTION IF EXISTS emp_name(INT); +DROP FUNCTION IF EXISTS current_manager(CHAR(4)); +DROP FUNCTION IF EXISTS employees_usage(); +DROP FUNCTION IF EXISTS show_departments(); +DROP PROCEDURE IF EXISTS employees_help(); + +-- +-- returns the department id of a given employee +-- +CREATE OR REPLACE FUNCTION emp_dept_id(employee_id INT) +RETURNS CHAR(4) +LANGUAGE plpgsql +STABLE +AS $$ +DECLARE + max_date DATE; + result CHAR(4); +BEGIN + SELECT MAX(from_date) INTO max_date + FROM dept_emp + WHERE emp_no = employee_id; + + SELECT dept_no INTO result + FROM dept_emp + WHERE emp_no = employee_id AND from_date = max_date + LIMIT 1; + + RETURN result; +END; +$$; + +-- +-- returns the department name of a given employee +-- +CREATE OR REPLACE FUNCTION emp_dept_name(employee_id INT) +RETURNS VARCHAR(40) +LANGUAGE plpgsql +STABLE +AS $$ +BEGIN + RETURN ( + SELECT dept_name + FROM departments + WHERE dept_no = emp_dept_id(employee_id) + ); +END; +$$; + +-- +-- returns the employee name of a given employee id +-- +CREATE OR REPLACE FUNCTION emp_name(employee_id INT) +RETURNS VARCHAR(32) +LANGUAGE plpgsql +STABLE +AS $$ +BEGIN + RETURN ( + SELECT concat(first_name, ' ', last_name) AS name + FROM employees + WHERE emp_no = employee_id + ); +END; +$$; + +-- +-- returns the manager of a department +-- choosing the most recent one +-- from the manager list +-- +CREATE OR REPLACE FUNCTION current_manager(dept_id CHAR(4)) +RETURNS VARCHAR(32) +LANGUAGE plpgsql +STABLE +AS $$ +DECLARE + max_date DATE; + result VARCHAR(32); +BEGIN + SELECT MAX(from_date) INTO max_date + FROM dept_manager + WHERE dept_no = dept_id; + + SELECT emp_name(emp_no) INTO result + FROM dept_manager + WHERE dept_no = dept_id AND from_date = max_date + LIMIT 1; + + RETURN result; +END; +$$; + +-- +-- selects the employee records with the +-- latest department +-- +CREATE OR REPLACE VIEW v_full_employees +AS +SELECT + emp_no, + first_name, last_name, + birth_date, gender, + hire_date, + emp_dept_name(emp_no) AS department +FROM + employees; + +-- +-- selects the department list with manager names +-- +CREATE OR REPLACE VIEW v_full_departments +AS +SELECT + dept_no, dept_name, current_manager(dept_no) AS manager +FROM + departments; + +-- +-- shows the departments with the number of employees +-- per department +-- +CREATE OR REPLACE FUNCTION show_departments() +RETURNS TABLE(dept_no CHAR(4), dept_name VARCHAR(40), manager VARCHAR(32), count BIGINT) +LANGUAGE plpgsql +AS $$ +BEGIN + DROP TABLE IF EXISTS department_max_date; + DROP TABLE IF EXISTS department_people; + + CREATE TEMPORARY TABLE department_max_date + ( + emp_no INT NOT NULL PRIMARY KEY, + dept_from_date DATE NOT NULL, + dept_to_date DATE NOT NULL + ); + + INSERT INTO department_max_date + SELECT + de.emp_no, MAX(de.from_date), MAX(de.to_date) + FROM + dept_emp de + GROUP BY + de.emp_no; + + CREATE TEMPORARY TABLE department_people + ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + PRIMARY KEY (emp_no, dept_no) + ); + + INSERT INTO department_people + SELECT dmd.emp_no, de.dept_no + FROM + department_max_date dmd + INNER JOIN dept_emp de + ON dmd.dept_from_date=de.from_date + AND dmd.dept_to_date=de.to_date + AND dmd.emp_no=de.emp_no; + + RETURN QUERY + SELECT + v.dept_no, v.dept_name, v.manager::VARCHAR(32), COUNT(*)::BIGINT + FROM v_full_departments v + INNER JOIN department_people dp ON v.dept_no = dp.dept_no + GROUP BY v.dept_no, v.dept_name, v.manager; + + DROP TABLE department_max_date; + DROP TABLE department_people; +END; +$$; + +CREATE OR REPLACE FUNCTION employees_usage() +RETURNS TEXT +LANGUAGE plpgsql +IMMUTABLE +AS $$ +BEGIN + RETURN ' + == USAGE == + ==================== + + FUNCTION show_departments() + + shows the departments with the manager and + number of employees per department + (returns TABLE - use: SELECT * FROM show_departments();) + + FUNCTION current_manager (dept_id) + + Shows who is the manager of a given department + + FUNCTION emp_name (emp_id) + + Shows name and surname of a given employee + + FUNCTION emp_dept_id (emp_id) + + Shows the current department of given employee +'; +END; +$$; + +CREATE OR REPLACE PROCEDURE employees_help() +LANGUAGE plpgsql +AS $$ +BEGIN + SELECT employees_usage() AS info; +END; +$$; diff --git a/postgresql/test_employees_md5.sql b/postgresql/test_employees_md5.sql new file mode 100644 index 00000000..aa7aea10 --- /dev/null +++ b/postgresql/test_employees_md5.sql @@ -0,0 +1,105 @@ +-- Test employees database integrity using MD5 checksums +-- PostgreSQL version + +\connect employees + +SELECT 'TESTING INSTALLATION' AS info; + +DROP TABLE IF EXISTS expected_values, found_values; +CREATE TABLE expected_values ( + table_name VARCHAR(30) NOT NULL PRIMARY KEY, + recs INT NOT NULL, + crc_sha VARCHAR(100) NOT NULL, + crc_md5 VARCHAR(100) NOT NULL +); + + +CREATE TABLE found_values (LIKE expected_values); + +INSERT INTO expected_values VALUES +('employees', 300024,'4d4aa689914d8fd41db7e45c2168e7dcb9697359', + '4ec56ab5ba37218d187cf6ab09ce1aa1'), +('departments', 9,'4b315afa0e35ca6649df897b958345bcb3d2b764', + 'd1af5e170d2d1591d776d5638d71fc5f'), +('dept_manager', 24,'9687a7d6f93ca8847388a42a6d8d93982a841c6c', + '8720e2f0853ac9096b689c14664f847e'), +('dept_emp', 331603, 'd95ab9fe07df0865f592574b3b33b9c741d9fd1b', + 'ccf6fe516f990bdaa49713fc478701b7'), +('titles', 443308,'d12d5f746b88f07e69b9e36675b6067abb01b60e', + 'bfa016c472df68e70a03facafa1bc0a8'), +('salaries', 2844047,'b5a1785c27d75e33a4173aaa22ccf41ebd7d4a9f', + 'fd220654e95aea1b169624ffe3fca934'); +SELECT table_name, recs AS expected_records, crc_md5 AS expected_crc FROM expected_values; + +-- Helper function: computes incremental MD5 over rows returned by query. +-- The query must return a single column named "row_text". +-- This replicates MySQL's @crc := MD5(CONCAT_WS('#', @crc, col1, col2, ...)) +-- by using crc := md5(crc || '#' || row_text) for each row. +CREATE OR REPLACE FUNCTION compute_table_md5(p_query TEXT) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE + crc TEXT := ''; + r RECORD; +BEGIN + FOR r IN EXECUTE p_query LOOP + crc := md5(crc || '#' || r.row_text); + END LOOP; + RETURN crc; +END; +$$; + +-- Compute checksums for each table +INSERT INTO found_values VALUES +('employees', + (SELECT COUNT(*) FROM employees), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, birth_date, first_name, last_name, gender, hire_date) AS row_text FROM employees ORDER BY emp_no$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, birth_date, first_name, last_name, gender, hire_date) AS row_text FROM employees ORDER BY emp_no$$))), + +('departments', + (SELECT COUNT(*) FROM departments), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, dept_name) AS row_text FROM departments ORDER BY dept_no$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, dept_name) AS row_text FROM departments ORDER BY dept_no$$))), + +('dept_manager', + (SELECT COUNT(*) FROM dept_manager), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_manager ORDER BY dept_no, emp_no$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_manager ORDER BY dept_no, emp_no$$))), + +('dept_emp', + (SELECT COUNT(*) FROM dept_emp), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_emp ORDER BY dept_no, emp_no$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_emp ORDER BY dept_no, emp_no$$))), + +('titles', + (SELECT COUNT(*) FROM titles), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, title, from_date, to_date) AS row_text FROM titles ORDER BY emp_no, title, from_date$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, title, from_date, to_date) AS row_text FROM titles ORDER BY emp_no, title, from_date$$))), + +('salaries', + (SELECT COUNT(*) FROM salaries), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, salary, from_date, to_date) AS row_text FROM salaries ORDER BY emp_no, from_date, to_date$$)), + (SELECT compute_table_md5($$SELECT CONCAT_WS('#', emp_no, salary, from_date, to_date) AS row_text FROM salaries ORDER BY emp_no, from_date, to_date$$))); + +SELECT table_name, recs AS found_records, crc_md5 AS found_crc FROM found_values; + +SELECT + e.table_name, + CASE WHEN e.recs=f.recs THEN 'OK' ELSE 'not ok' END AS records_match, + CASE WHEN e.crc_md5=f.crc_md5 THEN 'ok' ELSE 'not ok' END AS crc_match +FROM + expected_values e INNER JOIN found_values f USING (table_name); + +SELECT 'CRC' AS summary, + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.crc_md5 != e.crc_md5 + ) THEN 'OK' ELSE 'FAIL' END AS result +UNION ALL +SELECT 'count', + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.recs != e.recs + ) THEN 'OK' ELSE 'FAIL' END; + +DROP FUNCTION compute_table_md5(TEXT); +DROP TABLE expected_values, found_values; diff --git a/postgresql/test_employees_sha.sql b/postgresql/test_employees_sha.sql new file mode 100644 index 00000000..0e7e7607 --- /dev/null +++ b/postgresql/test_employees_sha.sql @@ -0,0 +1,106 @@ +-- Test employees database integrity using SHA1 checksums +-- PostgreSQL version (requires pgcrypto extension) + +\connect employees + +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +SELECT 'TESTING INSTALLATION' AS info; + +DROP TABLE IF EXISTS expected_values, found_values; +CREATE TABLE expected_values ( + table_name VARCHAR(30) NOT NULL PRIMARY key, + recs INT NOT NULL, + crc_sha VARCHAR(100) NOT NULL, + crc_md5 VARCHAR(100) NOT NULL +); + + +CREATE TABLE found_values (LIKE expected_values); + +INSERT INTO expected_values VALUES +('employees', 300024,'4d4aa689914d8fd41db7e45c2168e7dcb9697359', + '4ec56ab5ba37218d187cf6ab09ce1aa1'), +('departments', 9,'4b315afa0e35ca6649df897b958345bcb3d2b764', + 'd1af5e170d2d1591d776d5638d71fc5f'), +('dept_manager', 24,'9687a7d6f93ca8847388a42a6d8d93982a841c6c', + '8720e2f0853ac9096b689c14664f847e'), +('dept_emp', 331603, 'd95ab9fe07df0865f592574b3b33b9c741d9fd1b', + 'ccf6fe516f990bdaa49713fc478701b7'), +('titles', 443308,'d12d5f746b88f07e69b9e36675b6067abb01b60e', + 'bfa016c472df68e70a03facafa1bc0a8'), +('salaries', 2844047,'b5a1785c27d75e33a4173aaa22ccf41ebd7d4a9f', + 'fd220654e95aea1b169624ffe3fca934'); +SELECT table_name, recs AS expected_records, crc_sha AS expected_crc FROM expected_values; + +-- Helper function: computes incremental SHA1 over rows returned by query. +-- The query must return a single column named "row_text". +-- Uses pgcrypto's digest() for SHA1 (MySQL's sha() equivalent). +CREATE OR REPLACE FUNCTION compute_table_sha1(p_query TEXT) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE + crc TEXT := ''; + r RECORD; +BEGIN + FOR r IN EXECUTE p_query LOOP + crc := encode(digest(crc || '#' || r.row_text, 'sha1'), 'hex'); + END LOOP; + RETURN crc; +END; +$$; + +-- Compute checksums for each table +INSERT INTO found_values VALUES +('employees', + (SELECT COUNT(*) FROM employees), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, birth_date, first_name, last_name, gender, hire_date) AS row_text FROM employees ORDER BY emp_no$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, birth_date, first_name, last_name, gender, hire_date) AS row_text FROM employees ORDER BY emp_no$$))), + +('departments', + (SELECT COUNT(*) FROM departments), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, dept_name) AS row_text FROM departments ORDER BY dept_no$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, dept_name) AS row_text FROM departments ORDER BY dept_no$$))), + +('dept_manager', + (SELECT COUNT(*) FROM dept_manager), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_manager ORDER BY dept_no, emp_no$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_manager ORDER BY dept_no, emp_no$$))), + +('dept_emp', + (SELECT COUNT(*) FROM dept_emp), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_emp ORDER BY dept_no, emp_no$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_emp ORDER BY dept_no, emp_no$$))), + +('titles', + (SELECT COUNT(*) FROM titles), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, title, from_date, to_date) AS row_text FROM titles ORDER BY emp_no, title, from_date$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, title, from_date, to_date) AS row_text FROM titles ORDER BY emp_no, title, from_date$$))), + +('salaries', + (SELECT COUNT(*) FROM salaries), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, salary, from_date, to_date) AS row_text FROM salaries ORDER BY emp_no, from_date, to_date$$)), + (SELECT compute_table_sha1($$SELECT CONCAT_WS('#', emp_no, salary, from_date, to_date) AS row_text FROM salaries ORDER BY emp_no, from_date, to_date$$))); + +SELECT table_name, recs AS found_records, crc_sha AS found_crc FROM found_values; + +SELECT + e.table_name, + CASE WHEN e.recs=f.recs THEN 'OK' ELSE 'not ok' END AS records_match, + CASE WHEN e.crc_sha=f.crc_sha THEN 'ok' ELSE 'not ok' END AS crc_match +FROM + expected_values e INNER JOIN found_values f USING (table_name); + +SELECT 'CRC' AS summary, + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.crc_sha != e.crc_sha + ) THEN 'OK' ELSE 'FAIL' END AS result +UNION ALL +SELECT 'count', + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.recs != e.recs + ) THEN 'OK' ELSE 'FAIL' END; + +DROP FUNCTION compute_table_sha1(TEXT); +DROP TABLE expected_values, found_values; diff --git a/postgresql/test_employees_sha2.sql b/postgresql/test_employees_sha2.sql new file mode 100644 index 00000000..4a8ea1b2 --- /dev/null +++ b/postgresql/test_employees_sha2.sql @@ -0,0 +1,94 @@ +-- Test employees database integrity using SHA-256 checksums +-- PostgreSQL version using pgcrypto extension + +\connect employees + +SELECT 'TESTING INSTALLATION' AS info; + +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +DROP TABLE IF EXISTS expected_values, found_values; +CREATE TABLE expected_values ( + table_name VARCHAR(30) NOT NULL PRIMARY KEY, + recs INT NOT NULL, + crc_sha2 VARCHAR(100) NOT NULL +); + +CREATE TABLE found_values (LIKE expected_values); + +-- Expected SHA-256 checksums (same values as MySQL's SHA2(..., 256)) +INSERT INTO expected_values VALUES +('employees', 300024, '21f5d003842f24853e251d3d5116798bafe257ec3d1bb448b5365b68deaabbf4'), +('departments', 9, '377c5d727383a32633e2973f8e3411beffe29e2f4cc297c586fa6b24aa7df9ba'), +('dept_manager', 24, '3a4e69723deec413a7d8a4f5ce55013830303fa617b6380ed2b0fd2d48b1c768'), +('dept_emp', 331603, '34548ee9989dd4d5e065168b43249c8d3eb48bfbbfb3f2fc1cf01be6658f6a75'), +('titles', 443308, 'a9e940ef9ba1029a8f0356fdbe495430bedc59eec5ceb4f71e0cc35ddcbf9980'), +('salaries', 2844047, '4e99e691a9ea98fefc0b4fec8ca4e758baeefba2967bd8d6474810a9a5f6e729'); +SELECT table_name, recs AS expected_records, crc_sha2 AS expected_crc FROM expected_values; + +-- Helper function: computes incremental SHA-256 over rows returned by query. +-- The query must return a single column named "row_text". +-- This replicates MySQL's @crc := SHA2(CONCAT_WS('#', @crc, ...), 256) +-- by using crc := encode(digest(crc || '#' || row_text, 'sha256'), 'hex') +CREATE OR REPLACE FUNCTION compute_table_sha256(p_query TEXT) +RETURNS TEXT +LANGUAGE plpgsql +AS $$ +DECLARE + crc TEXT := ''; + r RECORD; +BEGIN + FOR r IN EXECUTE p_query LOOP + crc := encode(digest(crc || '#' || r.row_text, 'sha256'), 'hex'); + END LOOP; + RETURN crc; +END; +$$; + +-- Compute checksums for each table +INSERT INTO found_values VALUES +('employees', + (SELECT COUNT(*) FROM employees), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', emp_no, birth_date, first_name, last_name, gender, hire_date) AS row_text FROM employees ORDER BY emp_no$$))), + +('departments', + (SELECT COUNT(*) FROM departments), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', dept_no, dept_name) AS row_text FROM departments ORDER BY dept_no$$))), + +('dept_manager', + (SELECT COUNT(*) FROM dept_manager), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_manager ORDER BY dept_no, emp_no$$))), + +('dept_emp', + (SELECT COUNT(*) FROM dept_emp), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', dept_no, emp_no, from_date, to_date) AS row_text FROM dept_emp ORDER BY dept_no, emp_no$$))), + +('titles', + (SELECT COUNT(*) FROM titles), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', emp_no, title, from_date, to_date) AS row_text FROM titles ORDER BY emp_no, title, from_date$$))), + +('salaries', + (SELECT COUNT(*) FROM salaries), + (SELECT compute_table_sha256($$SELECT CONCAT_WS('#', emp_no, salary, from_date, to_date) AS row_text FROM salaries ORDER BY emp_no, from_date, to_date$$))); + +SELECT table_name, recs AS found_records, crc_sha2 AS found_crc FROM found_values; + +SELECT + e.table_name, + CASE WHEN e.recs=f.recs THEN 'OK' ELSE 'not ok' END AS records_match, + CASE WHEN e.crc_sha2=f.crc_sha2 THEN 'ok' ELSE 'not ok' END AS crc_match +FROM + expected_values e INNER JOIN found_values f USING (table_name); + +SELECT 'CRC' AS summary, + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.crc_sha2 != e.crc_sha2 + ) THEN 'OK' ELSE 'FAIL' END AS result +UNION ALL +SELECT 'count', + CASE WHEN NOT EXISTS ( + SELECT 1 FROM expected_values e JOIN found_values f ON e.table_name=f.table_name WHERE f.recs != e.recs + ) THEN 'OK' ELSE 'FAIL' END; + +DROP FUNCTION compute_table_sha256(TEXT); +DROP TABLE expected_values, found_values; diff --git a/test_employees_md5.sql b/test_employees_md5.sql index 04c72c26..bf0e2338 100644 --- a/test_employees_md5.sql +++ b/test_employees_md5.sql @@ -32,10 +32,8 @@ CREATE TABLE expected_values ( recs int not null, crc_sha varchar(100) not null, crc_md5 varchar(100) not null -) ENGINE=MyISAM; +); --- In MySQL 5.0, the creation and update time for memory tables is not recorded -/*!50130 ALTER TABLE expected_values engine=memory */; CREATE TABLE found_values LIKE expected_values; @@ -55,7 +53,7 @@ INSERT INTO `expected_values` VALUES SELECT table_name, recs AS expected_records, crc_md5 AS expected_crc FROM expected_values; DROP TABLE IF EXISTS tchecksum; -CREATE TABLE tchecksum (chk char(100)) ENGINE=blackhole; +CREATE TABLE tchecksum (chk char(100)); SET @crc= ''; diff --git a/test_employees_sha.sql b/test_employees_sha.sql index 77927a7a..793ace98 100644 --- a/test_employees_sha.sql +++ b/test_employees_sha.sql @@ -32,10 +32,8 @@ CREATE TABLE expected_values ( recs int not null, crc_sha varchar(100) not null, crc_md5 varchar(100) not null -) ENGINE=MyISAM; +); --- In MySQL 5.0, the creation and update time for memory tables is not recorded -/*!50130 ALTER TABLE expected_values engine=memory */; CREATE TABLE found_values LIKE expected_values; @@ -55,7 +53,7 @@ INSERT INTO `expected_values` VALUES SELECT table_name, recs AS expected_records, crc_sha AS expected_crc FROM expected_values; DROP TABLE IF EXISTS tchecksum; -CREATE TABLE tchecksum (chk char(100)) ENGINE=blackhole; +CREATE TABLE tchecksum (chk char(100)); SET @crc= ''; diff --git a/test_employees_sha2.sql b/test_employees_sha2.sql new file mode 100644 index 00000000..7d23e1b6 --- /dev/null +++ b/test_employees_sha2.sql @@ -0,0 +1,87 @@ +-- Test employees database integrity using SHA-256 checksums +-- Uses SHA2() which is available on all MySQL versions (8.0+) +-- This test works on MySQL 9.6+ where md5() and sha() have been removed + +USE employees; + +SELECT 'TESTING INSTALLATION' as 'INFO'; + +DROP TABLE IF EXISTS expected_values, found_values; +CREATE TABLE expected_values ( + table_name varchar(30) not null primary key, + recs int not null, + crc_sha2 varchar(100) not null +); + + +CREATE TABLE found_values (LIKE expected_values); + +-- Expected SHA-256 checksums (computed from the canonical data set) +INSERT INTO `expected_values` VALUES +('employees', 300024, '21f5d003842f24853e251d3d5116798bafe257ec3d1bb448b5365b68deaabbf4'), +('departments', 9, '377c5d727383a32633e2973f8e3411beffe29e2f4cc297c586fa6b24aa7df9ba'), +('dept_manager', 24, '3a4e69723deec413a7d8a4f5ce55013830303fa617b6380ed2b0fd2d48b1c768'), +('dept_emp', 331603, '34548ee9989dd4d5e065168b43249c8d3eb48bfbbfb3f2fc1cf01be6658f6a75'), +('titles', 443308, 'a9e940ef9ba1029a8f0356fdbe495430bedc59eec5ceb4f71e0cc35ddcbf9980'), +('salaries', 2844047, '4e99e691a9ea98fefc0b4fec8ca4e758baeefba2967bd8d6474810a9a5f6e729'); +SELECT table_name, recs AS expected_records, crc_sha2 AS expected_crc FROM expected_values; + +DROP TABLE IF EXISTS tchecksum; +CREATE TABLE tchecksum (chk char(100)); + +SET @crc= ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, + emp_no,birth_date,first_name,last_name,gender,hire_date), 256) + FROM employees ORDER BY emp_no; +INSERT INTO found_values VALUES ('employees', (SELECT COUNT(*) FROM employees), @crc); + +SET @crc = ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, dept_no,dept_name), 256) + FROM departments ORDER BY dept_no; +INSERT INTO found_values VALUES ('departments', (SELECT COUNT(*) FROM departments), @crc); + +SET @crc = ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, dept_no,emp_no, from_date,to_date), 256) + FROM dept_manager ORDER BY dept_no,emp_no; +INSERT INTO found_values VALUES ('dept_manager', (SELECT COUNT(*) FROM dept_manager), @crc); + +SET @crc = ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, dept_no,emp_no, from_date,to_date), 256) + FROM dept_emp ORDER BY dept_no,emp_no; +INSERT INTO found_values VALUES ('dept_emp', (SELECT COUNT(*) FROM dept_emp), @crc); + +SET @crc = ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, emp_no, title, from_date,to_date), 256) + FROM titles ORDER BY emp_no,title, from_date; +INSERT INTO found_values VALUES ('titles', (SELECT COUNT(*) FROM titles), @crc); + +SET @crc = ''; +INSERT INTO tchecksum + SELECT @crc := SHA2(CONCAT_WS('#',@crc, emp_no, salary, from_date,to_date), 256) + FROM salaries ORDER BY emp_no,from_date,to_date; +INSERT INTO found_values VALUES ('salaries', (SELECT COUNT(*) FROM salaries), @crc); + +DROP TABLE tchecksum; + +SELECT table_name, recs AS found_records, crc_sha2 AS found_crc FROM found_values; + +SELECT + e.table_name, + IF(e.recs=f.recs,'OK', 'not ok') AS records_match, + IF(e.crc_sha2=f.crc_sha2,'ok','not ok') AS crc_match +FROM + expected_values e INNER JOIN found_values f USING (table_name); + +SET @crc_fail=(SELECT COUNT(*) FROM expected_values e INNER JOIN found_values f ON (e.table_name=f.table_name) WHERE f.crc_sha2 != e.crc_sha2); +SET @count_fail=(SELECT COUNT(*) FROM expected_values e INNER JOIN found_values f ON (e.table_name=f.table_name) WHERE f.recs != e.recs); + +SELECT 'CRC' AS summary, IF(@crc_fail = 0, "OK", "FAIL") AS `result` +UNION ALL +SELECT 'count', IF(@count_fail = 0, "OK", "FAIL") AS `count`; + +DROP TABLE expected_values, found_values; diff --git a/test_versions.sh b/test_versions.sh new file mode 100755 index 00000000..12235f3a --- /dev/null +++ b/test_versions.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# Tests that the employees database can work with all versions of MySQL +# +# Requires dbdeployer to be installed and configured (https://www.dbdeployer.com) + +function found_in_path { + name=$1 + for dir in $(echo $PATH | tr ':' ' ') + do + if [ -x $dir/$name ] + then + echo $dir/$name + return + fi + done +} + + +function check_exit_code { + exit_code=$? + if [ "$exit_code" != "0" ] + then + echo "Execution error" + exit $exit_code + fi +} + +dbdeployer=$(found_in_path dbdeployer) + +if [ -z "$dbdeployer" ] +then + echo "dbdeployer not found in \$PATH" + exit 1 +fi + +[ -z "$SANDBOX_HOME" ] && SANDBOX_HOME=$HOME/sandboxes + +for short_version in 5.0 5.1 5.5 5.6 5.7 8.0 +do + version=$($dbdeployer info version $short_version) + if [ -z "$version" ] + then + continue + fi + echo "### -------------------" + echo "### $version" + echo "### -------------------" + ver_name=$(echo $version | tr '.' '_') + + $dbdeployer deploy single $version + check_exit_code + + msb=$SANDBOX_HOME/msb_$ver_name + + if [ ! -d $msb ] + then + echo "'$msb' not found or not a directory - Halting test" + exit 1 + fi + + $msb/use < employees.sql + check_exit_code + echo "Testing MD5" + $msb/use -t < test_employees_md5.sql > /tmp/test_md5.txt + md5_ok=$(grep -iw ok /tmp/test_md5.txt | wc -l | tr -d ' \t') + + if [ "$md5_ok" == "8" ] + then + echo "MD5 OK - $md5_ok" + else + echo "MD5 FAIL - expected 8 - found $md5_ok" + cat /tmp/test_md5.txt + exit 1 + fi + + echo "Testing SHA" + $msb/use -t < test_employees_sha.sql > /tmp/test_sha.txt + sha_ok=$(grep -iw ok /tmp/test_sha.txt | wc -l | tr -d ' \t') + if [ "$sha_ok" == "8" ] + then + echo "SHA OK - $sha_ok" + else + echo "SHA FAIL - expected 8 - found $sha_ok" + cat /tmp/test_sha.txt + exit 1 + fi + + $dbdeployer delete msb_$ver_name + check_exit_code + +done +