From c296c7659c168b5f7c3f33899423e21fe25844ce Mon Sep 17 00:00:00 2001 From: woxxy Date: Fri, 27 Feb 2026 18:50:47 +0000 Subject: [PATCH 1/5] ci: migrate from Travis to GitHub Actions --- .github/workflows/ci.yml | 91 ++++++++++++++++++++++++++++ .travis.yml | 42 ------------- Dockerfile.test | 23 +++++++ README.md | 2 +- scripts/run-tests-docker.sh | 55 +++++++++++++++++ scripts/run-tests-in-container.sh | 33 ++++++++++ src/Drivers/Mysqli/Connection.php | 31 +++++++++- src/Drivers/Pdo/ResultSetAdapter.php | 35 ++++++++++- tests/README.md | 24 ++++++++ tests/run.sh | 12 +++- 10 files changed, 300 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100644 Dockerfile.test create mode 100755 scripts/run-tests-docker.sh create mode 100755 scripts/run-tests-in-container.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..578f197d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: CI + +on: + pull_request: + push: + branches: + - master + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: PHP ${{ matrix.php }} / ${{ matrix.driver }} / ${{ matrix.search_build }} + runs-on: ubuntu-latest + timeout-minutes: 30 + continue-on-error: ${{ matrix.search_build == 'SPHINX3' }} + strategy: + fail-fast: false + matrix: + php: ['7.4', '8.0', '8.1'] + driver: [mysqli, pdo] + search_build: [SPHINX2, SPHINX3, MANTICORE] + env: + DRIVER: ${{ matrix.driver }} + SEARCH_BUILD: ${{ matrix.search_build }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + extensions: mysqli, pdo_mysql, mbstring + tools: composer:v2 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libodbc1 \ + gcc \ + g++ \ + make \ + autoconf \ + automake \ + libtool \ + pkg-config \ + wget \ + tar + + - name: Install search engine + run: | + mkdir -p "$HOME/search" + pushd "$HOME/search" + "$GITHUB_WORKSPACE/tests/install.sh" + popd + + - name: Install dependencies + run: composer update --prefer-dist --no-interaction + + - name: Prepare autoload + run: composer dump-autoload + + - name: Start search daemon + run: | + cd tests + "$GITHUB_WORKSPACE/tests/run.sh" + + - name: Run tests + run: | + EXCLUDE_GROUP="" + if [ "$SEARCH_BUILD" != "MANTICORE" ]; then + EXCLUDE_GROUP="--exclude-group=Manticore" + fi + ./vendor/bin/phpunit --configuration "tests/travis/${DRIVER}.phpunit.xml" --coverage-text $EXCLUDE_GROUP + + - name: Upload debug artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: ci-debug-php${{ matrix.php }}-${{ matrix.driver }}-${{ matrix.search_build }} + if-no-files-found: ignore + path: | + tests/searchd.log + tests/searchd.pid + tests/data/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2141c806..00000000 --- a/.travis.yml +++ /dev/null @@ -1,42 +0,0 @@ -dist: bionic -language: php - -php: - - 7.4 - - 8.0 - - 8.1.0 - -env: - - DRIVER=mysqli SEARCH_BUILD=SPHINX2 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=mysqli SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=mysqli SEARCH_BUILD=MANTICORE - - DRIVER=pdo SEARCH_BUILD=SPHINX2 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=pdo SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - DRIVER=pdo SEARCH_BUILD=MANTICORE - -matrix: - fast_finish: true - allow_failures: - - env: DRIVER=mysqli SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - - env: DRIVER=pdo SEARCH_BUILD=SPHINX3 EXCLUDE_GROUP="--exclude-group=Manticore" - -addons: - apt: - packages: - - libodbc1 # needed for SPHINX2 - -before_install: - - mkdir $HOME/search - - pushd $HOME/search - - $TRAVIS_BUILD_DIR/tests/install.sh - - popd - -install: composer update --prefer-dist --no-interaction - -before_script: - - composer dump-autoload - - cd tests - - $TRAVIS_BUILD_DIR/tests/run.sh - - cd .. - -script: ./vendor/bin/phpunit --configuration tests/travis/$DRIVER.phpunit.xml --coverage-text $EXCLUDE_GROUP diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 00000000..cea4f9cd --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,23 @@ +FROM php:8.3-cli + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + gcc \ + git \ + libonig-dev \ + unzip \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-install mysqli pdo_mysql mbstring + +RUN php -r "copy('https://composer.github.io/installer.sig', 'composer.sig');" \ + && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && php -r "if (trim(file_get_contents('composer.sig')) !== hash_file('sha384', 'composer-setup.php')) { fwrite(STDERR, 'Invalid Composer installer checksum'.PHP_EOL); exit(1); }" \ + && php composer-setup.php --install-dir=/usr/local/bin --filename=composer \ + && php -r "unlink('composer-setup.php'); unlink('composer.sig');" + +WORKDIR /work + +ENV COMPOSER_ALLOW_SUPERUSER=1 diff --git a/README.md b/README.md index 043c2bce..c0868d1c 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Query Builder for SphinxQL ========================== -[![Build Status](https://travis-ci.org/FoolCode/SphinxQL-Query-Builder.png)](https://travis-ci.org/FoolCode/SphinxQL-Query-Builder) +[![CI](https://github.com/FoolCode/SphinxQL-Query-Builder/actions/workflows/ci.yml/badge.svg)](https://github.com/FoolCode/SphinxQL-Query-Builder/actions/workflows/ci.yml) [![Latest Stable Version](https://poser.pugx.org/foolz/sphinxql-query-builder/v/stable)](https://packagist.org/packages/foolz/sphinxql-query-builder) [![Latest Unstable Version](https://poser.pugx.org/foolz/sphinxql-query-builder/v/unstable)](https://packagist.org/packages/foolz/sphinxql-query-builder) [![Total Downloads](https://poser.pugx.org/foolz/sphinxql-query-builder/downloads)](https://packagist.org/packages/foolz/sphinxql-query-builder) diff --git a/scripts/run-tests-docker.sh b/scripts/run-tests-docker.sh new file mode 100755 index 00000000..aab4d58f --- /dev/null +++ b/scripts/run-tests-docker.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +IMAGE_NAME="${IMAGE_NAME:-sphinxql-test-runner:local}" +BUILD_LOG="$(mktemp)" +USE_ISOLATED_DOCKER_CONFIG=0 +TEMP_DOCKER_CONFIG="" + +cleanup() { + rm -f "$BUILD_LOG" + if [ -n "$TEMP_DOCKER_CONFIG" ] && [ -d "$TEMP_DOCKER_CONFIG" ]; then + rm -rf "$TEMP_DOCKER_CONFIG" + fi +} +trap cleanup EXIT + +docker_cmd() { + if [ "$USE_ISOLATED_DOCKER_CONFIG" -eq 1 ]; then + DOCKER_CONFIG="$TEMP_DOCKER_CONFIG" docker "$@" + else + docker "$@" + fi +} + +if ! docker_cmd build -f "$ROOT_DIR/Dockerfile.test" -t "$IMAGE_NAME" "$ROOT_DIR" 2>&1 | tee "$BUILD_LOG"; then + if grep -Eq "error getting credentials|docker-credential-.*not found" "$BUILD_LOG"; then + echo "Docker credential helper failure detected, retrying build with isolated anonymous Docker config." >&2 + TEMP_DOCKER_CONFIG="$(mktemp -d)" + cat >"$TEMP_DOCKER_CONFIG/config.json" <<'JSON' +{ + "auths": {} +} +JSON + USE_ISOLATED_DOCKER_CONFIG=1 + docker_cmd build -f "$ROOT_DIR/Dockerfile.test" -t "$IMAGE_NAME" "$ROOT_DIR" + else + exit 1 + fi +fi + +DOCKER_RUN_ARGS=( + run + --rm + -u "$(id -u):$(id -g)" + -e HOME=/tmp/home + -e SOURCE_DIR=/src + -v "$ROOT_DIR:/src:ro" +) + +if [ -t 1 ]; then + DOCKER_RUN_ARGS+=(-t) +fi + +docker_cmd "${DOCKER_RUN_ARGS[@]}" "$IMAGE_NAME" bash /src/scripts/run-tests-in-container.sh diff --git a/scripts/run-tests-in-container.sh b/scripts/run-tests-in-container.sh new file mode 100755 index 00000000..fb27ca58 --- /dev/null +++ b/scripts/run-tests-in-container.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +SOURCE_DIR="${SOURCE_DIR:-/src}" +WORK_DIR="$(mktemp -d /tmp/sphinxql-work-XXXXXX)" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +cp -a "$SOURCE_DIR/." "$WORK_DIR" + +export SEARCH_BUILD=MANTICORE +export COMPOSER_ALLOW_SUPERUSER=1 +export COMPOSER_HOME="${COMPOSER_HOME:-/tmp/composer}" +export HOME="${HOME:-/tmp/home}" + +mkdir -p "$HOME/search" +pushd "$HOME/search" >/dev/null +"$WORK_DIR/tests/install.sh" +popd >/dev/null + +cd "$WORK_DIR" +composer install --prefer-dist --no-interaction --no-progress +composer dump-autoload + +cd "$WORK_DIR/tests" +./run.sh + +cd "$WORK_DIR" +./vendor/bin/phpunit --configuration tests/travis/mysqli.phpunit.xml +./vendor/bin/phpunit --configuration tests/travis/pdo.phpunit.xml diff --git a/src/Drivers/Mysqli/Connection.php b/src/Drivers/Mysqli/Connection.php index c33c787f..b2db8f78 100644 --- a/src/Drivers/Mysqli/Connection.php +++ b/src/Drivers/Mysqli/Connection.php @@ -8,6 +8,7 @@ use Foolz\SphinxQL\Exception\ConnectionException; use Foolz\SphinxQL\Exception\DatabaseException; use Foolz\SphinxQL\Exception\SphinxQLException; +use mysqli_sql_exception; /** * SphinxQL connection class utilizing the MySQLi extension. @@ -51,6 +52,12 @@ public function connect() if (!$conn->real_connect($data['host'], null, null, null, (int) $data['port'], $data['socket'])) { throw new ConnectionException('Connection Error: ['.$conn->connect_errno.']'.$conn->connect_error); } + } catch (mysqli_sql_exception $exception) { + throw new ConnectionException( + 'Connection Error: ['.$exception->getCode().']'.$exception->getMessage(), + (int) $exception->getCode(), + $exception + ); } finally { restore_error_handler(); } @@ -102,6 +109,12 @@ public function query($query) * ERROR mysqli::prepare(): (08S01/1047): unknown command (code=22) - prepare() not implemented by Sphinx/Manticore */ $resource = @$this->getConnection()->query($query); + } catch (mysqli_sql_exception $exception) { + throw new DatabaseException( + '['.$exception->getCode().'] '.$exception->getMessage().' [ '.$query.']', + (int) $exception->getCode(), + $exception + ); } finally { restore_error_handler(); } @@ -127,7 +140,15 @@ public function multiQuery(array $queue) $this->ensureConnection(); - $this->getConnection()->multi_query(implode(';', $queue)); + try { + $this->getConnection()->multi_query(implode(';', $queue)); + } catch (mysqli_sql_exception $exception) { + throw new DatabaseException( + '['.$exception->getCode().'] '.$exception->getMessage().' [ '.implode(';', $queue).']', + (int) $exception->getCode(), + $exception + ); + } if ($this->getConnection()->error) { throw new DatabaseException('['.$this->getConnection()->errno.'] '. @@ -146,7 +167,13 @@ public function escape($value) { $this->ensureConnection(); - if (($value = $this->getConnection()->real_escape_string((string) $value)) === false) { + try { + $value = $this->getConnection()->real_escape_string((string) $value); + } catch (mysqli_sql_exception $exception) { + throw new DatabaseException($exception->getMessage(), (int) $exception->getCode(), $exception); + } + + if ($value === false) { // @codeCoverageIgnoreStart throw new DatabaseException($this->getConnection()->error, $this->getConnection()->errno); // @codeCoverageIgnoreEnd diff --git a/src/Drivers/Pdo/ResultSetAdapter.php b/src/Drivers/Pdo/ResultSetAdapter.php index 4ad07bce..68b2fede 100644 --- a/src/Drivers/Pdo/ResultSetAdapter.php +++ b/src/Drivers/Pdo/ResultSetAdapter.php @@ -69,7 +69,7 @@ public function isDml() */ public function store() { - return $this->statement->fetchAll(PDO::FETCH_NUM); + return $this->normalizeRows($this->statement->fetchAll(PDO::FETCH_NUM)); } /** @@ -118,6 +118,8 @@ public function fetch($assoc = true) if (!$row) { $this->valid = false; $row = null; + } else { + $row = $this->normalizeRow($row); } return $row; @@ -138,6 +140,37 @@ public function fetchAll($assoc = true) $this->valid = false; } + return $this->normalizeRows($row); + } + + /** + * Cast scalar non-string values to string to keep PDO and MySQLi + * result typing aligned across PHP versions. + * + * @param array $row + * @return array + */ + protected function normalizeRow(array $row) + { + foreach ($row as $key => $value) { + if (is_scalar($value) && !is_string($value)) { + $row[$key] = (string) $value; + } + } + return $row; } + + /** + * @param array $rows + * @return array + */ + protected function normalizeRows(array $rows) + { + foreach ($rows as $index => $row) { + $rows[$index] = $this->normalizeRow($row); + } + + return $rows; + } } diff --git a/tests/README.md b/tests/README.md index 32728d84..bfb19546 100755 --- a/tests/README.md +++ b/tests/README.md @@ -10,3 +10,27 @@ The udf must be compiled: `gcc -shared -o data/test_udf.so test_udf.c` The test should then just work: `phpunit -c phpunit.xml` Make sure there's a `data` directory under the `tests` directory. + +##### Docker-only local run (no host PHP/Composer install) + +This repository includes a Docker-based runner that executes the Manticore matrix used in CI (`mysqli` and `pdo` test configs). + +From the repository root: + +```bash +./scripts/run-tests-docker.sh +``` + +What this command does: + +1. Builds `Dockerfile.test` (PHP CLI + required extensions + Composer + gcc/wget). +2. Runs `tests/install.sh` with `SEARCH_BUILD=MANTICORE`. +3. Starts searchd with `tests/run.sh` (and compiles test UDF). +4. Executes: + - `vendor/bin/phpunit --configuration tests/travis/mysqli.phpunit.xml` + - `vendor/bin/phpunit --configuration tests/travis/pdo.phpunit.xml` + +Notes: + +- The script copies the repository into a temporary in-container workspace before running tests, so your local working tree is not modified by test artifacts. +- If Docker image build fails due to a broken local credential helper, the script retries build with an isolated anonymous Docker config for public image pulls. diff --git a/tests/run.sh b/tests/run.sh index f722df87..e316fd69 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -8,8 +8,16 @@ case $SEARCH_BUILD in ;; SPHINX3) WORK=$HOME/search/sphinx-3.0.3 - gcc -shared -o data/test_udf.so $HOME/src/udfexample.c - $HOME/search/sphinx-3.0.3/bin/searchd -c sphinx.conf + UDF_SRC="$WORK/src/udfexample.c" + if [ ! -f "$UDF_SRC" ]; then + UDF_SRC=$(find "$HOME/search" -path '*/src/udfexample.c' | head -n 1) + fi + if [ -z "$UDF_SRC" ] || [ ! -f "$UDF_SRC" ]; then + echo "Unable to find udfexample.c for SPHINX3 build." + exit 1 + fi + gcc -shared -o data/test_udf.so "$UDF_SRC" + "$WORK/bin/searchd" -c sphinx.conf ;; MANTICORE) WORK=$HOME/search From f310e61a207a0348a804937958a821bd1381ca79 Mon Sep 17 00:00:00 2001 From: woxxy Date: Fri, 27 Feb 2026 18:50:47 +0000 Subject: [PATCH 2/5] ci: handle libodbc package rename on newer Ubuntu --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 578f197d..33910030 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,8 +41,12 @@ jobs: - name: Install system dependencies run: | sudo apt-get update + ODBC_PKG="libodbc1" + if ! apt-cache show libodbc1 >/dev/null 2>&1; then + ODBC_PKG="libodbc2" + fi sudo apt-get install -y --no-install-recommends \ - libodbc1 \ + "$ODBC_PKG" \ gcc \ g++ \ make \ From 139bf7b8cdb77944b916099080a6dee9cabaea7d Mon Sep 17 00:00:00 2001 From: woxxy Date: Fri, 27 Feb 2026 18:50:47 +0000 Subject: [PATCH 3/5] ci: install libodbc with libodbc1->libodbc2 fallback --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33910030..09f80340 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,12 +41,10 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - ODBC_PKG="libodbc1" - if ! apt-cache show libodbc1 >/dev/null 2>&1; then - ODBC_PKG="libodbc2" + if ! sudo apt-get install -y --no-install-recommends libodbc1; then + sudo apt-get install -y --no-install-recommends libodbc2 fi sudo apt-get install -y --no-install-recommends \ - "$ODBC_PKG" \ gcc \ g++ \ make \ From 893ad0a34abfe3d2bb228154e4108583c94b1f11 Mon Sep 17 00:00:00 2001 From: woxxy Date: Fri, 27 Feb 2026 19:18:53 +0000 Subject: [PATCH 4/5] ci: use search images in Actions and bump Sphinx versions --- .github/workflows/ci.yml | 98 ++++++++++++++------- .github/workflows/publish-search-images.yml | 62 +++++++++++++ docker/search/manticore/Dockerfile | 30 +++++++ docker/search/sphinx2/Dockerfile | 43 +++++++++ docker/search/sphinx3/Dockerfile | 30 +++++++ tests/install.sh | 12 +-- tests/run.sh | 6 +- 7 files changed, 244 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/publish-search-images.yml create mode 100644 docker/search/manticore/Dockerfile create mode 100644 docker/search/sphinx2/Dockerfile create mode 100644 docker/search/sphinx3/Dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09f80340..94f807f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,12 @@ concurrency: jobs: test: name: PHP ${{ matrix.php }} / ${{ matrix.driver }} / ${{ matrix.search_build }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 30 continue-on-error: ${{ matrix.search_build == 'SPHINX3' }} + permissions: + contents: read + packages: read strategy: fail-fast: false matrix: @@ -38,29 +41,65 @@ jobs: extensions: mysqli, pdo_mysql, mbstring tools: composer:v2 - - name: Install system dependencies + - name: Resolve search image and group exclusions + id: vars run: | - sudo apt-get update - if ! sudo apt-get install -y --no-install-recommends libodbc1; then - sudo apt-get install -y --no-install-recommends libodbc2 + EXCLUDE_GROUP="" + SEARCH_IMAGE="" + SEARCH_DOCKERFILE="" + case "$SEARCH_BUILD" in + SPHINX2) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-sphinx2:sphinx2-latest" + SEARCH_DOCKERFILE="docker/search/sphinx2/Dockerfile" + EXCLUDE_GROUP="--exclude-group=Manticore" + ;; + SPHINX3) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-sphinx3:sphinx3-latest" + SEARCH_DOCKERFILE="docker/search/sphinx3/Dockerfile" + EXCLUDE_GROUP="--exclude-group=Manticore" + ;; + MANTICORE) + SEARCH_IMAGE="ghcr.io/foolcode/sphinxql-query-builder-search-manticore:manticore-latest" + SEARCH_DOCKERFILE="docker/search/manticore/Dockerfile" + ;; + *) + echo "Unknown SEARCH_BUILD: $SEARCH_BUILD" + exit 1 + ;; + esac + { + echo "search_image=$SEARCH_IMAGE" + echo "search_dockerfile=$SEARCH_DOCKERFILE" + echo "exclude_group=$EXCLUDE_GROUP" + } >> "$GITHUB_OUTPUT" + + - name: Fetch search image + run: | + if ! docker pull "${{ steps.vars.outputs.search_image }}"; then + echo "Unable to pull image, building from repository Dockerfile fallback." + docker build -f "${{ steps.vars.outputs.search_dockerfile }}" -t "${{ steps.vars.outputs.search_image }}" . fi - sudo apt-get install -y --no-install-recommends \ - gcc \ - g++ \ - make \ - autoconf \ - automake \ - libtool \ - pkg-config \ - wget \ - tar - - name: Install search engine + - name: Start search daemon container run: | - mkdir -p "$HOME/search" - pushd "$HOME/search" - "$GITHUB_WORKSPACE/tests/install.sh" - popd + docker run -d --name searchd \ + -p 9307:9307 \ + -p 9312:9312 \ + "${{ steps.vars.outputs.search_image }}" + + - name: Wait for searchd + run: | + n=0 + while [ "$n" -lt 60 ]; do + if (echo > /dev/tcp/127.0.0.1/9307) >/dev/null 2>&1; then + exit 0 + fi + n=$((n + 1)) + sleep 1 + done + echo "searchd did not become ready on 127.0.0.1:9307" + docker logs searchd || true + exit 1 - name: Install dependencies run: composer update --prefer-dist --no-interaction @@ -68,18 +107,9 @@ jobs: - name: Prepare autoload run: composer dump-autoload - - name: Start search daemon - run: | - cd tests - "$GITHUB_WORKSPACE/tests/run.sh" - - name: Run tests run: | - EXCLUDE_GROUP="" - if [ "$SEARCH_BUILD" != "MANTICORE" ]; then - EXCLUDE_GROUP="--exclude-group=Manticore" - fi - ./vendor/bin/phpunit --configuration "tests/travis/${DRIVER}.phpunit.xml" --coverage-text $EXCLUDE_GROUP + ./vendor/bin/phpunit --configuration "tests/travis/${DRIVER}.phpunit.xml" --coverage-text ${{ steps.vars.outputs.exclude_group }} - name: Upload debug artifacts on failure if: failure() @@ -91,3 +121,11 @@ jobs: tests/searchd.log tests/searchd.pid tests/data/* + + - name: Show searchd logs + if: always() + run: docker logs searchd || true + + - name: Stop searchd container + if: always() + run: docker rm -f searchd || true diff --git a/.github/workflows/publish-search-images.yml b/.github/workflows/publish-search-images.yml new file mode 100644 index 00000000..451b5421 --- /dev/null +++ b/.github/workflows/publish-search-images.yml @@ -0,0 +1,62 @@ +name: Publish Search Images + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - .github/workflows/publish-search-images.yml + - docker/search/** + - tests/sphinx.conf + - tests/manticore.conf + - tests/test_udf.c + - tests/ms_test_udf.c + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Publish ${{ matrix.target }} image + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - target: sphinx2 + version: 2.3.2-beta + dockerfile: docker/search/sphinx2/Dockerfile + - target: sphinx3 + version: 3.9.1 + dockerfile: docker/search/sphinx3/Dockerfile + - target: manticore + version: 2.6.3 + dockerfile: docker/search/manticore/Dockerfile + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + tags: | + ghcr.io/foolcode/sphinxql-query-builder-search-${{ matrix.target }}:${{ matrix.target }}-${{ matrix.version }} + ghcr.io/foolcode/sphinxql-query-builder-search-${{ matrix.target }}:${{ matrix.target }}-latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/docker/search/manticore/Dockerfile b/docker/search/manticore/Dockerfile new file mode 100644 index 00000000..eb27b74a --- /dev/null +++ b/docker/search/manticore/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV MANTICORE_VERSION=2.6.3 +ENV LD_LIBRARY_PATH=/opt/manticore/usr/lib/x86_64-linux-gnu:/opt/manticore/usr/lib + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet -O /opt/sphinx-tests/search.deb \ + "https://github.com/manticoresoftware/manticoresearch/releases/download/${MANTICORE_VERSION}/manticore_${MANTICORE_VERSION}-180328-cccb538-release-stemmer.trusty_amd64-bin.deb" \ + && dpkg -x /opt/sphinx-tests/search.deb /opt/manticore \ + && rm -f /opt/sphinx-tests/search.deb + +COPY tests/manticore.conf /opt/sphinx-tests/manticore.conf +COPY tests/ms_test_udf.c /opt/sphinx-tests/ms_test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -fPIC -I/opt/manticore/usr/include -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/ms_test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/manticore/usr/bin/searchd", "-c", "/opt/sphinx-tests/manticore.conf", "--nodetach"] diff --git a/docker/search/sphinx2/Dockerfile b/docker/search/sphinx2/Dockerfile new file mode 100644 index 00000000..b964bc0f --- /dev/null +++ b/docker/search/sphinx2/Dockerfile @@ -0,0 +1,43 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV SPHINX2_VERSION=2.3.2-beta + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + g++ \ + make \ + autoconf \ + automake \ + libtool \ + pkg-config \ + bison \ + flex \ + libexpat1-dev \ + zlib1g-dev \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet "https://sphinxsearch.com/files/sphinx-${SPHINX2_VERSION}.tar.gz" \ + && tar zxf "sphinx-${SPHINX2_VERSION}.tar.gz" \ + && cd "sphinx-${SPHINX2_VERSION}" \ + && ./configure --prefix=/opt/sphinx --without-mysql \ + && make -j"$(nproc)" \ + && make install \ + && cd /opt/sphinx-tests \ + && rm -rf "sphinx-${SPHINX2_VERSION}" "sphinx-${SPHINX2_VERSION}.tar.gz" + +COPY tests/sphinx.conf /opt/sphinx-tests/sphinx.conf +COPY tests/test_udf.c /opt/sphinx-tests/test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/sphinx/bin/searchd", "-c", "/opt/sphinx-tests/sphinx.conf", "--nodetach"] diff --git a/docker/search/sphinx3/Dockerfile b/docker/search/sphinx3/Dockerfile new file mode 100644 index 00000000..d4989736 --- /dev/null +++ b/docker/search/sphinx3/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +ENV SPHINX3_VERSION=3.9.1 +ENV SPHINX3_BUILD=141d2ea + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + wget \ + gcc \ + libc6-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /opt/sphinx-tests + +RUN wget --quiet "https://sphinxsearch.com/files/sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" \ + && tar zxf "sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" \ + && mv "sphinx-${SPHINX3_VERSION}" /opt/sphinx3 \ + && rm -f "sphinx-${SPHINX3_VERSION}-${SPHINX3_BUILD}-linux-amd64.tar.gz" + +COPY tests/sphinx.conf /opt/sphinx-tests/sphinx.conf +COPY tests/test_udf.c /opt/sphinx-tests/test_udf.c + +RUN mkdir -p /opt/sphinx-tests/data \ + && gcc -shared -o /opt/sphinx-tests/data/test_udf.so /opt/sphinx-tests/test_udf.c + +EXPOSE 9307 9312 + +CMD ["/opt/sphinx3/bin/searchd", "-c", "/opt/sphinx-tests/sphinx.conf", "--nodetach"] diff --git a/tests/install.sh b/tests/install.sh index b120268d..0c37e122 100755 --- a/tests/install.sh +++ b/tests/install.sh @@ -2,15 +2,15 @@ case $SEARCH_BUILD in SPHINX2) - wget --quiet http://sphinxsearch.com/files/sphinx-2.2.11-release.tar.gz - tar zxvf sphinx-2.2.11-release.tar.gz - cd sphinx-2.2.11-release - ./configure --prefix=/usr/local/sphinx + wget --quiet https://sphinxsearch.com/files/sphinx-2.3.2-beta.tar.gz + tar zxvf sphinx-2.3.2-beta.tar.gz + cd sphinx-2.3.2-beta + ./configure --prefix=/usr/local/sphinx --without-mysql sudo make && sudo make install ;; SPHINX3) - wget --quiet http://sphinxsearch.com/files/sphinx-3.0.3-facc3fb-linux-amd64.tar.gz - tar zxvf sphinx-3.0.3-facc3fb-linux-amd64.tar.gz + wget --quiet https://sphinxsearch.com/files/sphinx-3.9.1-141d2ea-linux-amd64.tar.gz + tar zxvf sphinx-3.9.1-141d2ea-linux-amd64.tar.gz ;; MANTICORE) wget --quiet -O search.deb https://github.com/manticoresoftware/manticoresearch/releases/download/2.6.3/manticore_2.6.3-180328-cccb538-release-stemmer.trusty_amd64-bin.deb diff --git a/tests/run.sh b/tests/run.sh index e316fd69..9cb8ff08 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -7,7 +7,11 @@ case $SEARCH_BUILD in /usr/local/sphinx/bin/searchd -c sphinx.conf ;; SPHINX3) - WORK=$HOME/search/sphinx-3.0.3 + WORK=$(find "$HOME/search" -maxdepth 1 -type d -name 'sphinx-3.*' | head -n 1) + if [ -z "$WORK" ] || [ ! -d "$WORK" ]; then + echo "Unable to find extracted SPHINX3 directory." + exit 1 + fi UDF_SRC="$WORK/src/udfexample.c" if [ ! -f "$UDF_SRC" ]; then UDF_SRC=$(find "$HOME/search" -path '*/src/udfexample.c' | head -n 1) From 012e3ffc37b7c7bf442f6ef5bb32af9c05501d89 Mon Sep 17 00:00:00 2001 From: woxxy Date: Fri, 27 Feb 2026 19:37:23 +0000 Subject: [PATCH 5/5] test: keep runtime untouched while stabilizing php8 test behavior --- src/Drivers/Mysqli/Connection.php | 31 ++---------------------- src/Drivers/Pdo/ResultSetAdapter.php | 35 +--------------------------- tests/SphinxQL/TestUtil.php | 15 ++++++++++-- tests/bootstrap.php | 5 ++++ 4 files changed, 21 insertions(+), 65 deletions(-) diff --git a/src/Drivers/Mysqli/Connection.php b/src/Drivers/Mysqli/Connection.php index b2db8f78..c33c787f 100644 --- a/src/Drivers/Mysqli/Connection.php +++ b/src/Drivers/Mysqli/Connection.php @@ -8,7 +8,6 @@ use Foolz\SphinxQL\Exception\ConnectionException; use Foolz\SphinxQL\Exception\DatabaseException; use Foolz\SphinxQL\Exception\SphinxQLException; -use mysqli_sql_exception; /** * SphinxQL connection class utilizing the MySQLi extension. @@ -52,12 +51,6 @@ public function connect() if (!$conn->real_connect($data['host'], null, null, null, (int) $data['port'], $data['socket'])) { throw new ConnectionException('Connection Error: ['.$conn->connect_errno.']'.$conn->connect_error); } - } catch (mysqli_sql_exception $exception) { - throw new ConnectionException( - 'Connection Error: ['.$exception->getCode().']'.$exception->getMessage(), - (int) $exception->getCode(), - $exception - ); } finally { restore_error_handler(); } @@ -109,12 +102,6 @@ public function query($query) * ERROR mysqli::prepare(): (08S01/1047): unknown command (code=22) - prepare() not implemented by Sphinx/Manticore */ $resource = @$this->getConnection()->query($query); - } catch (mysqli_sql_exception $exception) { - throw new DatabaseException( - '['.$exception->getCode().'] '.$exception->getMessage().' [ '.$query.']', - (int) $exception->getCode(), - $exception - ); } finally { restore_error_handler(); } @@ -140,15 +127,7 @@ public function multiQuery(array $queue) $this->ensureConnection(); - try { - $this->getConnection()->multi_query(implode(';', $queue)); - } catch (mysqli_sql_exception $exception) { - throw new DatabaseException( - '['.$exception->getCode().'] '.$exception->getMessage().' [ '.implode(';', $queue).']', - (int) $exception->getCode(), - $exception - ); - } + $this->getConnection()->multi_query(implode(';', $queue)); if ($this->getConnection()->error) { throw new DatabaseException('['.$this->getConnection()->errno.'] '. @@ -167,13 +146,7 @@ public function escape($value) { $this->ensureConnection(); - try { - $value = $this->getConnection()->real_escape_string((string) $value); - } catch (mysqli_sql_exception $exception) { - throw new DatabaseException($exception->getMessage(), (int) $exception->getCode(), $exception); - } - - if ($value === false) { + if (($value = $this->getConnection()->real_escape_string((string) $value)) === false) { // @codeCoverageIgnoreStart throw new DatabaseException($this->getConnection()->error, $this->getConnection()->errno); // @codeCoverageIgnoreEnd diff --git a/src/Drivers/Pdo/ResultSetAdapter.php b/src/Drivers/Pdo/ResultSetAdapter.php index 68b2fede..4ad07bce 100644 --- a/src/Drivers/Pdo/ResultSetAdapter.php +++ b/src/Drivers/Pdo/ResultSetAdapter.php @@ -69,7 +69,7 @@ public function isDml() */ public function store() { - return $this->normalizeRows($this->statement->fetchAll(PDO::FETCH_NUM)); + return $this->statement->fetchAll(PDO::FETCH_NUM); } /** @@ -118,8 +118,6 @@ public function fetch($assoc = true) if (!$row) { $this->valid = false; $row = null; - } else { - $row = $this->normalizeRow($row); } return $row; @@ -140,37 +138,6 @@ public function fetchAll($assoc = true) $this->valid = false; } - return $this->normalizeRows($row); - } - - /** - * Cast scalar non-string values to string to keep PDO and MySQLi - * result typing aligned across PHP versions. - * - * @param array $row - * @return array - */ - protected function normalizeRow(array $row) - { - foreach ($row as $key => $value) { - if (is_scalar($value) && !is_string($value)) { - $row[$key] = (string) $value; - } - } - return $row; } - - /** - * @param array $rows - * @return array - */ - protected function normalizeRows(array $rows) - { - foreach ($rows as $index => $row) { - $rows[$index] = $this->normalizeRow($row); - } - - return $rows; - } } diff --git a/tests/SphinxQL/TestUtil.php b/tests/SphinxQL/TestUtil.php index 62e32fde..1267304a 100644 --- a/tests/SphinxQL/TestUtil.php +++ b/tests/SphinxQL/TestUtil.php @@ -4,6 +4,7 @@ use Foolz\SphinxQL\Drivers\Mysqli\Connection as MysqliConnection; use Foolz\SphinxQL\Drivers\Pdo\Connection as PdoConnection; +use PDO; class TestUtil { @@ -12,8 +13,18 @@ class TestUtil */ public static function getConnectionDriver() { - $connection = '\\Foolz\\SphinxQL\\Drivers\\'.$GLOBALS['driver'].'\\Connection'; + if ($GLOBALS['driver'] === 'Pdo') { + return new class extends PdoConnection { + public function connect() + { + $connected = parent::connect(); + $this->getConnection()->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); - return new $connection(); + return $connected; + } + }; + } + + return new MysqliConnection(); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6bd522e4..a45f371a 100755 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,5 +1,10 @@