diff --git a/.github/workflows/mysql-proxy-tests.yml b/.github/workflows/mysql-proxy-tests.yml index c3808cf1..cb9b71e5 100644 --- a/.github/workflows/mysql-proxy-tests.yml +++ b/.github/workflows/mysql-proxy-tests.yml @@ -26,8 +26,8 @@ jobs: with: ignore-cache: "yes" composer-options: "--optimize-autoloader" - working-directory: packages/wp-mysql-proxy + working-directory: packages/mysql-proxy - name: Run MySQL Proxy tests run: composer run test - working-directory: packages/wp-mysql-proxy + working-directory: packages/mysql-proxy diff --git a/.github/workflows/phpunit-tests-run.yml b/.github/workflows/phpunit-tests-run.yml index 43c38bc9..e492d4c4 100644 --- a/.github/workflows/phpunit-tests-run.yml +++ b/.github/workflows/phpunit-tests-run.yml @@ -72,11 +72,22 @@ jobs: exit 1 fi - - name: Install Composer dependencies + - name: Install Composer dependencies (root) uses: ramsey/composer-install@v3 with: ignore-cache: "yes" composer-options: "--optimize-autoloader" + - name: Install Composer dependencies (mysql-on-sqlite) + uses: ramsey/composer-install@v3 + with: + working-directory: packages/mysql-on-sqlite + ignore-cache: "yes" + composer-options: "--optimize-autoloader" + - name: Run PHPUnit tests - run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist + run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist + working-directory: packages/mysql-on-sqlite + + - name: Run PHPUnit tests for the legacy driver + run: php ./vendor/bin/phpunit -c ./phpunit.xml.dist diff --git a/.github/workflows/release-prepare.yml b/.github/workflows/release-prepare.yml new file mode 100644 index 00000000..ea8e5fe0 --- /dev/null +++ b/.github/workflows/release-prepare.yml @@ -0,0 +1,149 @@ +name: Prepare release + +on: + release: + types: [created, edited] + +jobs: + prepare-release: + name: Prepare release PR and build plugin zip + if: github.event.release.draft == true + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: trunk + + - name: Extract version and changelog + id: release_info + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + RELEASE_BODY: ${{ github.event.release.body }} + run: | + # Extract version from tag (strip leading "v" if present). + VERSION="${RELEASE_TAG#v}" + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then + echo "::error::Invalid version format: '$VERSION' (expected semver like '1.2.3')" + exit 1 + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Extract changelog, converting markdown list items to WP readme format. + CHANGELOG=$(echo "$RELEASE_BODY" | sed 's/^- /* /' | sed 's/^[[:space:]]*- /* /') + { + echo "changelog<> $GITHUB_OUTPUT + + # Branch name for the release PR. + echo "branch=release/v$VERSION" >> $GITHUB_OUTPUT + + - name: Create release branch + env: + BRANCH: ${{ steps.release_info.outputs.branch }} + run: | + git checkout -B "$BRANCH" + + - name: Bump version numbers + env: + VERSION: ${{ steps.release_info.outputs.version }} + run: | + # Update version.php + sed -i "s/define( 'SQLITE_DRIVER_VERSION', '.*' );/define( 'SQLITE_DRIVER_VERSION', '$VERSION' );/" \ + packages/mysql-on-sqlite/src/version.php + + # Update plugin header + sed -i "s/^\( \* Version:\).*/\1 $VERSION/" \ + packages/plugin-sqlite-database-integration/load.php + + # Update readme.txt stable tag + sed -i "s/^Stable tag:.*/Stable tag: $VERSION/" \ + packages/plugin-sqlite-database-integration/readme.txt + + - name: Update changelog in readme.txt + env: + VERSION: ${{ steps.release_info.outputs.version }} + CHANGELOG: ${{ steps.release_info.outputs.changelog }} + run: | + README="packages/plugin-sqlite-database-integration/readme.txt" + + # Build the new changelog entry. + ENTRY=$(printf "= %s =\n\n%s\n" "$VERSION" "$CHANGELOG") + + if grep -q "^== Changelog ==" "$README"; then + # Insert the new entry after the == Changelog == header. + awk -v entry="$ENTRY" ' + /^== Changelog ==/ { print; print ""; print entry; next } + { print } + ' "$README" > "$README.tmp" && mv "$README.tmp" "$README" + else + # Add a changelog section at the end. + printf "\n== Changelog ==\n\n%s\n" "$ENTRY" >> "$README" + fi + + - name: Commit version bump + env: + VERSION: ${{ steps.release_info.outputs.version }} + BRANCH: ${{ steps.release_info.outputs.branch }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "Bump version to $VERSION" + git push -f origin "$BRANCH" + + - name: Build plugin zip + run: composer run build + + - name: Upload zip to draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + # Remove any previously uploaded zip. + gh release delete-asset "$RELEASE_TAG" "sqlite-database-integration.zip" --yes 2>/dev/null || true + # Upload the new zip. + gh release upload "$RELEASE_TAG" "build/sqlite-database-integration.zip" + + - name: Create or update pull request + id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.release_info.outputs.version }} + BRANCH: ${{ steps.release_info.outputs.branch }} + run: | + # Check if a PR already exists for this branch. + EXISTING_PR=$(gh pr list --head "$BRANCH" --base trunk --json number --jq '.[0].number // empty') + + if [ -n "$EXISTING_PR" ]; then + PR_URL=$(gh pr view "$EXISTING_PR" --json url --jq '.url') + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + else + PR_URL=$(gh pr create \ + --base trunk \ + --head "$BRANCH" \ + --title "Release $VERSION" \ + --body "Version bump and changelog update for release $VERSION.") + echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT + fi + + - name: Add PR link to draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + RELEASE_BODY: ${{ github.event.release.body }} + PR_URL: ${{ steps.pr.outputs.pr_url }} + run: | + PR_NOTE="> To publish the release, review and merge: $PR_URL" + + # Remove any existing PR note, then append the new one. + CLEAN_BODY=$(echo "$RELEASE_BODY" | grep -v "^> To publish the release, review and merge:") + NEW_BODY=$(printf "%s\n\n%s" "$CLEAN_BODY" "$PR_NOTE") + + gh release edit "$RELEASE_TAG" --notes "$NEW_BODY" diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml new file mode 100644 index 00000000..03daf5ac --- /dev/null +++ b/.github/workflows/release-publish.yml @@ -0,0 +1,49 @@ +name: Publish release + +on: + pull_request: + types: [closed] + branches: [trunk] + +jobs: + publish-release: + name: Publish draft release + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Extract version from branch name + id: version + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: | + # Extract version from branch name (release/v2.3.0 → 2.3.0). + VERSION="${BRANCH#release/v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + + - name: Publish draft release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + run: | + # Get the current release body and remove the PR note. + BODY=$(gh release view "$TAG" --json body --jq '.body' 2>/dev/null || echo "") + CLEAN_BODY=$(echo "$BODY" | grep -v "^> To publish the release, review and merge:") + + # Publish the release, targeting the merge commit. + gh release edit "$TAG" \ + --draft=false \ + --target "$MERGE_SHA" \ + --notes "$CLEAN_BODY" + + - name: Delete release branch + env: + BRANCH: ${{ github.event.pull_request.head.ref }} + run: git push origin --delete "$BRANCH" 2>/dev/null || true diff --git a/.github/workflows/release-wporg.yml b/.github/workflows/release-wporg.yml new file mode 100644 index 00000000..261f6caa --- /dev/null +++ b/.github/workflows/release-wporg.yml @@ -0,0 +1,32 @@ +name: Deploy to WordPress.org + +on: + release: + types: [published] + +jobs: + deploy: + name: Deploy plugin to WordPress.org + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Download plugin zip from release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + gh release download "$RELEASE_TAG" \ + --repo "${{ github.repository }}" \ + --pattern "sqlite-database-integration.zip" \ + --dir . + unzip sqlite-database-integration.zip + + - name: Deploy to WordPress.org + uses: 10up/action-wordpress-plugin-deploy@v2 + env: + SVN_USERNAME: ${{ secrets.SVN_USERNAME }} + SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }} + SLUG: sqlite-database-integration + BUILD_DIR: sqlite-database-integration diff --git a/.github/workflows/verify-version.yml b/.github/workflows/verify-version.yml index 9bc9a617..9348c919 100644 --- a/.github/workflows/verify-version.yml +++ b/.github/workflows/verify-version.yml @@ -16,13 +16,13 @@ jobs: - name: Extract version from "load.php" id: load_version run: | - VERSION=$(grep "Version:" load.php | sed "s/.*Version: \([^ ]*\).*/\1/") + VERSION=$(grep "Version:" packages/plugin-sqlite-database-integration/load.php | sed "s/.*Version: \([^ ]*\).*/\1/") echo "load_version=$VERSION" >> $GITHUB_OUTPUT - name: Extract version from "version.php" id: const_version run: | - VERSION=$(php -r "require 'version.php'; echo SQLITE_DRIVER_VERSION;") + VERSION=$(php -r "require 'packages/mysql-on-sqlite/src/version.php'; echo SQLITE_DRIVER_VERSION;") echo "const_version=$VERSION" >> $GITHUB_OUTPUT - name: Compare versions diff --git a/.gitignore b/.gitignore index 0947ee4b..23c504c7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ composer.lock ._.DS_Store .DS_Store ._* +/build /wordpress /.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md index 910d8160..d46b2858 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,11 @@ It is a monorepo that includes the following components: - **WordPress plugin** — A plugin that adds SQLite support to WordPress. - **Test suites** — A set of extensive test suites to cover MySQL syntax and functionality. +The monorepo packages are placed under the `packages` directory. + +The WordPress plugin links the SQLite driver using a symlink. The build script +replaces the symlink with a copy of the driver for release. + The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5, MySQL syntax from version 5.7 onward, and requires SQLite 3.37.0 or newer (with legacy mode down to 3.27.0). @@ -40,8 +45,15 @@ The following commands are useful for development and testing: composer install # Install dependencies composer run check-cs # Check coding standards (PHPCS) composer run fix-cs # Auto-fix coding standards (PHPCBF) +composer run build # Build the plugin zip -# Tests +# SQLite driver tests (under packages/mysql-on-sqlite) +cd packages/mysql-on-sqlite +composer run test # Run unit tests +composer run test tests/SomeTest.php # Run specific unit test file +composer run test -- --filter testName # Run specific unit test class/method + +# SQLite Database Integration plugin tests composer run test # Run unit tests composer run test tests/SomeTest.php # Run specific unit test file composer run test -- --filter testName # Run specific unit test class/method @@ -56,6 +68,20 @@ composer run wp-test-e2e # Run WordPress E2E tests (Playwright) composer run wp-test-clean # Clean up WordPress environment (Docker and DB) ``` +## Release workflow +Release is automated with GitHub Actions. It requires only two manual steps: + +1. **Create a draft release with a new tag and changelog.** + The `release-prepare` workflow will automatically: + - Use the draft tag to bump versions in `version.php`, `load.php`, and `readme.txt`. + - Add the draft release changelog entry to `readme.txt`. + - Build the plugin ZIP and attach it to the draft release. + - Create a PR (`release/` → `trunk`) and link it in the draft release body. +2. **Review and merge the PR.** + The `release-publish` workflow will automatically: + - Publish the draft release. + - Publish the release artifact to WordPress.org. + ## Architecture The project consists of multiple components providing different APIs that funnel into the SQLite driver to support diverse use cases both inside and outside the diff --git a/README.md b/README.md index 2312a095..4686288d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ It is a monorepo that includes the following components: - **WordPress plugin** — A plugin that adds SQLite support to WordPress. - **Test suites** — A set of extensive test suites to cover MySQL syntax and functionality. +The monorepo packages are placed under the `packages` directory. + +The WordPress plugin links the SQLite driver using a symlink. The build script +replaces the symlink with a copy of the driver for release. + The codebase is pure PHP with zero dependencies. It supports PHP 7.2 through 8.5, MySQL syntax from version 5.7 onward, and requires SQLite 3.37.0 or newer (with legacy mode down to 3.27.0). @@ -29,8 +34,15 @@ The following commands are useful for development and testing: composer install # Install dependencies composer run check-cs # Check coding standards (PHPCS) composer run fix-cs # Auto-fix coding standards (PHPCBF) +composer run build # Build the plugin zip + +# SQLite driver tests (under packages/mysql-on-sqlite) +cd packages/mysql-on-sqlite +composer run test # Run unit tests +composer run test tests/SomeTest.php # Run specific unit test file +composer run test -- --filter testName # Run specific unit test class/method -# Tests +# SQLite Database Integration plugin tests composer run test # Run unit tests composer run test tests/SomeTest.php # Run specific unit test file composer run test -- --filter testName # Run specific unit test class/method @@ -45,6 +57,20 @@ composer run wp-test-e2e # Run WordPress E2E tests (Playwright) composer run wp-test-clean # Clean up WordPress environment (Docker and DB) ``` +## Release workflow +Release is automated with GitHub Actions. It requires only two manual steps: + +1. **Create a draft release with a new tag and changelog.** + The `release-prepare` workflow will automatically: + - Use the draft tag to bump versions in `version.php`, `load.php`, and `readme.txt`. + - Add the draft release changelog entry to `readme.txt`. + - Build the plugin ZIP and attach it to the draft release. + - Create a PR (`release/` → `trunk`) and link it in the draft release body. +2. **Review and merge the PR.** + The `release-publish` workflow will automatically: + - Publish the draft release. + - Publish the release artifact to WordPress.org. + ## Architecture The project consists of multiple components providing different APIs that funnel into the SQLite driver to support diverse use cases both inside and outside the diff --git a/bin/build-plugin-zip.sh b/bin/build-plugin-zip.sh new file mode 100755 index 00000000..dd45b851 --- /dev/null +++ b/bin/build-plugin-zip.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +## +# Build the SQLite Database Integration plugin zip. +# +# This script copies the plugin package into ./build/sqlite-database-integration/, +# resolves the driver symlink, removes dev-only files, and creates a zip archive. +## + +set -e + +DIR="$(cd "$(dirname "$0")/.." && pwd)" +BUILD_DIR="$DIR/build" +PLUGIN_DIR="$BUILD_DIR/sqlite-database-integration" +ZIP_FILE="$BUILD_DIR/sqlite-database-integration.zip" + +# Clean previous build. +rm -rf "$PLUGIN_DIR" +rm -f "$ZIP_FILE" +mkdir -p "$BUILD_DIR" + +# Copy the plugin package. +cp -R "$DIR/packages/plugin-sqlite-database-integration" "$PLUGIN_DIR" + +# Resolve the database symlink — replace it with a real copy of the driver. +rm "$PLUGIN_DIR/wp-includes/database" +cp -R "$DIR/packages/mysql-on-sqlite/src" "$PLUGIN_DIR/wp-includes/database" + +# Remove dev-only files. +rm -rf "$PLUGIN_DIR/composer.json" +rm -rf "$PLUGIN_DIR/vendor" +rm -rf "$PLUGIN_DIR/node_modules" + +# Create the zip archive. +cd "$BUILD_DIR" +zip -r "$ZIP_FILE" "sqlite-database-integration/" -x "*.DS_Store" + +echo "Built: $ZIP_FILE" diff --git a/composer.json b/composer.json index d41a209d..9ba267bc 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,9 @@ "fix-cs": [ "@php ./vendor/bin/phpcbf" ], + "build": [ + "./bin/build-plugin-zip.sh" + ], "test": [ "phpunit" ], diff --git a/grammar-tools/convert-grammar.php b/grammar-tools/convert-grammar.php index 56e7c478..6e01682a 100644 --- a/grammar-tools/convert-grammar.php +++ b/grammar-tools/convert-grammar.php @@ -10,10 +10,10 @@ * @TODO Migrate the current regex-based solution to a proper grammar parser. */ -require_once __DIR__ . '/../wp-includes/parser/class-wp-parser-grammar.php'; -require_once __DIR__ . '/../wp-includes/mysql/class-wp-mysql-lexer.php'; +require_once __DIR__ . '/../packages/mysql-on-sqlite/src/parser/class-wp-parser-grammar.php'; +require_once __DIR__ . '/../packages/mysql-on-sqlite/src/mysql/class-wp-mysql-lexer.php'; -const GRAMMAR_FILE = __DIR__ . '/../wp-includes/mysql/mysql-grammar.php'; +const GRAMMAR_FILE = __DIR__ . '/../packages/mysql-on-sqlite/src/mysql/mysql-grammar.php'; // Convert the original MySQLParser.g4 grammar to a JSON format. // The grammar is also flattened and expanded to an ebnf-to-json-like format. diff --git a/packages/mysql-on-sqlite/composer.json b/packages/mysql-on-sqlite/composer.json new file mode 100644 index 00000000..9d2b148f --- /dev/null +++ b/packages/mysql-on-sqlite/composer.json @@ -0,0 +1,10 @@ +{ + "name": "wordpress/mysql-on-sqlite", + "type": "library", + "scripts": { + "test": "phpunit" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + } +} diff --git a/packages/mysql-on-sqlite/phpunit.xml.dist b/packages/mysql-on-sqlite/phpunit.xml.dist new file mode 100644 index 00000000..ccb53e6a --- /dev/null +++ b/packages/mysql-on-sqlite/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + tests/ + tests/tools + + + diff --git a/packages/mysql-on-sqlite/src/load.php b/packages/mysql-on-sqlite/src/load.php new file mode 100644 index 00000000..41c7d803 --- /dev/null +++ b/packages/mysql-on-sqlite/src/load.php @@ -0,0 +1,26 @@ +query( 'SELECT SQLITE_VERSION();' )->fetch()[0]; +if ( version_compare( $sqlite_version, WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' ) ) { + define( 'WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS', true ); +} + +// Configure the test environment. +error_reporting( E_ALL ); +define( 'FQDB', ':memory:' ); +define( 'FQDBDIR', __DIR__ . '/../testdb' ); + +// Polyfill WPDB globals. +$GLOBALS['table_prefix'] = 'wptests_'; +$GLOBALS['wpdb'] = new class() { + public function set_prefix( string $prefix ): void {} +}; + +/** + * Polyfills for WordPress functions + */ +if ( ! function_exists( 'do_action' ) ) { + /** + * Polyfill the do_action function. + */ + function do_action() {} +} + +if ( ! function_exists( 'apply_filters' ) ) { + /** + * Polyfill the apply_filters function. + * + * @param string $tag The filter name. + * @param mixed $value The value to filter. + * @param mixed ...$args Additional arguments to pass to the filter. + * + * @return mixed Returns $value. + */ + function apply_filters( $tag, $value, ...$args ) { + return $value; + } +} + +if ( extension_loaded( 'mbstring' ) ) { + + if ( ! function_exists( 'mb_str_starts_with' ) ) { + /** + * Polyfill for mb_str_starts_with. + * + * @param string $haystack The string to search in. + * @param string $needle The string to search for. + * + * @return bool + */ + function mb_str_starts_with( string $haystack, string $needle ) { + return empty( $needle ) || 0 === mb_strpos( $haystack, $needle ); + } + } + + if ( ! function_exists( 'mb_str_contains' ) ) { + /** + * Polyfill for mb_str_contains. + * + * @param string $haystack The string to search in. + * @param string $needle The string to search for. + * + * @return bool + */ + function mb_str_contains( string $haystack, string $needle ) { + return empty( $needle ) || false !== mb_strpos( $haystack, $needle ); + } + } + + if ( ! function_exists( 'mb_str_ends_with' ) ) { + /** + * Polyfill for mb_str_ends_with. + * + * @param string $haystack The string to search in. + * @param string $needle The string to search for. + * + * @return bool + */ + function mb_str_ends_with( string $haystack, string $needle ) { + // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found + return empty( $needle ) || $needle = mb_substr( $haystack, - mb_strlen( $needle ) ); + } + } +} diff --git a/tests/mysql/WP_MySQL_Lexer_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php similarity index 100% rename from tests/mysql/WP_MySQL_Lexer_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Lexer_Tests.php diff --git a/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php similarity index 100% rename from tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Lexer_Tests.php diff --git a/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php similarity index 97% rename from tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php rename to packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php index 27274f71..02769884 100644 --- a/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php +++ b/packages/mysql-on-sqlite/tests/mysql/WP_MySQL_Server_Suite_Parser_Tests.php @@ -7,7 +7,7 @@ */ class WP_MySQL_Server_Suite_Parser_Tests extends TestCase { const TEST_DATA_PATH = __DIR__ . '/data/mysql-server-tests-queries.csv'; - const GRAMMAR_PATH = __DIR__ . '/../../wp-includes/mysql/mysql-grammar.php'; + const GRAMMAR_PATH = __DIR__ . '/../../src/mysql/mysql-grammar.php'; /** * Some of the queries in the test suite are known to fail parsing. diff --git a/tests/mysql/data/mysql-server-tests-queries.csv b/packages/mysql-on-sqlite/tests/mysql/data/mysql-server-tests-queries.csv similarity index 100% rename from tests/mysql/data/mysql-server-tests-queries.csv rename to packages/mysql-on-sqlite/tests/mysql/data/mysql-server-tests-queries.csv diff --git a/tests/parser/WP_Parser_Node_Tests.php b/packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php similarity index 98% rename from tests/parser/WP_Parser_Node_Tests.php rename to packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php index 16fa49d3..80fbcb06 100644 --- a/tests/parser/WP_Parser_Node_Tests.php +++ b/packages/mysql-on-sqlite/tests/parser/WP_Parser_Node_Tests.php @@ -1,6 +1,6 @@ ':memory:' ) ), diff --git a/tests/tools/mysql-download-tests.sh b/packages/mysql-on-sqlite/tests/tools/mysql-download-tests.sh similarity index 100% rename from tests/tools/mysql-download-tests.sh rename to packages/mysql-on-sqlite/tests/tools/mysql-download-tests.sh diff --git a/tests/tools/mysql-extract-queries.php b/packages/mysql-on-sqlite/tests/tools/mysql-extract-queries.php similarity index 100% rename from tests/tools/mysql-extract-queries.php rename to packages/mysql-on-sqlite/tests/tools/mysql-extract-queries.php diff --git a/tests/tools/run-lexer-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php similarity index 81% rename from tests/tools/run-lexer-benchmark.php rename to packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php index a86d0f0f..79c0c3c6 100644 --- a/tests/tools/run-lexer-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-lexer-benchmark.php @@ -12,9 +12,9 @@ function ( $severity, $message, $file, $line ) { } ); -require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-token.php'; -require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php'; -require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php'; +require_once __DIR__ . '/../../src/parser/class-wp-parser-token.php'; +require_once __DIR__ . '/../../src/mysql/class-wp-mysql-token.php'; +require_once __DIR__ . '/../../src/mysql/class-wp-mysql-lexer.php'; // Load the queries. $handle = fopen( __DIR__ . '/../mysql/data/mysql-server-tests-queries.csv', 'r' ); diff --git a/tests/tools/run-parser-benchmark.php b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php similarity index 66% rename from tests/tools/run-parser-benchmark.php rename to packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php index 5d5f5e2e..ad87dd43 100644 --- a/tests/tools/run-parser-benchmark.php +++ b/packages/mysql-on-sqlite/tests/tools/run-parser-benchmark.php @@ -13,15 +13,15 @@ function ( $severity, $message, $file, $line ) { } ); -require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-grammar.php'; -require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-node.php'; -require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser-token.php'; -require_once __DIR__ . '/../../wp-includes/parser/class-wp-parser.php'; -require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-token.php'; -require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-lexer.php'; -require_once __DIR__ . '/../../wp-includes/mysql/class-wp-mysql-parser.php'; +require_once __DIR__ . '/../../src/parser/class-wp-parser-grammar.php'; +require_once __DIR__ . '/../../src/parser/class-wp-parser-node.php'; +require_once __DIR__ . '/../../src/parser/class-wp-parser-token.php'; +require_once __DIR__ . '/../../src/parser/class-wp-parser.php'; +require_once __DIR__ . '/../../src/mysql/class-wp-mysql-token.php'; +require_once __DIR__ . '/../../src/mysql/class-wp-mysql-lexer.php'; +require_once __DIR__ . '/../../src/mysql/class-wp-mysql-parser.php'; -function getStats( $total, $failures, $exceptions ) { +function get_stats( $total, $failures, $exceptions ) { return sprintf( 'Total: %5d | Failures: %4d / %2d%% | Exceptions: %4d / %2d%%', $total, @@ -33,14 +33,14 @@ function getStats( $total, $failures, $exceptions ) { } // Load the MySQL grammar. -$grammar_data = include __DIR__ . '/../../wp-includes/mysql/mysql-grammar.php'; +$grammar_data = include __DIR__ . '/../../src/mysql/mysql-grammar.php'; $grammar = new WP_Parser_Grammar( $grammar_data ); // Load the queries. $data_dir = __DIR__ . '/../mysql/data'; $handle = fopen( "$data_dir/mysql-server-tests-queries.csv", 'r' ); $records = array(); -while ( ( $record = fgetcsv( $handle ) ) !== false ) { +while ( ( $record = fgetcsv( $handle, null, ',', '"', '\\' ) ) !== false ) { $records[] = $record; } @@ -71,12 +71,12 @@ function getStats( $total, $failures, $exceptions ) { } if ( $i > 0 && 0 === $i % 1000 ) { - echo getStats( $i, count( $failures ), count( $exceptions ) ), "\n"; + echo get_stats( $i, count( $failures ), count( $exceptions ) ), "\n"; } } $duration = microtime( true ) - $start; -echo getStats( $i, count( $failures ), count( $exceptions ) ), "\n"; +echo get_stats( $i, count( $failures ), count( $exceptions ) ), "\n"; // Print the results. printf( "\nParsed %d queries in %.5fs @ %d QPS.\n", $i, $duration, $i / $duration ); diff --git a/packages/mysql-on-sqlite/tests/wp-sqlite-schema.php b/packages/mysql-on-sqlite/tests/wp-sqlite-schema.php new file mode 100644 index 00000000..82a2adc5 --- /dev/null +++ b/packages/mysql-on-sqlite/tests/wp-sqlite-schema.php @@ -0,0 +1,177 @@ += 80400 ? PDO\SQLite::class : PDO::class; + $pdo_class = PHP_VERSION_ID >= 80400 ? PDO\MySQL::class : PDO::class; $this->pdo = new $pdo_class( sprintf( 'mysql:host=127.0.0.1;port=%d', $this->port ), 'user', diff --git a/packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_Test.php b/packages/mysql-proxy/tests/WP_MySQL_Proxy_Test.php similarity index 100% rename from packages/wp-mysql-proxy/tests/WP_MySQL_Proxy_Test.php rename to packages/mysql-proxy/tests/WP_MySQL_Proxy_Test.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/bootstrap.php b/packages/mysql-proxy/tests/bootstrap/bootstrap.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/bootstrap.php rename to packages/mysql-proxy/tests/bootstrap/bootstrap.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/mysql-server-process.php b/packages/mysql-proxy/tests/bootstrap/mysql-server-process.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/mysql-server-process.php rename to packages/mysql-proxy/tests/bootstrap/mysql-server-process.php diff --git a/packages/wp-mysql-proxy/tests/bootstrap/run-server.php b/packages/mysql-proxy/tests/bootstrap/run-server.php similarity index 100% rename from packages/wp-mysql-proxy/tests/bootstrap/run-server.php rename to packages/mysql-proxy/tests/bootstrap/run-server.php diff --git a/packages/plugin-sqlite-database-integration/LICENSE b/packages/plugin-sqlite-database-integration/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/packages/plugin-sqlite-database-integration/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/activate.php b/packages/plugin-sqlite-database-integration/activate.php similarity index 100% rename from activate.php rename to packages/plugin-sqlite-database-integration/activate.php diff --git a/admin-notices.php b/packages/plugin-sqlite-database-integration/admin-notices.php similarity index 100% rename from admin-notices.php rename to packages/plugin-sqlite-database-integration/admin-notices.php diff --git a/admin-page.php b/packages/plugin-sqlite-database-integration/admin-page.php similarity index 100% rename from admin-page.php rename to packages/plugin-sqlite-database-integration/admin-page.php diff --git a/packages/plugin-sqlite-database-integration/composer.json b/packages/plugin-sqlite-database-integration/composer.json new file mode 100644 index 00000000..d77c7f47 --- /dev/null +++ b/packages/plugin-sqlite-database-integration/composer.json @@ -0,0 +1,15 @@ +{ + "name": "wordpress/sqlite-database-integration", + "license": "GPL-2.0-or-later", + "description": "SQLite integration plugin for WordPress.", + "homepage": "https://github.com/wordpress/sqlite-database-integration", + "keywords": [ "wordpress", "plugin", "database", "sqlite" ], + "support": { + "issues": "https://github.com/wordpress/sqlite-database-integration/issues" + }, + "require": { + "php": ">=7.2", + "ext-pdo": "*", + "ext-pdo_sqlite": "*" + } +} diff --git a/constants.php b/packages/plugin-sqlite-database-integration/constants.php similarity index 100% rename from constants.php rename to packages/plugin-sqlite-database-integration/constants.php diff --git a/db.copy b/packages/plugin-sqlite-database-integration/db.copy similarity index 100% rename from db.copy rename to packages/plugin-sqlite-database-integration/db.copy diff --git a/deactivate.php b/packages/plugin-sqlite-database-integration/deactivate.php similarity index 100% rename from deactivate.php rename to packages/plugin-sqlite-database-integration/deactivate.php diff --git a/health-check.php b/packages/plugin-sqlite-database-integration/health-check.php similarity index 100% rename from health-check.php rename to packages/plugin-sqlite-database-integration/health-check.php diff --git a/integrations/query-monitor/boot.php b/packages/plugin-sqlite-database-integration/integrations/query-monitor/boot.php similarity index 100% rename from integrations/query-monitor/boot.php rename to packages/plugin-sqlite-database-integration/integrations/query-monitor/boot.php diff --git a/integrations/query-monitor/plugin.php b/packages/plugin-sqlite-database-integration/integrations/query-monitor/plugin.php similarity index 100% rename from integrations/query-monitor/plugin.php rename to packages/plugin-sqlite-database-integration/integrations/query-monitor/plugin.php diff --git a/load.php b/packages/plugin-sqlite-database-integration/load.php similarity index 89% rename from load.php rename to packages/plugin-sqlite-database-integration/load.php index b947cbcb..4acc667b 100644 --- a/load.php +++ b/packages/plugin-sqlite-database-integration/load.php @@ -16,11 +16,10 @@ * Load the "SQLITE_DRIVER_VERSION" constant. * This constant needs to be updated on plugin release! */ -require_once __DIR__ . '/version.php'; +require_once __DIR__ . '/wp-includes/database/version.php'; define( 'SQLITE_MAIN_FILE', __FILE__ ); -require_once __DIR__ . '/php-polyfills.php'; require_once __DIR__ . '/admin-page.php'; require_once __DIR__ . '/activate.php'; require_once __DIR__ . '/deactivate.php'; diff --git a/readme.txt b/packages/plugin-sqlite-database-integration/readme.txt similarity index 100% rename from readme.txt rename to packages/plugin-sqlite-database-integration/readme.txt diff --git a/packages/plugin-sqlite-database-integration/wp-includes/database b/packages/plugin-sqlite-database-integration/wp-includes/database new file mode 120000 index 00000000..57642c9c --- /dev/null +++ b/packages/plugin-sqlite-database-integration/wp-includes/database @@ -0,0 +1 @@ +../../mysql-on-sqlite/src/ \ No newline at end of file diff --git a/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php similarity index 99% rename from wp-includes/sqlite/class-wp-sqlite-db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php index c0ee2bbc..465f5b5b 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-db.php @@ -335,7 +335,6 @@ public function db_connect( $allow_bail = true ) { return false; } - require_once __DIR__ . '/../../wp-pdo-mysql-on-sqlite.php'; $this->ensure_database_directory( FQDB ); try { diff --git a/wp-includes/sqlite/class-wp-sqlite-lexer.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-lexer.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php diff --git a/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php new file mode 100644 index 00000000..b72d787f --- /dev/null +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php @@ -0,0 +1,899 @@ + + * new WP_SQLite_PDO_User_Defined_Functions(ref_to_pdo_obj); + * + * + * This automatically enables ref_to_pdo_obj to replace the function in the SQL statement + * to the ones defined here. + */ +class WP_SQLite_PDO_User_Defined_Functions { + + /** + * Registers the user defined functions for SQLite to a PDO instance. + * The functions are registered using PDO::sqliteCreateFunction(). + * + * @param PDO|PDO\SQLite $pdo The PDO object. + */ + public static function register_for( $pdo ): self { + $instance = new self(); + foreach ( $instance->functions as $f => $t ) { + if ( $pdo instanceof PDO\SQLite ) { + $pdo->createFunction( $f, array( $instance, $t ) ); + } else { + $pdo->sqliteCreateFunction( $f, array( $instance, $t ) ); + } + } + return $instance; + } + + /** + * Array to define MySQL function => function defined with PHP. + * + * Replaced functions must be public. + * + * @var array + */ + private $functions = array( + 'throw' => 'throw', + 'month' => 'month', + 'monthnum' => 'month', + 'year' => 'year', + 'day' => 'day', + 'hour' => 'hour', + 'minute' => 'minute', + 'second' => 'second', + 'week' => 'week', + 'weekday' => 'weekday', + 'dayofweek' => 'dayofweek', + 'dayofmonth' => 'dayofmonth', + 'unix_timestamp' => 'unix_timestamp', + 'now' => 'now', + 'md5' => 'md5', + 'curdate' => 'curdate', + 'rand' => 'rand', + 'from_unixtime' => 'from_unixtime', + 'localtime' => 'now', + 'localtimestamp' => 'now', + 'isnull' => 'isnull', + 'if' => '_if', + 'regexp' => 'regexp', + 'field' => 'field', + 'log' => 'log', + 'least' => 'least', + 'greatest' => 'greatest', + 'get_lock' => 'get_lock', + 'release_lock' => 'release_lock', + 'ucase' => 'ucase', + 'lcase' => 'lcase', + 'unhex' => 'unhex', + 'from_base64' => 'from_base64', + 'to_base64' => 'to_base64', + 'inet_ntoa' => 'inet_ntoa', + 'inet_aton' => 'inet_aton', + 'datediff' => 'datediff', + 'locate' => 'locate', + 'utc_date' => 'utc_date', + 'utc_time' => 'utc_time', + 'utc_timestamp' => 'utc_timestamp', + 'version' => 'version', + + // Internal helper functions. + '_helper_like_to_glob_pattern' => '_helper_like_to_glob_pattern', + ); + + /** + * A helper function to throw an error from SQLite expressions. + * + * @param string $message The error message. + * + * @throws Exception The error message. + * @return void + */ + public function throw( $message ): void { + throw new Exception( $message ); + } + + /** + * Method to return the unix timestamp. + * + * Used without an argument, it returns PHP time() function (total seconds passed + * from '1970-01-01 00:00:00' GMT). Used with the argument, it changes the value + * to the timestamp. + * + * @param string $field Representing the date formatted as '0000-00-00 00:00:00'. + * + * @return number of unsigned integer + */ + public function unix_timestamp( $field = null ) { + return is_null( $field ) ? time() : strtotime( $field ); + } + + /** + * Method to emulate MySQL FROM_UNIXTIME() function. + * + * @param int $field The unix timestamp. + * @param string $format Indicate the way of formatting(optional). + * + * @return string + */ + public function from_unixtime( $field, $format = null ) { + // Convert to ISO time. + $date = gmdate( 'Y-m-d H:i:s', $field ); + + return is_null( $format ) ? $date : $this->dateformat( $date, $format ); + } + + /** + * Method to emulate MySQL NOW() function. + * + * @return string representing current time formatted as '0000-00-00 00:00:00'. + */ + public function now() { + return gmdate( 'Y-m-d H:i:s' ); + } + + /** + * Method to emulate MySQL CURDATE() function. + * + * @return string representing current time formatted as '0000-00-00'. + */ + public function curdate() { + return gmdate( 'Y-m-d' ); + } + + /** + * Method to emulate MySQL MD5() function. + * + * @param string $field The string to be hashed. + * + * @return string of the md5 hash value of the argument. + */ + public function md5( $field ) { + return md5( $field ); + } + + /** + * Method to emulate MySQL RAND() function. + * + * SQLite does have a random generator, but it is called RANDOM() and returns random + * number between -9223372036854775808 and +9223372036854775807. So we substitute it + * with PHP random generator. + * + * This function uses mt_rand() which is four times faster than rand() and returns + * the random number between 0 and 1. + * + * @return int + */ + public function rand() { + return mt_rand( 0, 1 ); + } + + /** + * Method to emulate MySQL DATEFORMAT() function. + * + * @param string $date Formatted as '0000-00-00' or datetime as '0000-00-00 00:00:00'. + * @param string $format The string format. + * + * @return string formatted according to $format + */ + public function dateformat( $date, $format ) { + $mysql_php_date_formats = array( + '%a' => 'D', + '%b' => 'M', + '%c' => 'n', + '%D' => 'jS', + '%d' => 'd', + '%e' => 'j', + '%H' => 'H', + '%h' => 'h', + '%I' => 'h', + '%i' => 'i', + '%j' => 'z', + '%k' => 'G', + '%l' => 'g', + '%M' => 'F', + '%m' => 'm', + '%p' => 'A', + '%r' => 'h:i:s A', + '%S' => 's', + '%s' => 's', + '%T' => 'H:i:s', + '%U' => 'W', + '%u' => 'W', + '%V' => 'W', + '%v' => 'W', + '%W' => 'l', + '%w' => 'w', + '%X' => 'Y', + '%x' => 'o', + '%Y' => 'Y', + '%y' => 'y', + ); + + $time = strtotime( $date ); + $format = strtr( $format, $mysql_php_date_formats ); + + return gmdate( $format, $time ); + } + + /** + * Method to extract the month value from the date. + * + * @param string $field Representing the date formatted as 0000-00-00. + * + * @return string Representing the number of the month between 1 and 12. + */ + public function month( $field ) { + /* + * MySQL returns 0 for MONTH('0000-00-00') and for dates with + * zero month parts like '2020-00-15'. PHP's strtotime() can't + * parse these, so we extract the month directly from the string. + */ + if ( preg_match( '/^\d{4}-(\d{2})/', $field, $matches ) ) { + return intval( $matches[1] ); + } + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * n - Numeric representation of a month, without leading zeros. + * 1 through 12 + */ + return intval( gmdate( 'n', strtotime( $field ) ) ); + } + + /** + * Method to extract the year value from the date. + * + * @param string $field Representing the date formatted as 0000-00-00. + * + * @return string Representing the number of the year. + */ + public function year( $field ) { + /* + * MySQL returns 0 for YEAR('0000-00-00'). PHP's strtotime() + * can't parse zero dates, so we extract the year directly. + */ + if ( preg_match( '/^(\d{4})-\d{2}/', $field, $matches ) ) { + return intval( $matches[1] ); + } + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * Y - A full numeric representation of a year, 4 digits. + */ + return intval( gmdate( 'Y', strtotime( $field ) ) ); + } + + /** + * Method to extract the day value from the date. + * + * @param string $field Representing the date formatted as 0000-00-00. + * + * @return string Representing the number of the day of the month from 1 and 31. + */ + public function day( $field ) { + /* + * MySQL returns 0 for DAY('0000-00-00') and for dates with + * zero day parts like '2020-01-00'. PHP's strtotime() can't + * parse these, so we extract the day directly from the string. + */ + if ( preg_match( '/^\d{4}-\d{2}-(\d{2})/', $field, $matches ) ) { + return intval( $matches[1] ); + } + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * j - Day of the month without leading zeros. + * 1 to 31. + */ + return intval( gmdate( 'j', strtotime( $field ) ) ); + } + + /** + * Method to emulate MySQL SECOND() function. + * + * @see https://www.php.net/manual/en/datetime.format.php + * + * @param string $field Representing the time formatted as '00:00:00'. + * + * @return number Unsigned integer + */ + public function second( $field ) { + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * s - Seconds, with leading zeros (00 to 59) + */ + return intval( gmdate( 's', strtotime( $field ) ) ); + } + + /** + * Method to emulate MySQL MINUTE() function. + * + * @param string $field Representing the time formatted as '00:00:00'. + * + * @return int + */ + public function minute( $field ) { + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * i - Minutes with leading zeros. + * 00 to 59. + */ + return intval( gmdate( 'i', strtotime( $field ) ) ); + } + + /** + * Method to emulate MySQL HOUR() function. + * + * Returns the hour for time, in 24-hour format, from 0 to 23. + * Importantly, midnight is 0, not 24. + * + * @param string $time Representing the time formatted, like '14:08:12'. + * + * @return int + */ + public function hour( $time ) { + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * H 24-hour format of an hour with leading zeros. + * 00 through 23. + */ + return intval( gmdate( 'H', strtotime( $time ) ) ); + } + + /** + * Covers MySQL WEEK() function. + * + * Always assumes $mode = 1. + * + * @TODO: Support other modes. + * + * From https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_week: + * + * > Returns the week number for date. The two-argument form of WEEK() + * > enables you to specify whether the week starts on Sunday or Monday + * > and whether the return value should be in the range from 0 to 53 + * > or from 1 to 53. If the mode argument is omitted, the value of the + * > default_week_format system variable is used. + * > + * > The following table describes how the mode argument works: + * > + * > Mode First day of week Range Week 1 is the first week … + * > 0 Sunday 0-53 with a Sunday in this year + * > 1 Monday 0-53 with 4 or more days this year + * > 2 Sunday 1-53 with a Sunday in this year + * > 3 Monday 1-53 with 4 or more days this year + * > 4 Sunday 0-53 with 4 or more days this year + * > 5 Monday 0-53 with a Monday in this year + * > 6 Sunday 1-53 with 4 or more days this year + * > 7 Monday 1-53 with a Monday in this year + * + * @param string $field Representing the date. + * @param int $mode The mode argument. + */ + public function week( $field, $mode ) { + /* + * From https://www.php.net/manual/en/datetime.format.php: + * + * W - ISO-8601 week number of year, weeks starting on Monday. + * Example: 42 (the 42nd week in the year) + * + * Week 1 is the first week with a Thursday in it. + */ + return intval( gmdate( 'W', strtotime( $field ) ) ); + } + + /** + * Simulates WEEKDAY() function in MySQL. + * + * Returns the day of the week as an integer. + * The days of the week are numbered 0 to 6: + * * 0 for Monday + * * 1 for Tuesday + * * 2 for Wednesday + * * 3 for Thursday + * * 4 for Friday + * * 5 for Saturday + * * 6 for Sunday + * + * @param string $field Representing the date. + * + * @return int + */ + public function weekday( $field ) { + /* + * date('N') returns 1 (for Monday) through 7 (for Sunday) + * That's one more than MySQL. + * Let's subtract one to make it compatible. + */ + return intval( gmdate( 'N', strtotime( $field ) ) ) - 1; + } + + /** + * Method to emulate MySQL DAYOFMONTH() function. + * + * @see https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_dayofmonth + * + * @param string $field Representing the date. + * + * @return int Returns the day of the month for date as a number in the range 1 to 31. + */ + public function dayofmonth( $field ) { + return intval( gmdate( 'j', strtotime( $field ) ) ); + } + + /** + * Method to emulate MySQL DAYOFWEEK() function. + * + * > Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). + * > These index values correspond to the ODBC standard. Returns NULL if date is NULL. + * + * @param string $field Representing the date. + * + * @return int Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). + */ + public function dayofweek( $field ) { + /** + * From https://www.php.net/manual/en/datetime.format.php: + * + * `w` – Numeric representation of the day of the week + * 0 (for Sunday) through 6 (for Saturday) + */ + return intval( gmdate( 'w', strtotime( $field ) ) ) + 1; + } + + /** + * Method to emulate MySQL DATE() function. + * + * @see https://www.php.net/manual/en/datetime.format.php + * + * @param string $date formatted as unix time. + * + * @return string formatted as '0000-00-00'. + */ + public function date( $date ) { + return gmdate( 'Y-m-d', strtotime( $date ) ); + } + + /** + * Method to emulate MySQL ISNULL() function. + * + * This function returns true if the argument is null, and true if not. + * + * @param mixed $field The field to be tested. + * + * @return boolean + */ + public function isnull( $field ) { + return is_null( $field ); + } + + /** + * Method to emulate MySQL IF() function. + * + * As 'IF' is a reserved word for PHP, function name must be changed. + * + * @param mixed $expression The statement to be evaluated as true or false. + * @param mixed $truthy Statement or value returned if $expression is true. + * @param mixed $falsy Statement or value returned if $expression is false. + * + * @return mixed + */ + public function _if( $expression, $truthy, $falsy ) { + return ( true === $expression ) ? $truthy : $falsy; + } + + /** + * Method to emulate MySQL REGEXP() function. + * + * @param string $pattern Regular expression to match. + * @param string $field Haystack. + * + * @return integer 1 if matched, 0 if not matched. + */ + public function regexp( $pattern, $field ) { + /* + * If the original query says REGEXP BINARY + * the comparison is byte-by-byte and letter casing now + * matters since lower- and upper-case letters have different + * byte codes. + * + * The REGEXP function can't be easily made to accept two + * parameters, so we'll have to use a hack to get around this. + * + * If the first character of the pattern is a null byte, we'll + * remove it and make the comparison case-sensitive. This should + * be reasonably safe since PHP does not allow null bytes in + * regular expressions anyway. + */ + if ( "\x00" === $pattern[0] ) { + $pattern = substr( $pattern, 1 ); + $flags = ''; + } else { + // Otherwise, the search is case-insensitive. + $flags = 'i'; + } + $pattern = str_replace( '/', '\/', $pattern ); + $pattern = '/' . $pattern . '/' . $flags; + + return preg_match( $pattern, $field ); + } + + /** + * Method to emulate MySQL FIELD() function. + * + * This function gets the list argument and compares the first item to all the others. + * If the same value is found, it returns the position of that value. If not, it + * returns 0. + * + * @return int + */ + public function field() { + $num_args = func_num_args(); + if ( $num_args < 2 || is_null( func_get_arg( 0 ) ) ) { + return 0; + } + $arg_list = func_get_args(); + $search_string = strtolower( array_shift( $arg_list ) ); + + for ( $i = 0; $i < $num_args - 1; $i++ ) { + if ( strtolower( $arg_list[ $i ] ) === $search_string ) { + return $i + 1; + } + } + + return 0; + } + + /** + * Method to emulate MySQL LOG() function. + * + * Used with one argument, it returns the natural logarithm of X. + * + * LOG(X) + * + * Used with two arguments, it returns the natural logarithm of X base B. + * + * LOG(B, X) + * + * In this case, it returns the value of log(X) / log(B). + * + * Used without an argument, it returns false. This returned value will be + * rewritten to 0, because SQLite doesn't understand true/false value. + * + * @return double|null + */ + public function log() { + $num_args = func_num_args(); + if ( 1 === $num_args ) { + $arg1 = func_get_arg( 0 ); + + return log( $arg1 ); + } + if ( 2 === $num_args ) { + $arg1 = func_get_arg( 0 ); + $arg2 = func_get_arg( 1 ); + + return log( $arg1 ) / log( $arg2 ); + } + return null; + } + + /** + * Method to emulate MySQL LEAST() function. + * + * This function rewrites the function name to SQLite compatible function name. + * + * @return mixed + */ + public function least() { + $arg_list = func_get_args(); + + return min( $arg_list ); + } + + /** + * Method to emulate MySQL GREATEST() function. + * + * This function rewrites the function name to SQLite compatible function name. + * + * @return mixed + */ + public function greatest() { + $arg_list = func_get_args(); + + return max( $arg_list ); + } + + /** + * Method to dummy out MySQL GET_LOCK() function. + * + * This function is meaningless in SQLite, so we do nothing. + * + * @param string $name Not used. + * @param integer $timeout Not used. + * + * @return string + */ + public function get_lock( $name, $timeout ) { + return '1=1'; + } + + /** + * Method to dummy out MySQL RELEASE_LOCK() function. + * + * This function is meaningless in SQLite, so we do nothing. + * + * @param string $name Not used. + * + * @return string + */ + public function release_lock( $name ) { + return '1=1'; + } + + /** + * Method to emulate MySQL UCASE() function. + * + * This is MySQL alias for upper() function. This function rewrites it + * to SQLite compatible name upper(). + * + * @param string $content String to be converted to uppercase. + * + * @return string SQLite compatible function name. + */ + public function ucase( $content ) { + return "upper($content)"; + } + + /** + * Method to emulate MySQL LCASE() function. + * + * This is MySQL alias for lower() function. This function rewrites it + * to SQLite compatible name lower(). + * + * @param string $content String to be converted to lowercase. + * + * @return string SQLite compatible function name. + */ + public function lcase( $content ) { + return "lower($content)"; + } + + /** + * Method to emulate MySQL UNHEX() function. + * + * For a string argument str, UNHEX(str) interprets each pair of characters + * in the argument as a hexadecimal number and converts it to the byte represented + * by the number. The return value is a binary string. + * + * @param string $number Number to be unhexed. + * + * @return string Binary string + */ + public function unhex( $number ) { + return pack( 'H*', $number ); + } + + /** + * Method to emulate MySQL FROM_BASE64() function. + * + * Takes a base64-encoded string and returns the decoded result as a binary + * string. Returns NULL if the argument is NULL or is not a valid base64 string. + * + * @param string|null $str The base64-encoded string. + * + * @return string|null Decoded binary string, or NULL. + */ + public function from_base64( $str ) { + if ( null === $str ) { + return null; + } + $decoded = base64_decode( $str, true ); + if ( false === $decoded ) { + return null; + } + return $decoded; + } + + /** + * Method to emulate MySQL TO_BASE64() function. + * + * Takes a string and returns a base64-encoded result. + * Returns NULL if the argument is NULL. + * + * @param string|null $str The string to encode. + * + * @return string|null Base64-encoded string, or NULL. + */ + public function to_base64( $str ) { + if ( null === $str ) { + return null; + } + return base64_encode( $str ); + } + + /** + * Method to emulate MySQL INET_NTOA() function. + * + * This function gets 4 or 8 bytes integer and turn it into the network address. + * + * @param integer $num Long integer. + * + * @return string + */ + public function inet_ntoa( $num ) { + return long2ip( $num ); + } + + /** + * Method to emulate MySQL INET_ATON() function. + * + * This function gets the network address and turns it into integer. + * + * @param string $addr Network address. + * + * @return int long integer + */ + public function inet_aton( $addr ) { + return absint( ip2long( $addr ) ); + } + + /** + * Method to emulate MySQL DATEDIFF() function. + * + * This function compares two dates value and returns the difference. + * + * @param string $start Start date. + * @param string $end End date. + * + * @return string + */ + public function datediff( $start, $end ) { + $start_date = new DateTime( $start ); + $end_date = new DateTime( $end ); + $interval = $end_date->diff( $start_date, false ); + + return $interval->format( '%r%a' ); + } + + /** + * Method to emulate MySQL LOCATE() function. + * + * This function returns the position if $substr is found in $str. If not, + * it returns 0. If mbstring extension is loaded, mb_strpos() function is + * used. + * + * @param string $substr Needle. + * @param string $str Haystack. + * @param integer $pos Position. + * + * @return integer + */ + public function locate( $substr, $str, $pos = 0 ) { + if ( ! extension_loaded( 'mbstring' ) ) { + $val = strpos( $str, $substr, $pos ); + if ( false !== $val ) { + return $val + 1; + } + return 0; + } + $val = mb_strpos( $str, $substr, $pos ); + if ( false !== $val ) { + return $val + 1; + } + return 0; + } + + /** + * Method to return GMT date in the string format. + * + * @return string formatted GMT date 'dddd-mm-dd' + */ + public function utc_date() { + return gmdate( 'Y-m-d', time() ); + } + + /** + * Method to return GMT time in the string format. + * + * @return string formatted GMT time '00:00:00' + */ + public function utc_time() { + return gmdate( 'H:i:s', time() ); + } + + /** + * Method to return GMT time stamp in the string format. + * + * @return string formatted GMT timestamp 'yyyy-mm-dd 00:00:00' + */ + public function utc_timestamp() { + return gmdate( 'Y-m-d H:i:s', time() ); + } + + /** + * Method to return MySQL version. + * + * This function only returns the current newest version number of MySQL, + * because it is meaningless for SQLite database. + * + * @return string representing the version number: major_version.minor_version + */ + public function version() { + return '5.5'; + } + + /** + * A helper to covert LIKE pattern to a GLOB pattern for "LIKE BINARY" support. + + * @TODO: Some of the MySQL string specifics described below are likely to + * affect also other patterns than just "LIKE BINARY". We should + * consider applying some of the conversions more broadly. + * + * @param string $pattern + * @return string + */ + public function _helper_like_to_glob_pattern( $pattern ) { + if ( null === $pattern ) { + return null; + } + + /* + * 1. Escape characters that have special meaning in GLOB patterns. + * + * We need to: + * 1. Escape "]" as "[]]" to avoid interpreting "[...]" as a character class. + * 2. Escape "*" as "[*]" (must be after 1 to avoid being escaped). + * 3. Escape "?" as "[?]" (must be after 1 to avoid being escaped). + */ + $pattern = str_replace( ']', '[]]', $pattern ); + $pattern = str_replace( '*', '[*]', $pattern ); + $pattern = str_replace( '?', '[?]', $pattern ); + + /* + * 2. Convert LIKE wildcards to GLOB wildcards ("%" -> "*", "_" -> "?"). + * + * We need to convert them only when they don't follow any backslashes, + * or when they follow an even number of backslashes (as "\\" is "\"). + */ + $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)%/', '$1*', $pattern ); + $pattern = preg_replace( '/(^|[^\\\\](?:\\\\{2})*)_/', '$1?', $pattern ); + + /* + * 3. Unescape LIKE escape sequences. + * + * While in MySQL LIKE patterns, a backslash is usually used to escape + * special characters ("%", "_", and "\"), it works with all characters. + * + * That is: + * SELECT '\\x' prints '\x', but LIKE '\\x' is equivalent to LIKE 'x'. + * + * This is true also for multi-byte characters: + * SELECT '\\©' prints '\©', but LIKE '\\©' is equivalent to LIKE '©'. + * + * However, the multi-byte behavior is likely to depend on the charset. + * For now, we'll assume UTF-8 and thus the "u" modifier for the regex. + */ + $pattern = preg_replace( '/\\\\(.)/u', '$1', $pattern ); + + return $pattern; + } +} diff --git a/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-query-rewriter.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php diff --git a/wp-includes/sqlite/class-wp-sqlite-token.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-token.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-token.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-token.php diff --git a/wp-includes/sqlite/class-wp-sqlite-translator.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php similarity index 100% rename from wp-includes/sqlite/class-wp-sqlite-translator.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php diff --git a/wp-includes/sqlite/db.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php similarity index 75% rename from wp-includes/sqlite/db.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php index dc50d22e..dc4d14f8 100644 --- a/wp-includes/sqlite/db.php +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/db.php @@ -9,10 +9,10 @@ /** * Load the "SQLITE_DRIVER_VERSION" constant. */ -require_once dirname( __DIR__, 2 ) . '/version.php'; +require_once __DIR__ . '/../database/version.php'; // Require the constants file. -require_once dirname( __DIR__, 2 ) . '/constants.php'; +require_once __DIR__ . '/../../constants.php'; // Bail early if DB_ENGINE is not defined as sqlite. if ( ! defined( 'DB_ENGINE' ) || 'sqlite' !== DB_ENGINE ) { @@ -47,11 +47,16 @@ ); } -require_once __DIR__ . '/class-wp-sqlite-lexer.php'; -require_once __DIR__ . '/class-wp-sqlite-query-rewriter.php'; -require_once __DIR__ . '/class-wp-sqlite-translator.php'; -require_once __DIR__ . '/class-wp-sqlite-token.php'; -require_once __DIR__ . '/class-wp-sqlite-pdo-user-defined-functions.php'; +if ( defined( 'WP_SQLITE_AST_DRIVER' ) && WP_SQLITE_AST_DRIVER ) { + require_once __DIR__ . '/../database/load.php'; +} else { + require_once __DIR__ . '/php-polyfills.php'; + require_once __DIR__ . '/class-wp-sqlite-lexer.php'; + require_once __DIR__ . '/class-wp-sqlite-query-rewriter.php'; + require_once __DIR__ . '/class-wp-sqlite-translator.php'; + require_once __DIR__ . '/class-wp-sqlite-token.php'; + require_once __DIR__ . '/class-wp-sqlite-pdo-user-defined-functions.php'; +} require_once __DIR__ . '/class-wp-sqlite-db.php'; require_once __DIR__ . '/install-functions.php'; @@ -76,7 +81,7 @@ * that are present in the GitHub repository * but not the plugin published on WordPress.org. */ -$crosscheck_tests_file_path = dirname( __DIR__, 2 ) . '/tests/class-wp-sqlite-crosscheck-db.php'; +$crosscheck_tests_file_path = __DIR__ . '/class-wp-sqlite-crosscheck-db.php'; if ( defined( 'SQLITE_DEBUG_CROSSCHECK' ) && SQLITE_DEBUG_CROSSCHECK && file_exists( $crosscheck_tests_file_path ) ) { require_once $crosscheck_tests_file_path; $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( $db_name ); @@ -84,5 +89,5 @@ $GLOBALS['wpdb'] = new WP_SQLite_DB( $db_name ); // Boot the Query Monitor plugin if it is active. - require_once dirname( __DIR__, 2 ) . '/integrations/query-monitor/boot.php'; + require_once __DIR__ . '/../../integrations/query-monitor/boot.php'; } diff --git a/wp-includes/sqlite/install-functions.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/install-functions.php similarity index 100% rename from wp-includes/sqlite/install-functions.php rename to packages/plugin-sqlite-database-integration/wp-includes/sqlite/install-functions.php diff --git a/packages/plugin-sqlite-database-integration/wp-includes/sqlite/php-polyfills.php b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/php-polyfills.php new file mode 100644 index 00000000..b3ab8ed6 --- /dev/null +++ b/packages/plugin-sqlite-database-integration/wp-includes/sqlite/php-polyfills.php @@ -0,0 +1,68 @@ + /vendor/* /node_modules/* + /build/* /wordpress/* - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php + /packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - warning - - /tests/phpunit/* - - - warning - - - warning - - - warning - - - warning - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /wp-includes/sqlite/class-wp-sqlite-translator.php + /packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php + /packages/mysql-on-sqlite/src/sqlite/*\.php + /tests/* + + + + /tests/* @@ -168,34 +76,11 @@ * - - /src/wp-includes/sqlite/*\.php - /tests/* - - /tests/* - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /tests/* - - - /tests/* - /tests/* - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - - - /wp-includes/sqlite/class-wp-sqlite-crosscheck-db.php - diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 2340405a..134b752a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,9 +14,10 @@ - tests/ - - tests/tools + tests/ + tests/e2e/ + packages/ + wordpress/ diff --git a/tests/WP_SQLite_Translator_Tests.php b/tests/WP_SQLite_Translator_Tests.php index 80ec49d8..a3a1dc02 100644 --- a/tests/WP_SQLite_Translator_Tests.php +++ b/tests/WP_SQLite_Translator_Tests.php @@ -18,7 +18,7 @@ public function setUp(): void { // Skip all old driver tests when running on legacy SQLite version. // The old driver is to be removed in favor of the new AST driver, // so this is just a temporary measure to pass all CI combinations. - $is_legacy_sqlite = version_compare( $this->engine->get_sqlite_version(), WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' ); + $is_legacy_sqlite = version_compare( $this->engine->get_sqlite_version(), '3.37.0', '<' ); if ( $is_legacy_sqlite ) { $this->markTestSkipped( "The old SQLite driver doesn't pass some test on legacy SQLite versions" ); return; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 88217cfa..f2d89b4b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,19 +1,11 @@ query( 'SELECT SQLITE_VERSION();' )->fetch()[0]; -if ( version_compare( $sqlite_version, WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' ) ) { - define( 'WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS', true ); -} +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-query-rewriter.php'; +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-lexer.php'; +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-token.php'; +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-pdo-user-defined-functions.php'; +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/class-wp-sqlite-translator.php'; // Configure the test environment. error_reporting( E_ALL ); @@ -51,6 +43,8 @@ function apply_filters( $tag, $value, ...$args ) { } } +require_once __DIR__ . '/../packages/plugin-sqlite-database-integration/wp-includes/sqlite/php-polyfills.php'; + if ( extension_loaded( 'mbstring' ) ) { if ( ! function_exists( 'mb_str_starts_with' ) ) { diff --git a/wp-pdo-mysql-on-sqlite.php b/wp-pdo-mysql-on-sqlite.php index 22b090d4..83610f3a 100644 --- a/wp-pdo-mysql-on-sqlite.php +++ b/wp-pdo-mysql-on-sqlite.php @@ -1,25 +1,5 @@