diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7699388b..cfe014bc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,6 +40,7 @@ jobs: - 5.34.0 - 5.36.0 - 5.38.0 + - 5.40.0 - system dbi-version: - 1.627 @@ -104,6 +105,7 @@ jobs: - mysql-8.0.22 - mysql-8.0.33 - mysql-8.0.34 + - mysql-8.0.35 - mariadb-5.5.40 - mariadb-5.5.44 - mariadb-5.5.47 @@ -255,6 +257,32 @@ jobs: client-version: mariadbconc-3.0.2 - server-version: mysql-4.1.22 client-version: mariadbconc-3.0.6 + # MySQL client versions 8.0.4 - 8.4.0 and 8.1.0 have bug which + # prevents connection to MySQL server versions prior 5.5.7. + # Moreover, old server versions don't send Warning Count and Server + # Status fields in the response to prepare, but the client doesn't + # check the packet length and dereferences the memory out of the + # buffer. + #- server-version: mysql-4.1.22 + # client-version: mysql-8.0.3-rc + - server-version: mysql-5.1.72 + client-version: mysql-8.0.3-rc + # This combination stops responding during the first test and + # times out in GHA. We haven't found the cause. + - server-version: system-pic + client-version: system-pic + - server-version: mysql-5.7.43 + client-version: system-pic + - server-version: mysql-8.0.35 + client-version: system-pic + - server-version: mariadb-5.5.40 + client-version: system-pic + - server-version: mariadb-10.0.38 + client-version: system-pic + - server-version: mariadb-10.2.44 + client-version: system-pic + - server-version: mariadb-10.4.2 + client-version: system-pic - server-version: none client-version: system - perl-version: 5.12.0 @@ -286,16 +314,18 @@ jobs: with: path: ~/.cpan/sources key: cache-cpan - - name: Install client dependences (1) - if: ${{ matrix.client-version != 'system' && ( matrix.server-version != 'system' || matrix.client-version != 'same-as-server' ) }} + - name: Update apt run: | - sudo apt install libgnutls28-dev libncurses5 libncursesw5 libstdc++5 - - name: Install client dependences (2) + sudo apt update + - name: Uninstall system MySQL/MariaDB + run: | + sudo apt purge `{ dpkg --get-selections '*mysql*'; dpkg --get-selections '*mariadb*'; } | sed 's/[:\t].*//'` + - name: Install client dependencies if: ${{ matrix.client-version != 'system' && ( matrix.server-version != 'system' || matrix.client-version != 'same-as-server' ) }} run: | wget --progress=bar:force http://security.ubuntu.com/ubuntu/pool/main/o/openssl1.0/libssl1.0.0_1.0.2n-1ubuntu5_amd64.deb -O /tmp/libssl1.0.0_1.0.2n-1ubuntu5_amd64.deb wget --progress=bar:force http://security.ubuntu.com/ubuntu/pool/universe/j/jemalloc/libjemalloc1_3.6.0-11_amd64.deb -O /tmp/libjemalloc1_3.6.0-11_amd64.deb - sudo dpkg -i /tmp/libssl1.0.0_1.0.2n-1ubuntu5_amd64.deb /tmp/libjemalloc1_3.6.0-11_amd64.deb + sudo apt install libgnutls28-dev libncurses5 libncursesw5 libstdc++5 /tmp/libssl1.0.0_1.0.2n-1ubuntu5_amd64.deb /tmp/libjemalloc1_3.6.0-11_amd64.deb - name: Install MariaDB client system if: ${{ matrix.client-version == 'system' || ( matrix.server-version == 'system' && matrix.client-version == 'same-as-server' ) }} run: | @@ -306,6 +336,21 @@ jobs: sudo apt install mariadb-server sudo systemctl start mariadb.service sudo mariadb -e "GRANT ALL PRIVILEGES ON test.* TO 'test'@'localhost' IDENTIFIED BY 'test'" + - name: Install MySQL embedded PIC library + if: ${{ matrix.client-version == 'system-pic' || matrix.server-version == 'system-pic' }} + run: | + wget --progress=bar:force http://archive.ubuntu.com/ubuntu/pool/universe/m/mysql-5.5/libmysqld-pic_5.5.62-0ubuntu0.14.04.1_amd64.deb -O /tmp/libmysqld-pic_5.5.62-0ubuntu0.14.04.1_amd64.deb + wget --progress=bar:force http://archive.ubuntu.com/ubuntu/pool/main/m/mysql-5.5/libmysqlclient-dev_5.5.62-0ubuntu0.14.04.1_amd64.deb -O /tmp/libmysqlclient-dev_5.5.62-0ubuntu0.14.04.1_amd64.deb + wget --progress=bar:force http://archive.ubuntu.com/ubuntu/pool/main/m/mysql-5.5/libmysqlclient18_5.5.62-0ubuntu0.14.04.1_amd64.deb -O /tmp/libmysqlclient18_5.5.62-0ubuntu0.14.04.1_amd64.deb + wget --progress=bar:force http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/multiarch-support_2.27-3ubuntu1.6_amd64.deb -O /tmp/multiarch-support_2.27-3ubuntu1.6_amd64.deb + sudo apt install libaio-dev libwrap0-dev + sudo apt install /tmp/libmysqld-pic_5.5.62-0ubuntu0.14.04.1_amd64.deb /tmp/libmysqlclient-dev_5.5.62-0ubuntu0.14.04.1_amd64.deb /tmp/libmysqlclient18_5.5.62-0ubuntu0.14.04.1_amd64.deb /tmp/multiarch-support_2.27-3ubuntu1.6_amd64.deb + echo 'extern int sched_yield(void); int pthread_yield(void) { return sched_yield(); }' > "$HOME/libpthread_yield.c" + gcc -O2 -o "$HOME/libpthread_yield.o" -c "$HOME/libpthread_yield.c" + rm -f "$HOME/libpthread_yield.a" + ar rcs "$HOME/libpthread_yield.a" "$HOME/libpthread_yield.o" + sudo cp "$HOME/libpthread_yield.a" /usr/lib/mysql/ + - name: Install Perl system if: ${{ matrix.perl-version == 'system' }} run: | @@ -327,18 +372,20 @@ jobs: mariadb*) DB=MariaDB ;; none) DB=""; ;; system) DB=""; ;; + system-pic) DB=""; ;; *) DB=unknown ;; esac case "${{ matrix.client-version }}" in - mysql*) CONC_DB=MySQL ;; - mariadb*) CONC_DB=MariaDB ;; - system) CONC_DB=""; ;; - system-pic) CONC_DB=""; ;; - same-as-server) CONC_DB=""; ;; - *) CONC_DB=unknown ;; + mysql-*) CLIENT_DB=MySQL-Server ;; + mysqlconc-*) CLIENT_DB=MySQL-ConC ;; + mariadbconc-*) CLIENT_DB=MariaDB-ConC ;; + system) CLIENT_DB=""; ;; + system-pic) CLIENT_DB=""; ;; + same-as-server) CLIENT_DB=""; ;; + *) CLIENT_DB=unknown ;; esac VERSION=`echo "${{ matrix.server-version }}" | sed 's/^[^-]*-//'` - CONC_VERSION=`echo "${{ matrix.client-version }}" | sed 's/^[^-]*-//'` + CLIENT_VERSION=`echo "${{ matrix.client-version }}" | sed 's/^[^-]*-//'` if [ "$DB" = "MySQL" ]; then case "$VERSION" in 4.1.*) SANDBOX_URL=https://mysql.linux.cz/Downloads/MySQL-4.1/mysql-standard-$VERSION-unknown-linux-gnu-x86_64-glibc23.tar.gz ;; @@ -381,34 +428,46 @@ jobs: SANDBOX_OPTIONS="$SANDBOX_OPTIONS --init_options=--innodb_use_native_aio=0 --my_clause=innodb_use_native_aio=0 --my_clause=performance_schema=ON" fi fi - if [ "$CONC_DB" = "MySQL" ]; then - case "$CONC_VERSION" in - *-labs) CONC_URL=https://downloads.mysql.com/snapshots/pb/mysql-connector-c-$CONC_VERSION/mysql-connector-c-$CONC_VERSION-linux-glibc2.5-x86_64.tar.gz ;; - 6.0.*) CONC_URL=https://dev.mysql.com/get/mysql-connector-c-$CONC_VERSION-linux-glibc2.3-x86-64bit.tar.gz ;; - 6.1.[0123456789]) CONC_URL=https://dev.mysql.com/get/mysql-connector-c-$CONC_VERSION-linux-glibc2.5-x86_64.tar.gz ;; - 6.1.*) CONC_URL=https://dev.mysql.com/get/mysql-connector-c-$CONC_VERSION-linux-glibc2.12-x86_64.tar.gz ;; - *) echo "Unsupported MySQL Connector/C version '$CONC_VERSION'"; exit 1 ;; + if [ "$CLIENT_DB" = "MySQL-ConC" ]; then + case "$CLIENT_VERSION" in + *-labs) CLIENT_URL=https://downloads.mysql.com/snapshots/pb/mysql-connector-c-$CLIENT_VERSION/mysql-connector-c-$CLIENT_VERSION-linux-glibc2.5-x86_64.tar.gz ;; + 6.0.*) CLIENT_URL=https://dev.mysql.com/get/mysql-connector-c-$CLIENT_VERSION-linux-glibc2.3-x86-64bit.tar.gz ;; + 6.1.[0123456789]) CLIENT_URL=https://dev.mysql.com/get/mysql-connector-c-$CLIENT_VERSION-linux-glibc2.5-x86_64.tar.gz ;; + 6.1.*) CLIENT_URL=https://dev.mysql.com/get/mysql-connector-c-$CLIENT_VERSION-linux-glibc2.12-x86_64.tar.gz ;; + *) echo "Unsupported MySQL Connector/C version '$CLIENT_VERSION'"; exit 1 ;; + esac + CLIENT_FILE="$HOME/cache/$(basename "$CLIENT_URL")" + elif [ "$CLIENT_DB" = "MariaDB-ConC" ]; then + case "$CLIENT_VERSION" in + 3.0.*) CLIENT_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CLIENT_VERSION/-*/}/mariadb-connector-c-$CLIENT_VERSION-linux-x86_64.tar.gz ;; + 3.1.[01234567]) CLIENT_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CLIENT_VERSION/-*/}/mariadb-connector-c-$CLIENT_VERSION-linux-x86_64.tar.gz ;; + 3.*) CLIENT_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CLIENT_VERSION/-*/}/mariadb-connector-c-$CLIENT_VERSION-ubuntu-focal-amd64.tar.gz ;; + *) CLIENT_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CLIENT_VERSION/-*/}/mariadb-connector-c-$CLIENT_VERSION-linux-x86_64.tar.gz ;; esac - CONC_FILE="$HOME/cache/$(basename "$CONC_URL")" - elif [ "$CONC_DB" = "MariaDB" ]; then - case "$CONC_VERSION" in - 3.0.*) CONC_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CONC_VERSION/-*/}/mariadb-connector-c-$CONC_VERSION-linux-x86_64.tar.gz ;; - 3.1.[01234567]) CONC_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CONC_VERSION/-*/}/mariadb-connector-c-$CONC_VERSION-linux-x86_64.tar.gz ;; - 3.*) CONC_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CONC_VERSION/-*/}/mariadb-connector-c-$CONC_VERSION-ubuntu-focal-amd64.tar.gz ;; - *) CONC_URL=https://downloads.mariadb.com/Connectors/c/connector-c-${CONC_VERSION/-*/}/mariadb-connector-c-$CONC_VERSION-linux-x86_64.tar.gz ;; + CLIENT_FILE="$HOME/cache/$(basename "$CLIENT_URL")" + elif [ "$CLIENT_DB" = "MySQL-Server" ]; then + case "$CLIENT_VERSION" in + # FIXME: Only MySQL 8.x server versions are defined here for usage as client library for now + 8.0.?-*|8.0.11) CLIENT_URL=https://dev.mysql.com/get/mysql-$CLIENT_VERSION-linux-glibc2.12-x86_64.tar.gz ;; + 8.0.*) CLIENT_URL=https://dev.mysql.com/get/mysql-$CLIENT_VERSION-linux-glibc2.12-x86_64.tar.xz ;; + 8.1.*) CLIENT_URL=https://dev.mysql.com/get/mysql-$CLIENT_VERSION-linux-glibc2.28-x86_64.tar.xz ;; + *) echo "Unsupported MySQL version '$CLIENT_VERSION'"; exit 1 ;; esac - CONC_FILE="$HOME/cache/$(basename "$CONC_URL")" - elif [ -n "$CONC_DB" ]; then - echo "Unsupported Connector/C '$CONC_DB'"; exit 1 + CLIENT_FILE="$HOME/cache/$(basename "$CLIENT_URL")" + elif [ -n "$CLIENT_DB" ]; then + echo "Unsupported Connector/C '$CLIENT_DB'"; exit 1 fi - if [ -n "$CONC_DB" ]; then - if [ ! -f "$CONC_FILE" ]; then wget --progress=bar:force "$CONC_URL" -O "$CONC_FILE" || exit 1; fi + if [ -n "$CLIENT_DB" ]; then + if [ ! -f "$CLIENT_FILE" ]; then wget --progress=bar:force "$CLIENT_URL" -O "$CLIENT_FILE" || exit 1; fi fi if [ "${{ matrix.client-version }}" = "system-pic" ]; then - sed 's/-L\$pkglibdir *-lmysqld/-L\/usr\/lib\/mysql -lmysqld_pic /' `which mysql_config_pic` > "$HOME/mysql_config_pic" + sed 's/-L\$pkglibdir *-lmysqld/-L\/usr\/lib\/mysql -lmysqld_pic -lpthread_yield /' `which mysql_config_pic` > "$HOME/mysql_config_pic" chmod +x $HOME/mysql_config_pic - apt download mysql-server-core-5.5 - dpkg -x mysql-server-core-5.5_*.deb $HOME/mysql-server-core-5.5 + fi + if [ "${{ matrix.client-version }}" = "system-pic" ] || [ "${{ matrix.server-version }}" = "system-pic" ]; then + wget --progress=bar:force http://archive.ubuntu.com/ubuntu/pool/main/m/mysql-5.5/mysql-server-core-5.5_5.5.62-0ubuntu0.14.04.1_amd64.deb -O /tmp/mysql-server-core-5.5_5.5.62-0ubuntu0.14.04.1_amd64.deb + dpkg -x /tmp/mysql-server-core-5.5_5.5.62-0ubuntu0.14.04.1_amd64.deb $HOME/mysql-server-core-5.5 + mkdir -p "$HOME/datadir" fi if [ -n "$DB" ]; then cpanm --quiet --notest --skip-satisfied MySQL::Sandbox || exit 1 @@ -422,28 +481,17 @@ jobs: printf '#!/bin/sh\nexec %s/msb/my sql_config "$@"\n' $SANDBOX_HOME > "$HOME/mysql_config" chmod +x $HOME/mysql_config fi - if [ -n "$CONC_DB" ]; then - mkdir -p "$HOME/conc" - tar --strip-components=1 --directory="$HOME/conc" -xf "$CONC_FILE" || exit 1 - if $HOME/conc/bin/mysql_config 2>&1 | grep -q /usr/local; then - rm -f $HOME/conc/bin/mysql_config + if [ -n "$CLIENT_DB" ]; then + mkdir -p "$HOME/client" + tar --strip-components=1 --directory="$HOME/client" -xf "$CLIENT_FILE" || exit 1 + if $HOME/client/bin/mysql_config 2>&1 | grep -q /usr/local; then + rm -f $HOME/client/bin/mysql_config fi - if [ -x $HOME/conc/bin/mysql_config ]; then - sed 's/-l "/-lmysqlclient "/g' -i "$HOME/conc/bin/mysql_config" || exit 1 + if [ -x $HOME/client/bin/mysql_config ]; then + sed 's/-l "/-lmysqlclient "/g' -i "$HOME/client/bin/mysql_config" || exit 1 fi fi - - name: Install dependences - run: | - if [ "${{ matrix.perl-version }}" = "system" ]; then - eval $(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib) - fi - perl '-MExtUtils::MakeMaker 7.00' -e '' || cpanm --quiet --notest ExtUtils::MakeMaker@7.00 - perl '-MCPAN::Meta 2.112580' -e '' || cpanm --quiet --notest CPAN::Meta@2.112580 - perl '-Mv5.12' -e '' || cpanm --quiet --notest Test::Deep@1.130 - if [ "${{ matrix.dbi-version }}" != "latest" ]; then cpanm --quiet --notest DBI@${{ matrix.dbi-version }}; fi - cpanm --quiet --notest --skip-satisfied DBI~1.608 Devel::CheckLib~1.12 - cpanm --quiet --notest --skip-satisfied --installdeps --with-configure --with-develop --with-recommends --with-suggests . - - name: Configure + - name: Setup DBD_MARIADB_* env run: | if [ "${{ matrix.perl-version }}" = "system" ]; then eval $(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib) @@ -453,6 +501,9 @@ jobs: export DBD_MARIADB_TESTPASSWORD=test export DBD_MARIADB_TESTHOST=127.0.0.1 export DBD_MARIADB_TESTPORT=3306 + elif [ "${{ matrix.server-version }}" = "system-pic" ]; then + export DBD_MARIADB_TESTHOST=embedded + export DBD_MARIADB_TESTEMBDATADIR="$HOME/datadir" elif [ "${{ matrix.server-version }}" = "none" ]; then export DBD_MARIADB_TESTHOST=0.0.0.0 export DBD_MARIADB_TESTPORT=0 @@ -462,13 +513,16 @@ jobs: export DBD_MARIADB_TESTHOST=127.0.0.1 export DBD_MARIADB_TESTPORT=3310 fi + if [[ ${{ matrix.client-version }} =~ mysql-8 ]]; then + export DBD_MARIADB_TESTAUTHPLUGIN=mysql_native_password + fi if [ "${{ matrix.client-version }}" != "system" ] && [ "${{ matrix.client-version }}" != "system-pic" ] && [ "${{ matrix.client-version }}" != "same-as-server" ]; then - if [ -x $HOME/conc/bin/mysql_config ]; then - export DBD_MARIADB_CONFIG="$HOME/conc/bin/mysql_config" + if [ -x $HOME/client/bin/mysql_config ]; then + export DBD_MARIADB_CONFIG="$HOME/client/bin/mysql_config" else - INCLUDE_PATH=`find "$HOME/conc" -name "mysql.h" | sort | head -1` + INCLUDE_PATH=`find "$HOME/client" -name "mysql.h" | sort | head -1` if [ -z "$INCLUDE_PATH" ]; then echo "File mysql.h was not found"; exit 1; fi - LIB_PATH=`find "$HOME/conc" -name "lib*.so" | sort | head -1` + LIB_PATH=`find "$HOME/client" -name "lib*.so" | sort | head -1` if [ -z "$INCLUDE_PATH" ]; then echo "File lib*.so was not found"; exit 1; fi export DBD_MARIADB_CFLAGS="-I`dirname $INCLUDE_PATH`" export DBD_MARIADB_LIBS="-L`dirname $LIB_PATH` -l`echo $LIB_PATH | sed 's/.*\/lib//;s/\.so//'`" @@ -476,8 +530,29 @@ jobs: fi elif [ "${{ matrix.client-version }}" = "same-as-server" ] && [ "${{ matrix.server-version }}" != "system" ]; then export DBD_MARIADB_CONFIG="$HOME/mysql_config" + export DBD_MARIADB_TESTEMBOPTIONS="--language=`find $HOME/sandbox/* -name english | sed 's/english//'`,--log-error=/dev/null" elif [ "${{ matrix.client-version }}" = "system-pic" ]; then export DBD_MARIADB_CONFIG="$HOME/mysql_config_pic" + export DBD_MARIADB_TESTEMBOPTIONS="--language=$HOME/mysql-server-core-5.5/usr/share/mysql/,--log-error=/dev/null" + export DBD_MARIADB_REQUIREEMBSUP=1 + fi + export | sed 's/^declare -x //;s/="/=/;s/"$//' | grep '^DBD_MARIADB_' >> $GITHUB_ENV + - name: Install dependencies + run: | + if [ "${{ matrix.perl-version }}" = "system" ]; then + eval $(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib) + fi + perl -M5.008003 || cpanm --quiet --notest ExtUtils::ParseXS@3.51 + perl '-MExtUtils::MakeMaker 7.00' -e '' || cpanm --quiet --notest ExtUtils::MakeMaker@7.00 + perl '-MCPAN::Meta 2.112580' -e '' || cpanm --quiet --notest CPAN::Meta@2.112580 + perl '-Mv5.12' -e '' || cpanm --quiet --notest Test::Deep@1.130 + if [ "${{ matrix.dbi-version }}" != "latest" ]; then cpanm --quiet --notest DBI@${{ matrix.dbi-version }}; fi + cpanm --quiet --notest --skip-satisfied DBI~1.608 Devel::CheckLib~1.12 + cpanm --quiet --notest --skip-satisfied --installdeps --with-configure --with-develop --with-recommends --with-suggests . + - name: Configure + run: | + if [ "${{ matrix.perl-version }}" = "system" ]; then + eval $(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib) fi make realclean || true perl Makefile.PL @@ -488,16 +563,14 @@ jobs: fi make - name: Test + if: ${{ matrix.server-version != 'system-pic' }} run: | if [ "${{ matrix.perl-version }}" = "system" ]; then eval $(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib) fi - if [ "${{ matrix.client-version }}" = "system-pic" ]; then - export DBD_MARIADB_TESTLANGDIR="$HOME/mysql-server-core-5.5/usr/share/mysql/english" - elif [ "${{ matrix.server-version }}" != "none" ] && [ "${{ matrix.server-version }}" != "system" ]; then - export DBD_MARIADB_TESTLANGDIR=`find $HOME/sandbox/* -name english | head -1` + if [ "${{ matrix.server-version }}" != "system-pic" ]; then + export HARNESS_OPTIONS=j4 fi - export HARNESS_OPTIONS=j4 export RELEASE_TESTING=1 if [ "${{ matrix.server-version }}" != "none" ]; then export CONNECTION_TESTING=1 diff --git a/Makefile.PL b/Makefile.PL index 8be7df01..e46b4ac7 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -24,9 +24,9 @@ push @mysql_headers, 'mysql.h'; our $opt = { "help" => \&Usage, }; -my ($test_host, $test_port, $test_socket, $test_authplugin); +my ($test_host, $test_port, $test_socket, $test_embdatadir); { -local ($::test_host, $::test_port, $::test_user, $::test_socket, $::test_authplugin, $::test_password, $::test_db, $::test_mysql_config, $::test_cflags, $::test_libs); +local ($::test_host, $::test_port, $::test_user, $::test_socket, $::test_embdatadir, $::test_emboptions, $::test_authplugin, $::test_password, $::test_db, $::test_mysql_config, $::test_cflags, $::test_libs); eval { require "./t/MariaDB.mtest" } and do { $opt->{'testuser'} = $::test_user; $opt->{'testpassword'} = $::test_password; @@ -34,10 +34,12 @@ $opt->{'testdb'} = $::test_db; $opt->{'mysql_config'} = $::test_mysql_config; $opt->{'cflags'} = $::test_cflags; $opt->{'libs'} = $::test_libs; +$opt->{'testemboptions'} = $::test_emboptions; +$opt->{'testauthplugin'} = $::test_authplugin; $test_host = $::test_host; $test_port = $::test_port; $test_socket = $::test_socket; -$test_authplugin = $::test_authplugin; +$test_embdatadir = $::test_embdatadir; } } @@ -50,7 +52,10 @@ Getopt::Long::GetOptions( "testuser:s", "testpassword:s", "testsocket:s", + "testembdatadir:s", + "testemboptions:s", "testauthplugin:s", + "requireembsup!", "cflags:s", "libs:s", "mysql_config:s", @@ -106,62 +111,112 @@ MSG } } -for my $key (qw(testdb testhost testuser testpassword testsocket testauthplugin testport cflags libs)) +if (exists $opt->{requireembsup}) +{ + $source->{'requireembsup'} = "User's choice"; +} +elsif (defined $ENV{'DBD_MARIADB_REQUIREEMBSUP'}) +{ + $source->{'requireembsup'} = 'environment'; + $opt->{'requireembsup'} = !!$ENV{DBD_MARIADB_REQUIREEMBSUP}; +} +else +{ + $source->{'requireembsup'} = 'default'; + $opt->{'requireembsup'} = 0; +} + +for my $key (qw(testdb testhost testuser testpassword testsocket testport testembdatadir testemboptions testauthplugin cflags libs)) { Configure($opt, $source, $key); } -if (!$opt->{testport} && (!$opt->{testhost} || $opt->{testhost} eq 'localhost') && !defined $opt->{testsocket} && $test_socket) { +# Reusing old test host is possible if it does not conflict with new test port, new test socket or new test embdatadir +if (!defined $opt->{testhost} && defined $test_host && length $test_host && + ((defined $opt->{testport} && $test_host ne 'localhost' && $test_host ne 'embedded') || + (defined $opt->{testsocket} && $test_host eq 'localhost') || + (defined $opt->{testembdatadir} && $test_host eq 'embedded') || + (!defined $opt->{testsocket} && !defined $opt->{testembdatadir} && !defined $opt->{testport}))) { + $opt->{testhost} = $test_host; + $source->{testhost} = "User's choice"; +} + +# Reusing old test port is possible if it does not conflict with new test host, new test socket or new test embdatadir +if (!defined $opt->{testport} && defined $test_port && length $test_port && + (!defined $opt->{testhost} || ($opt->{testhost} ne 'localhost' && $opt->{testhost} ne 'embedded')) && + !defined $opt->{testsocket} && + !defined $opt->{testembdatadir}) { + $opt->{testport} = $test_port; + $source->{testport} = "User's choice"; +} + +# Reusing old test socket is possible if it does not conflict with new test host, new test port or new test embdatadir +if (!defined $opt->{testsocket} && defined $test_socket && length $test_socket && + (!defined $opt->{testhost} || $opt->{testhost} eq 'localhost') && + !defined $opt->{testport} && + !defined $opt->{testembdatadir}) { $opt->{testsocket} = $test_socket; $source->{testsocket} = "User's choice"; } -if (!$opt->{testsocket}) { - if (!defined $opt->{testhost} && $test_host && (!$opt->{testport} || $test_host ne 'localhost')) { - $opt->{testhost} = $test_host; - $source->{testhost} = "User's choice"; - } - if (!defined $opt->{testport} && $test_port && (!$opt->{testhost} || $opt->{testhost} ne 'localhost')) { - $opt->{testport} = $test_port; - $source->{testport} = "User's choice"; - } -} else { - if (!defined $opt->{testhost} && $test_host && $test_host eq 'localhost') { - $opt->{testhost} = 'localhost'; - $source->{testhost} = "User's choice"; - } +# Reusing old test embdatadir is possible if it does not conflict with new test host, new test port or new test socket +if (!defined $opt->{testembdatadir} && defined $test_embdatadir && length $test_embdatadir && + (!defined $opt->{testhost} || $opt->{testhost} eq 'embedded') && + !defined $opt->{testport} && + !defined $opt->{testsocket}) { + $opt->{testembdatadir} = $test_embdatadir; + $source->{testembdatadir} = "User's choice"; +} + +# if we have a testsocket but no host, set localhost +if (defined $opt->{testsocket} && !defined $opt->{testhost}) { + $opt->{testhost} = 'localhost'; + $source->{testhost} = 'guessed'; } -#if we have a testport but no host, assume 127.0.0.1 -if ( $opt->{testport} && !$opt->{testhost} ) { +# if we have a testembdatadir but no host, set embedded +if (defined $opt->{testembdatadir} && !defined $opt->{testhost}) { + $opt->{testhost} = 'embedded'; + $source->{testhost} = 'guessed'; +} + +# if we have a testport but no host, assume 127.0.0.1 +if (defined $opt->{testport} && !defined $opt->{testhost}) { $opt->{testhost} = '127.0.0.1'; $source->{testhost} = 'guessed'; } -foreach (qw(testhost testport testsocket testauthplugin)) { +foreach (qw(testhost testport testsocket testembdatadir)) { next if defined $opt->{$_}; $opt->{$_} = ''; $source->{$_} = 'default'; } # testsocket makes sense only when testhost is localhost -if ($opt->{testsocket} && $opt->{testhost} && $opt->{testhost} ne 'localhost') { +if (length $opt->{testsocket} && $opt->{testhost} ne 'localhost') { die << "MSG"; -Option --testport or --testhost different from localhost cannot be specified together with option --testsocket. +Option --testhost different from localhost cannot be specified together with option --testsocket. MSG } -# testport cannot be specified when host is localhost -if ($opt->{testport} && $opt->{testhost} && $opt->{testhost} eq 'localhost') { +# testembdatadir makes sense only when testhost is embedded +if (length $opt->{testembdatadir} && $opt->{testhost} ne 'embedded') { die << "MSG"; -Option --testport cannot be specified when --testhost is localhost. +Option --testhost different from embedded cannot be specified together with option --testembdatadir. MSG } -# testhost cannot be embedded -if ($opt->{testhost} && $opt->{testhost} eq 'embedded') { +# there is no default testembdatadir, so check that it is set +if ($opt->{testhost} eq 'embedded' && !length $opt->{testembdatadir}) { die << "MSG"; -Option --testhost cannot be embedded. +Option --testembdatadir must be specified when --testhost is embedded. +MSG +} + +# testport cannot be specified when host is localhost or embedded +if (length $opt->{testport} && ($opt->{testhost} eq 'localhost' || $opt->{testhost} eq 'embedded')) { + die << "MSG"; +Option --testport cannot be specified when --testhost is localhost or embedded. MSG } @@ -262,6 +317,14 @@ my $have_embedded = check_lib( print "Embedded server: " . ($have_embedded ? "supported" : "not supported by client library") . "\n\n"; +if (!$have_embedded && ($opt->{testhost} eq 'embedded' || $opt->{requireembsup})) { + die << "MSG"; +Cannot run test suite against Embedded server (specified via +option --testhost=embedded or option --requireembsup) because +Embedded server is not supported by client library. +MSG +} + my $have_get_charset_number = check_lib( LIBS => (join ' ', @libdirs, $main_lib), ccflags => $opt->{cflags}, @@ -414,13 +477,17 @@ EOF print "Client library deinitialize OpenSSL library functions: " . ($have_problem_with_openssl ? "yes" : "no") . "\n\n"; my $fileName = File::Spec->catfile("t", "MariaDB.mtest"); +print "Writing $fileName for test suite\n"; (open(FILE, ">$fileName") && - (print FILE ("{ local " . Data::Dumper->Dump([$opt], ["opt"]) . - " local " . Data::Dumper->Dump([$source], ["source"]) . + (print FILE ("{\n" . + "local " . Data::Dumper->new([$opt], ["opt"])->Sortkeys(1)->Indent(1)->Dump() . + "local " . Data::Dumper->new([$source], ["source"])->Sortkeys(1)->Indent(1)->Dump() . "\$::test_host = \$opt->{'testhost'};\n" . "\$::test_port = \$opt->{'testport'};\n" . "\$::test_user = \$opt->{'testuser'};\n" . "\$::test_socket = \$opt->{'testsocket'};\n" . + "\$::test_embdatadir = \$opt->{'testembdatadir'};\n" . + "\$::test_emboptions = \$opt->{'testemboptions'};\n" . "\$::test_authplugin = \$opt->{'testauthplugin'};\n" . "\$::test_password = \$opt->{'testpassword'};\n" . "\$::test_db = \$opt->{'testdb'};\n" . @@ -428,6 +495,8 @@ my $fileName = File::Spec->catfile("t", "MariaDB.mtest"); "\$::test_dsn .= \":\$::test_host\" if \$::test_host;\n" . "\$::test_dsn .= \":\$::test_port\" if \$::test_port;\n". "\$::test_dsn .= \";mariadb_socket=\$::test_socket\" if \$::test_socket;\n" . + "\$::test_dsn .= \";mariadb_embedded_options=--datadir=\$::test_embdatadir\" if \$::test_embdatadir;\n" . + "\$::test_dsn .= \",\$::test_emboptions\" if \$::test_embdatadir and \$::test_emboptions;\n" . "\$::test_dsn .= \";mariadb_auth_plugin=\$::test_authplugin\" if \$::test_authplugin;\n" . "\$::test_dsn .= \";mariadb_connect_timeout=120;mariadb_read_timeout=120;mariadb_write_timeout=120\";\n" . "\$::test_mysql_config = \$opt->{'mysql_config'} if \$source->{'mysql_config'} eq 'User\\'s choice';\n" . @@ -661,9 +730,19 @@ Possible options are: the database server; by default unix socket is chosen by mariadb/mysqlclient library; takes effect only when --testhost is set to "localhost" + --testembdatadir= Use as database directory for embedded server, + it may be and it is suggested to be empty, which means + that database is uninitialized; takes effect only when + --testhost is set to "embedded" + --testemboptions= Use as additional options for embedded server + separated by comma, it is recommended to set output + log file (e.g. '--log-error=/dev/null') and language + directory (e.g. '--language=/usr/local/share/mysql') + if language directory is different than system one --testauthplugin= Use auth plugin when doing user authentication handshake with server; for older server versions it is needed to pass "mysql_native_password" + --requireembsup Require client library with embedded server support --mariadb_config Synonym for --mysql_config, override it --mysql_config= Specify for mariadb_config or mysql_config script --help Print this message and exit @@ -858,6 +937,14 @@ perl Makefile.PL --testuser=username $source->{$param} = "default"; $opt->{$param} = ""; } + elsif ($param eq "testauthplugin") { + $source->{$param} = "default"; + $opt->{$param} = ""; + } + elsif ($param eq "testemboptions") { + $source->{$param} = "default"; + $opt->{$param} = ""; + } elsif ($param eq "cflags") { $source->{$param} = "guessed"; my ($dir, $file) = SearchFor('include', 'mysql.h'); @@ -900,7 +987,7 @@ section "Linker flags" or type perl Makefile.PL --help MSG } - elsif ($param eq "testhost" || $param eq "testport" || $param eq "testsocket" || $param eq "testauthplugin") { + elsif (grep { $param eq $_ } ("testhost", "testport", "testsocket", "testembdatadir")) { # known parameter, but do nothing } else { diff --git a/dbdimp.c b/dbdimp.c index 7b276cc7..951a81e5 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -1693,6 +1693,7 @@ static bool mariadb_dr_connect( (void)hv_stores(processed, "mariadb_embedded_options", &PL_sv_yes); mysql_options(sock, MYSQL_OPT_USE_EMBEDDED_CONNECTION, NULL); + imp_dbh->is_embedded = TRUE; host = NULL; } else @@ -2146,8 +2147,20 @@ static bool mariadb_dr_connect( } } + #if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80300 + if (mysql_options(sock, MYSQL_OPT_SSL_KEY, client_key) != 0 || + mysql_options(sock, MYSQL_OPT_SSL_CERT, client_cert) != 0 || + mysql_options(sock, MYSQL_OPT_SSL_CA, ca_file) != 0 || + mysql_options(sock, MYSQL_OPT_SSL_CAPATH, ca_path) != 0 || + mysql_options(sock, MYSQL_OPT_SSL_CIPHER, cipher) != 0) { + mariadb_dr_do_error(dbh, CR_SSL_CONNECTION_ERROR, "SSL connection error: Setting SSL options failed", "HY000"); + mariadb_db_disconnect(dbh, imp_dbh); + return FALSE; + } + #else mysql_ssl_set(sock, client_key, client_cert, ca_file, ca_path, cipher); + #endif if (ssl_verify && !(ca_file || ca_path)) { mariadb_dr_do_error(dbh, CR_SSL_CONNECTION_ERROR, "SSL connection error: mariadb_ssl_verify_server_cert=1 is not supported without mariadb_ssl_ca_file or mariadb_ssl_ca_path", "HY000"); @@ -2375,6 +2388,12 @@ static bool mariadb_dr_connect( sock->reconnect = FALSE; #endif + /* Connection to Embedded server does not have socket */ + if (imp_dbh->is_embedded) + { + imp_dbh->sock_fd = -1; + } + else { my_socket sock_os; int retval; @@ -2645,6 +2664,7 @@ int mariadb_db_login6_sv(SV *dbh, imp_dbh_t *imp_dbh, SV *dsn, SV *user, SV *pas imp_dbh->bind_comment_placeholders= FALSE; imp_dbh->auto_reconnect = FALSE; imp_dbh->connected = FALSE; /* Will be switched to TRUE after DBI->connect finish */ + imp_dbh->is_embedded = FALSE; if (!mariadb_db_my_login(aTHX_ dbh, imp_dbh)) return 0; @@ -2662,7 +2682,7 @@ int mariadb_db_login6_sv(SV *dbh, imp_dbh_t *imp_dbh, SV *dsn, SV *user, SV *pas static my_ulonglong mariadb_st_internal_execute(SV *h, char *sbuf, STRLEN slen, int num_params, imp_sth_ph_t *params, MYSQL_RES **result, MYSQL **svsock, bool use_mysql_use_result); -static my_ulonglong mariadb_st_internal_execute41(SV *h, char *sbuf, STRLEN slen, bool has_params, MYSQL_RES **result, MYSQL_STMT **stmt_ptr, MYSQL_BIND *bind, MYSQL **svsock, bool *has_been_bound); +static my_ulonglong mariadb_st_internal_execute41(SV *h, char *sbuf, STRLEN slen, int num_params, MYSQL_RES **result, MYSQL_STMT **stmt_ptr, MYSQL_BIND *bind, MYSQL **svsock, bool *has_been_bound); /************************************************************************** * @@ -2784,6 +2804,11 @@ IV mariadb_db_do6(SV *dbh, imp_dbh_t *imp_dbh, SV *statement_sv, SV *attribs, I3 if (async) { + if (imp_dbh->is_embedded) + { + mariadb_dr_do_error(dbh, CR_UNKNOWN_ERROR, "Async option not supported for Embedded server", "HY000"); + return -2; + } if (disable_fallback_for_server_prepare) { mariadb_dr_do_error(dbh, ER_UNSUPPORTED_PS, "Async option not supported with server side prepare", "HY000"); @@ -2821,7 +2846,7 @@ IV mariadb_db_do6(SV *dbh, imp_dbh_t *imp_dbh, SV *statement_sv, SV *attribs, I3 #endif /* This is error for previous unfetched result ret. So do not report server errors to caller which is expecting new result set. */ error = mysql_errno(imp_dbh->pmysql); - if (error == CR_COMMANDS_OUT_OF_SYNC || error == CR_OUT_OF_MEMORY || error == CR_SERVER_GONE_ERROR || error == CR_SERVER_LOST || error == CR_UNKNOWN_ERROR) + if (error == CR_COMMANDS_OUT_OF_SYNC || error == CR_OUT_OF_MEMORY || error == CR_SERVER_GONE_ERROR || error == CR_SERVER_LOST || error == ER_CLIENT_INTERACTION_TIMEOUT || error == CR_UNKNOWN_ERROR) { mariadb_dr_do_error(dbh, mysql_errno(imp_dbh->pmysql), mysql_error(imp_dbh->pmysql), mysql_sqlstate(imp_dbh->pmysql)); return -2; @@ -3164,8 +3189,11 @@ static void mariadb_db_close_mysql(pTHX_ imp_drh_t *imp_drh, imp_dbh_t *imp_dbh) socket from C file descriptor sock_fd via _set_osfhnd() function and then close sock_fd via close() function. */ + if (imp_dbh->sock_fd >= 0) + { _set_osfhnd(imp_dbh->sock_fd, INVALID_HANDLE_VALUE); close(imp_dbh->sock_fd); + } #endif imp_dbh->sock_fd = -1; svp = hv_fetchs((HV*)DBIc_MY_H(imp_dbh), "ChildHandles", FALSE); @@ -3480,6 +3508,11 @@ mariadb_db_STORE_attrib( #endif else if (memEQs(key, kl, "mariadb_max_allowed_packet")) { + if (imp_dbh->is_embedded) + { + mariadb_dr_do_error(dbh, CR_UNKNOWN_ERROR, "mariadb_max_allowed_packet is not supported for Embedded server", "HY000"); + return 0; + } #if (!defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709 && MYSQL_VERSION_ID != 60000) || (defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100206 && MYSQL_VERSION_ID != 100300) /* MYSQL_OPT_MAX_ALLOWED_PACKET was added in mysql 5.7.9 */ /* MYSQL_OPT_MAX_ALLOWED_PACKET was added in MariaDB 10.2.6 and MariaDB 10.3.1 */ @@ -3666,7 +3699,7 @@ SV* mariadb_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) } else if (memEQs(key, kl, "mariadb_hostinfo")) { - const char *hostinfo = imp_dbh->pmysql ? mysql_get_host_info(imp_dbh->pmysql) : NULL; + const char *hostinfo = imp_dbh->pmysql ? (imp_dbh->is_embedded ? "Embedded" : mysql_get_host_info(imp_dbh->pmysql)) : NULL; result = hostinfo ? sv_2mortal(newSVpv(hostinfo, 0)) : &PL_sv_undef; sv_utf8_decode(result); } @@ -3684,6 +3717,11 @@ SV* mariadb_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) else if (memEQs(key, kl, "mariadb_max_allowed_packet")) { unsigned long packet_size; + if (imp_dbh->is_embedded) + { + mariadb_dr_do_error(dbh, CR_UNKNOWN_ERROR, "mariadb_max_allowed_packet is not supported for Embedded server", "HY000"); + return Nullsv; + } #if (!defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50709 && MYSQL_VERSION_ID != 60000) || (defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 100206 && MYSQL_VERSION_ID != 100300) /* mysql_get_option() is not available in all versions */ /* if we do not have mysql_get_option() we cannot retrieve max_allowed_packet */ @@ -3961,6 +3999,11 @@ mariadb_st_prepare_sv( (void)hv_stores(processed, "mariadb_async", &PL_sv_yes); svp = MARIADB_DR_ATTRIB_GET_SVPS(attribs, "mariadb_async"); if(svp && SvTRUE(*svp)) { + if (imp_dbh->is_embedded) + { + mariadb_dr_do_error(sth, CR_UNKNOWN_ERROR, "Async option not supported for Embedded server", "HY000"); + return 0; + } imp_sth->is_async = TRUE; if (imp_sth->disable_fallback_for_server_prepare) { @@ -4210,7 +4253,7 @@ static bool mariadb_st_free_result_sets(SV *sth, imp_sth_t *imp_sth, bool free_l /* This is error for previous unfetched result ret. So do not report server errors to caller which is expecting new result set. */ error = mysql_errno(imp_dbh->pmysql); - if (error == CR_COMMANDS_OUT_OF_SYNC || error == CR_OUT_OF_MEMORY || error == CR_SERVER_GONE_ERROR || error == CR_SERVER_LOST || error == CR_UNKNOWN_ERROR) + if (error == CR_COMMANDS_OUT_OF_SYNC || error == CR_OUT_OF_MEMORY || error == CR_SERVER_GONE_ERROR || error == CR_SERVER_LOST || error == ER_CLIENT_INTERACTION_TIMEOUT || error == CR_UNKNOWN_ERROR) { mariadb_dr_do_error(sth, mysql_errno(imp_dbh->pmysql), mysql_error(imp_dbh->pmysql), mysql_sqlstate(imp_dbh->pmysql)); return FALSE; @@ -4558,7 +4601,7 @@ static my_ulonglong mariadb_st_internal_execute( * Inputs: h - object handle, for storing error messages * statement - query being executed * attribs - statement attributes, currently ignored - * has_params - non-zero parameters being bound + * num_params - number of parameters being bound * params - parameter array * result - where to store results, if any * svsock - socket connected to the database @@ -4569,7 +4612,7 @@ static my_ulonglong mariadb_st_internal_execute41( SV *h, char *sbuf, STRLEN slen, - bool has_params, + int num_params, MYSQL_RES **result, MYSQL_STMT **stmt_ptr, MYSQL_BIND *bind, @@ -4612,9 +4655,13 @@ static my_ulonglong mariadb_st_internal_execute41( we have to rebind them */ - if (!reconnected && has_params && !(*has_been_bound)) + if (!reconnected && num_params > 0 && !(*has_been_bound)) { +#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80300 + if (mysql_stmt_bind_named_param(stmt, bind, num_params, NULL) == 0) +#else if (mysql_stmt_bind_param(stmt,bind) == 0) +#endif { *has_been_bound = TRUE; } @@ -4627,7 +4674,9 @@ static my_ulonglong mariadb_st_internal_execute41( } if (DBIc_TRACE_LEVEL(imp_xxh) >= 2) - PerlIO_printf(DBIc_LOGPIO(imp_xxh), "\t\tmariadb_st_internal_execute41 calling mysql_execute\n"); + PerlIO_printf(DBIc_LOGPIO(imp_xxh), + "\t\tmariadb_st_internal_execute41 calling mysql_execute with %d num_params\n", + num_params); if (!reconnected) { @@ -4652,9 +4701,13 @@ static my_ulonglong mariadb_st_internal_execute41( } mysql_stmt_close(*stmt_ptr); *stmt_ptr = stmt; - if (has_params) + if (num_params > 0) { +#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80300 + if (mysql_stmt_bind_named_param(stmt, bind, num_params, NULL) == 0) +#else if (mysql_stmt_bind_param(stmt,bind)) +#endif goto error; *has_been_bound = TRUE; } @@ -4813,7 +4866,7 @@ IV mariadb_st_execute_iv(SV* sth, imp_sth_t* imp_sth) sth, imp_sth->statement, imp_sth->statement_len, - !!(DBIc_NUM_PARAMS(imp_sth) > 0), + DBIc_NUM_PARAMS(imp_sth), &imp_sth->result, &imp_sth->stmt, imp_sth->bind, @@ -6401,8 +6454,10 @@ bool mariadb_db_reconnect(SV *h, MYSQL_STMT *stmt) if (imp_dbh->pmysql && mysql_errno(imp_dbh->pmysql) != CR_SERVER_GONE_ERROR && mysql_errno(imp_dbh->pmysql) != CR_SERVER_LOST && + mysql_errno(imp_dbh->pmysql) != ER_CLIENT_INTERACTION_TIMEOUT && (!stmt || (mysql_stmt_errno(stmt) != CR_SERVER_GONE_ERROR && mysql_stmt_errno(stmt) != CR_SERVER_LOST && + mysql_stmt_errno(stmt) != ER_CLIENT_INTERACTION_TIMEOUT && mysql_stmt_errno(stmt) != CR_STMT_CLOSED))) { /* Other error */ diff --git a/dbdimp.h b/dbdimp.h index 8b715810..765306b2 100644 --- a/dbdimp.h +++ b/dbdimp.h @@ -86,6 +86,11 @@ #define CR_STMT_CLOSED 2056 #endif +/* Macro was added in MySQL 8.0.24 */ +#ifndef ER_CLIENT_INTERACTION_TIMEOUT +#define ER_CLIENT_INTERACTION_TIMEOUT 4031 +#endif + /******************************************************************** * Standard Perl macros which are not defined in every Perl version * @@ -527,6 +532,7 @@ struct imp_dbh_st { MYSQL *pmysql; int sock_fd; bool connected; /* Set to true after DBI->connect finished */ + bool is_embedded; bool auto_reconnect; bool bind_type_guessing; bool bind_comment_placeholders; diff --git a/lib/DBD/MariaDB.pm b/lib/DBD/MariaDB.pm index bd5c6488..515c8f17 100644 --- a/lib/DBD/MariaDB.pm +++ b/lib/DBD/MariaDB.pm @@ -731,7 +731,6 @@ my %odbc_info_constants = ( 169 => 127, # SQL_AGGREGATE_FUNCTIONS 117 => 0, # SQL_ALTER_DOMAIN 86 => 3, # SQL_ALTER_TABLE - 10021 => 2, # SQL_ASYNC_MODE 120 => 2, # SQL_BATCH_ROW_COUNT 121 => 2, # SQL_BATCH_SUPPORT 82 => 0, # SQL_BOOKMARK_PERSISTENCE @@ -905,6 +904,7 @@ my %odbc_info_constants = ( ); my %odbc_info_subs = ( + 10021 => sub { $_[0]->FETCH('mariadb_hostinfo') eq 'Embedded' ? 0 : 2 }, # SQL_ASYNC_MODE 2 => sub { "DBI:MariaDB:" . $_[0]->{Name} }, # SQL_DATA_SOURCE_NAME 17 => sub { ($_[0]->FETCH('mariadb_serverinfo') =~ /MariaDB|-maria-/) ? 'MariaDB' : 'MySQL' }, # SQL_DBMS_NAME 18 => sub { my $ver = $_[0]->FETCH('mariadb_serverversion'); sprintf("%02u.%02u.%02u00", $ver/10000, ($ver%10000)/100, $ver%100) }, # SQL_DBMS_VER diff --git a/lib/DBD/MariaDB.pod b/lib/DBD/MariaDB.pod index c5cf8d04..4ea097b1 100644 --- a/lib/DBD/MariaDB.pod +++ b/lib/DBD/MariaDB.pod @@ -364,15 +364,20 @@ Example: use DBI; my $datadir = '/var/lib/mysql/'; - my $langdir = '/usr/share/mysql/english'; + my $langdir = '/usr/share/mysql/'; my $dsn = 'DBI:MariaDB:host=embedded;database=test;' - . "mariadb_embedded_options=--datadir=$datadir,--language=$langdir"; + . "mariadb_embedded_options=--datadir=$datadir,' + . '--language=$langdir,' + . '--log-error=/dev/null"; my $dbh = DBI->connect($dsn, undef, undef); This would start embedded server with language directory C<$langdir>, database -directory C<$datadir> and connects to database C. Embedded server does not -have to be supported by configured MariaDB or MySQL library. In that case -Lconnect() >>|/connect> returns an error. +directory C<$datadir>, drop error messages and connects to database C. +It is suggested to specify C<--log-error=> option which defines location for +warning messages because by default Embedded server prints its warning and +error messages to C which is not expected by Perl or application. +Embedded server does not have to be supported by configured MariaDB or MySQL +library. In that case Lconnect() >>|/connect> returns an error. =item mariadb_embedded_groups diff --git a/t/10connect.t b/t/10connect.t index e629af0f..abc677b0 100644 --- a/t/10connect.t +++ b/t/10connect.t @@ -32,8 +32,9 @@ for my $attribute ( qw( mariadb_stat mariadb_protoinfo ) ) { - ok($dbh->{$attribute}, "Value of '$attribute'"); - diag "$attribute is: ". $dbh->{$attribute}; + my $value = $dbh->{$attribute}; + ok(defined $value, "Value of '$attribute'"); + diag "$attribute is: ". (defined $value ? $value : "(undef)"); } my $sql_dbms_name = $dbh->get_info($GetInfoType{SQL_DBMS_NAME}); diff --git a/t/12embedded.t b/t/12embedded.t index 88327cf3..901eee22 100644 --- a/t/12embedded.t +++ b/t/12embedded.t @@ -3,32 +3,27 @@ use warnings; use Test::More; use DBI; +use DBD::MariaDB; use File::Temp; -use vars qw($test_dsn $test_user $test_password); +use vars qw($test_dsn $test_user $test_password $test_emboptions); use lib 't', '.'; require 'lib.pl'; -sub fatal_tmpdir_error { +sub get_error { my $err = $@; $err =~ s/ at \S+ line \d+\.?\s*$//; $err = 'unknown error' unless $err; - fatal("Cannot create temporary directory: $err"); - if ( $ENV{CONNECTION_TESTING} ) { - BAIL_OUT "Cannot create temporary directory: $err"; - } else { - plan skip_all => "Cannot create temporary directory: $err"; - } + return $err; } sub fatal_connection_error { - my $err = $@; - $err =~ s/ at \S+ line \d+\.?\s*$//; - $err = 'unknown error' unless $err; + my ($type) = @_; + my $err = get_error(); if ( $ENV{CONNECTION_TESTING} ) { - BAIL_OUT "No connection to embedded server: $err"; + BAIL_OUT "No connection to $type server: $err"; } else { - diag "No connection to embedded server: $err"; + diag "No connection to $type server: $err"; done_testing; exit; } @@ -36,41 +31,36 @@ sub fatal_connection_error { sub connect_to_embedded_server { my ($tmpdir, $database) = @_; - my $lang_arg = $ENV{DBD_MARIADB_TESTLANGDIR} ? ",--language=$ENV{DBD_MARIADB_TESTLANGDIR}" : ''; - my $emb_dsn = "DBI:MariaDB:host=embedded;mariadb_embedded_options=--datadir=$tmpdir$lang_arg;"; - $emb_dsn .= "database=$database" if defined $database; + my $emb_dsn = "DBI:MariaDB:host=embedded;mariadb_embedded_options=--datadir=$tmpdir"; + $emb_dsn .= ",$test_emboptions" if length $test_emboptions; + $emb_dsn .= ";database=$database" if defined $database; return eval { DBI->connect($emb_dsn, undef, undef, { RaiseError => 1, PrintError => 0 }) }; } sub connect_to_real_server { - my $dbh = eval { DBI->connect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0, AutoCommit => 0 }) }; - if (not defined $dbh) { - my $err = $@; - $err =~ s/ at \S+ line \d+\.?\s*$//; - $err = 'unknown error' unless $err; - if ( $ENV{CONNECTION_TESTING} ) { - BAIL_OUT "No connection to real non-embedded server: $err"; - } else { - diag "No connection to real non-embedded server: $err"; - } - } - return $dbh; + return eval { DBI->connect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0, AutoCommit => 0 }) }; +} + +sub test_dsn_uses_embedded_server { + my ($dbi_dsn, $driver_dsn) = ($test_dsn =~ /^([^:]*:[^:]*:)(.*)$/); + my $attr_dsn = DBD::MariaDB->parse_dsn($driver_dsn); + return exists $attr_dsn->{host} && $attr_dsn->{host} eq 'embedded'; } -my $tmpdir1 = eval { File::Temp::tempdir(CLEANUP => 1) } or fatal_tmpdir_error(); -my $tmpdir2 = eval { File::Temp::tempdir(CLEANUP => 1) } or fatal_tmpdir_error(); +my $tmpdir1 = File::Temp::tempdir(CLEANUP => 1); +my $tmpdir2 = File::Temp::tempdir(CLEANUP => 1); my $dbh1 = connect_to_embedded_server($tmpdir1); plan skip_all => $DBI::errstr if not defined $dbh1 and $DBI::errstr =~ /Embedded server is not supported/; -ok(defined $dbh1, "Connected to embedded server with datadir in $tmpdir1") or fatal_connection_error(); +ok(defined $dbh1, "Connected to embedded server with datadir in $tmpdir1") or fatal_connection_error('embedded'); ok($dbh1->do('CREATE DATABASE dbd_mariadb_embedded'), 'Created database'); ok($dbh1->do('USE dbd_mariadb_embedded'), 'Switched to database'); my $dbh2 = connect_to_embedded_server($tmpdir1, 'dbd_mariadb_embedded'); -ok(defined $dbh2, "Second connection to embedded server with datadir in $tmpdir1") or fatal_connection_error(); +ok(defined $dbh2, "Second connection to embedded server with datadir in $tmpdir1") or fatal_connection_error('embedded'); my $dbh3 = connect_to_embedded_server($tmpdir1, 'dbd_mariadb_embedded'); -ok(defined $dbh3, "Third conection to embedded server with datadir in $tmpdir1") or fatal_connection_error(); +ok(defined $dbh3, "Third connection to embedded server with datadir in $tmpdir1") or fatal_connection_error('embedded'); ok($dbh1->do('CREATE TABLE dbd_mariadb_embedded(id INT)'), 'Created table with first connection'); ok($dbh2->do('INSERT INTO dbd_mariadb_embedded(id) VALUES(10)'), 'Inserted values into table with second connection'); @@ -81,9 +71,10 @@ is(scalar $dbh3->selectrow_array('SELECT id FROM dbd_mariadb_embedded'), 10, 'Fe ok(!defined connect_to_embedded_server($tmpdir2), "Not connected to different embedded server with datadir in $tmpdir2 while previous connection to embedded server are still active"); SKIP: { + skip 'No connection to real non-embedded server: Test suite uses embedded server only', 3 if test_dsn_uses_embedded_server(); my $dbh4 = connect_to_real_server(); - skip 'No connection to real non-embedded server', 3 unless defined $dbh4; - ok(defined $dbh4, 'Connected to real non-embedded server'); + skip 'No connection to real non-embeeded server: ' . get_error(), 3 if not defined $dbh4 and not $ENV{CONNECTION_TESTING}; + ok(defined $dbh4, 'Connected to real non-embedded server') or fatal_connection_error('real non-embedded'); ok(!defined $dbh4->selectrow_array('SHOW TABLES LIKE "dbd_mariadb_embedded"'), 'Real non-embedded server does not have tables from embedded server'); ok($dbh4->disconnect(), 'Disconnected from real non-embedded server'); } diff --git a/t/15reconnect.t b/t/15reconnect.t index 9c501c37..b6c4d82f 100644 --- a/t/15reconnect.t +++ b/t/15reconnect.t @@ -16,7 +16,7 @@ $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0 }); $dbh->disconnect(); -plan tests => 21 * 2; +plan tests => 30 * 2; for my $mariadb_server_prepare (0, 1) { $dbh= DBI->connect("$test_dsn;mariadb_server_prepare=$mariadb_server_prepare;mariadb_server_prepare_disable_fallback=1", $test_user, $test_password, @@ -38,12 +38,18 @@ ok($dbh->do("SELECT 1"), "implicitly reconnecting handle with 'do'"); ok($dbh->{Active}, "checking for reactivated handle"); +SKIP: { + +skip "Connection to Embedded server does not have socket", 3 if $dbh->{mariadb_hostinfo} eq 'Embedded'; + ok(shutdown_mariadb_socket($dbh), "shutdown socket handle"); ok($dbh->do("SELECT 1"), "implicitly reconnecting handle after shutdown with 'do'"); ok($dbh->{Active}, "checking for reactivated handle"); +} + ok($dbh->disconnect(), "disconnecting active handle"); ok(!$dbh->{Active}, "checking for inactive handle"); @@ -68,5 +74,34 @@ ok($dbh->{Active}, "checking for reactivated handle"); $sth->finish(); +ok($dbh->do("SET SESSION wait_timeout=1"), "set wait_timeout to 1"); + +note("call perl sleep 2 for implicit disconnect due to wait_timeout"); +sleep(2); + +ok($sth = $dbh->prepare("SELECT 1"), "implicitly reconnecting handle with preparing statement"); + +ok($sth->execute(), "execute prepared statement"); + +ok($dbh->{Active}, "checking for reactivated handle"); + +ok($sth = $dbh->prepare("SELECT 1"), "prepare statement"); + +note("call perl sleep 2 for implicit disconnect due to wait_timeout"); +sleep(2); + +ok($sth->execute(), "implicitly reconnecting handle with executing prepared statement"); + +ok($dbh->{Active}, "checking for reactivated handle"); + +$sth->finish(); + +note("call perl sleep 2 for implicit disconnect due to wait_timeout"); +sleep(2); + +ok($dbh->do("SELECT 1"), "implicitly reconnecting handle with 'do'"); + +ok($dbh->{Active}, "checking for reactivated handle"); + $dbh->disconnect(); } diff --git a/t/17close_on_exec.t b/t/17close_on_exec.t index c34e2389..d7b2acd0 100644 --- a/t/17close_on_exec.t +++ b/t/17close_on_exec.t @@ -10,6 +10,8 @@ require 'lib.pl'; my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0 }); +plan skip_all => "Connection to Embedded server does not have socket" if $dbh->{mariadb_hostinfo} eq 'Embedded'; + plan tests => 2; # Spawn new perl child process with MariaDB file descriptor passed as first argument and check that it is invalid (EBADF) in spawned process diff --git a/t/18begin_work_without_raise_error.t b/t/18begin_work_without_raise_error.t index 816cbcb4..605b563d 100644 --- a/t/18begin_work_without_raise_error.t +++ b/t/18begin_work_without_raise_error.t @@ -10,6 +10,8 @@ require 'lib.pl'; my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 0, PrintError => 1 }); +plan skip_all => "Connection to Embedded server does not have socket" if $dbh->{mariadb_hostinfo} eq 'Embedded'; + plan tests => 12; my $warn; diff --git a/t/78use_result.t b/t/78use_result.t index a0c903df..1d9723bd 100644 --- a/t/78use_result.t +++ b/t/78use_result.t @@ -4,6 +4,7 @@ use warnings; use Test::More; use Test::Deep; use DBI; +use DBI::Const::GetInfoType; use vars qw($test_dsn $test_user $test_password); use lib 't', '.'; @@ -11,7 +12,10 @@ require 'lib.pl'; my $dbh1 = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0 }); -plan tests => 12 + 2*2*2*1 + 3 * (2*2*2*13 + 2*2*2 + 2*2*9); +my $have_async_support = $dbh1->get_info($GetInfoType{'SQL_ASYNC_MODE'}); + +my $mult = ($have_async_support ? 2 : 1); +plan tests => 12 + 2*$mult*2*1 + 3 * (2*$mult*2*13 + 2*$mult*9) + ($have_async_support ? 3*2*2*2 : 0); $SIG{__WARN__} = sub { die @_ }; @@ -45,7 +49,7 @@ for my $multi_statements (0, 1) { $dbh->do('INSERT INTO t VALUES(1, 20)'); $dbh->do('INSERT INTO t VALUES(2, 30)'); - for my $async (0, 1) { + for my $async (0, ($have_async_support ? 1 : ())) { for my $use_result (0, 1) { diff --git a/t/87async.t b/t/87async.t index b65b6172..eb9afad5 100644 --- a/t/87async.t +++ b/t/87async.t @@ -16,7 +16,10 @@ my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, if ($dbh->{mariadb_serverversion} < 50012) { plan skip_all => "Servers < 5.0.12 do not support SLEEP()"; } -plan tests => 147; +if ($dbh->{mariadb_hostinfo} eq 'Embedded') { + plan skip_all => 'Async mode is not supported for Embedded server'; +} +plan tests => 150; is $dbh->get_info($GetInfoType{'SQL_ASYNC_MODE'}), 2; # statement-level async is $dbh->get_info($GetInfoType{'SQL_MAX_ASYNC_CONCURRENT_STATEMENTS'}), 1; @@ -46,7 +49,7 @@ cmp_ok(($end - $start), '>=', 1.9); $start = Time::HiRes::gettimeofday(); $rows = $dbh->do('INSERT INTO async_test VALUES (SLEEP(2), 0, 0)', { mariadb_async => 1 }); -ok(defined($dbh->mariadb_async_ready)) or die; +ok defined($dbh->mariadb_async_ready); $end = Time::HiRes::gettimeofday(); ok $rows; @@ -54,7 +57,11 @@ is $rows, '0E0'; cmp_ok(($end - $start), '<', 2); -sleep 1 until $dbh->mariadb_async_ready; +for (1..120) { + last if $dbh->mariadb_async_ready; + sleep 1; +} +ok $dbh->mariadb_async_ready; $end = Time::HiRes::gettimeofday(); cmp_ok(($end - $start), '>=', 1.9); @@ -65,7 +72,7 @@ is $rows, 1; $start = Time::HiRes::gettimeofday(); $rows = $dbh->do('INSERT INTO async_test VALUES (SLEEP(2), 0, 0)', { mariadb_async => 1 }); -ok(defined($dbh->mariadb_async_ready)) or die; +ok defined($dbh->mariadb_async_ready); $end = Time::HiRes::gettimeofday(); ok $rows; @@ -104,7 +111,11 @@ is $rows, '0E0'; cmp_ok(($end - $start), '<', 2); -sleep 1 until $dbh->mariadb_async_ready; +for (1..120) { + last if $dbh->mariadb_async_ready; + sleep 1; +} +ok $dbh->mariadb_async_ready; $end = Time::HiRes::gettimeofday(); cmp_ok(($end - $start), '>=', 1.9); @@ -139,7 +150,11 @@ $end = Time::HiRes::gettimeofday(); cmp_ok(($end - $start), '<', 2); ok $sth->{Active}; -sleep 1 until $sth->mariadb_async_ready; +for (1..120) { + last if $sth->mariadb_async_ready; + sleep 1; +} +ok $sth->mariadb_async_ready; ok $sth->{Active}; my $row = $sth->fetch; diff --git a/t/88async-multi-stmts.t b/t/88async-multi-stmts.t index aa4befa3..629624f5 100644 --- a/t/88async-multi-stmts.t +++ b/t/88async-multi-stmts.t @@ -10,6 +10,7 @@ require 'lib.pl'; my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 0, PrintError => 0, AutoCommit => 0, mariadb_multi_statements => 1 }); +plan skip_all => 'Async mode is not supported for Embedded server' if $dbh->{mariadb_hostinfo} eq 'Embedded'; plan tests => 104; ok $dbh->do(< 0, PrintError => 0, AutoCommit => 0 }); +plan skip_all => 'Async mode is not supported for Embedded server' if $dbh->{mariadb_hostinfo} eq 'Embedded'; plan tests => 2 * @db_safe_methods + 4 * @db_unsafe_methods + diff --git a/t/lib.pl b/t/lib.pl index 2139d144..b0ca8809 100644 --- a/t/lib.pl +++ b/t/lib.pl @@ -3,7 +3,7 @@ use Test::More; use FindBin qw($Bin); -use vars qw($test_dsn $test_user $test_password $test_db); +use vars qw($test_dsn $test_user $test_password $test_db $test_emboptions); use DBD::MariaDB; @@ -91,7 +91,10 @@ sub shutdown_mariadb_socket { # close automatically close C file descriptor in Perl file handle # mysql client library does not expect if somebody closes its file descriptors # so always take a copy of mariadb_sockfd() C file descriptor and just shutdown it - open my $socket, '+<&', $dbh->mariadb_sockfd(); + my $fd = $dbh->mariadb_sockfd(); + return undef unless defined $fd; + open my $socket, '+<&', $fd; + return undef unless defined $socket; my $ret = shutdown($socket, 2); close $socket; return $ret; diff --git a/t/rt110983-valid-mysqlfd.t b/t/rt110983-valid-mysqlfd.t index 914fd862..909d0d05 100644 --- a/t/rt110983-valid-mysqlfd.t +++ b/t/rt110983-valid-mysqlfd.t @@ -10,6 +10,8 @@ require "lib.pl"; my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0, AutoCommit => 0 }); +plan skip_all => "Connection to Embedded server does not have socket" if $dbh->{mariadb_hostinfo} eq 'Embedded'; + plan tests => 4; my $fd = $dbh->mariadb_sockfd; diff --git a/t/rt85919-fetch-lost-connection.t b/t/rt85919-fetch-lost-connection.t index f418a7c5..d1684d8d 100644 --- a/t/rt85919-fetch-lost-connection.t +++ b/t/rt85919-fetch-lost-connection.t @@ -8,6 +8,9 @@ require 'lib.pl'; my $dbh = DbiTestConnect($test_dsn, $test_user, $test_password, { RaiseError => 1, PrintError => 0, AutoCommit => 0 }); +if ($dbh->{mariadb_hostinfo} eq 'Embedded') { + plan skip_all => "Connection to Embedded server is not disconnected on wait_timeout"; +} my $sth; my $ok = eval { note "Connecting...\n";