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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/phpunit-tests-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ on:
required: false
type: 'string'
default: 'phpunit.xml.dist'
sqlite:
description: 'SQLite version to install (e.g., 3.24.0). Leave empty for latest version.'
required: false
type: 'string'
default: 'latest'
env:
LOCAL_PHP: ${{ inputs.php }}-fpm
PHPUNIT_CONFIG: ${{ inputs.phpunit-config }}
Expand All @@ -31,12 +36,42 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up SQLite
run: |
VERSION='${{ inputs.sqlite }}'
if [ "$VERSION" = 'latest' ]; then
TAG='release'
else
TAG="version-${VERSION}"
fi
wget -O sqlite.tar.gz "https://sqlite.org/src/tarball/sqlite.tar.gz?r=${TAG}"
tar xzf sqlite.tar.gz
cd sqlite
./configure --prefix=/usr/local CFLAGS="-DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_ENABLE_FTS5 -DSQLITE_USE_URI -DSQLITE_ENABLE_JSON1" LDFLAGS="-lm"
make -j$(nproc)
sudo make install
sudo ldconfig

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '${{ inputs.php }}'
tools: phpunit-polyfills

- name: Verify SQLite version in PHP
run: |
EXPECTED='${{ inputs.sqlite }}'
if [ "$EXPECTED" = 'latest' ]; then
EXPECTED=$(cat sqlite/VERSION)
fi
PDO=$(php -r "echo (new PDO('sqlite::memory'))->query('SELECT SQLITE_VERSION();')->fetch()[0];")
echo "Expected SQLite version: $EXPECTED"
echo "PHP PDO SQLite version: $PDO"
if [ "$EXPECTED" != "$PDO" ]; then
echo "Error: Expected SQLite version $EXPECTED, but PHP PDO uses $PDO"
exit 1
fi

- name: Install Composer dependencies
uses: ramsey/composer-install@v3
with:
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/phpunit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
test:
name: PHP ${{ matrix.php }}
name: PHP ${{ matrix.php }} / SQLite ${{ matrix.sqlite }}
uses: ./.github/workflows/phpunit-tests-run.yml
permissions:
contents: read
Expand All @@ -18,8 +18,21 @@ jobs:
matrix:
os: [ ubuntu-latest ]
php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ]
include:
# Add specific SQLite versions for specific PHP versions here:
- php: '7.2'
sqlite: '3.27.0' # minimum version with WP_SQLITE_UNSAFE_ENABLE_UNSUPPORTED_VERSIONS
- php: '7.3'
sqlite: '3.34.1' # Debian 11 (Bullseye), common with PHP < 8.1
- php: '7.4'
sqlite: '3.37.0' # minimum supported version (STRICT table support)
- php: '8.0'
sqlite: '3.40.1' # Debian 12 (Bookworm)
- php: '8.1'
sqlite: '3.46.1' # Debian 13 (Trixie), Ubuntu >= 24.10

with:
os: ${{ matrix.os }}
php: ${{ matrix.php }}
sqlite: ${{ matrix.sqlite || 'latest' }}
phpunit-config: ${{ 'phpunit.xml.dist' }}
84 changes: 58 additions & 26 deletions tests/WP_SQLite_Driver_Metadata_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function testInformationSchemaTables() {
"SELECT
table_name as 'name',
engine AS 'engine',
FLOOR( data_length / 1024 / 1024 ) 'data'
CAST( data_length / 1024 / 1024 AS UNSIGNED ) AS 'data'
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 't'
ORDER BY name ASC"
Expand Down Expand Up @@ -251,51 +251,83 @@ public function testCheckTable() {
$result
);

/**
* With SQLite < 3.33.0, the integrity check operation doesn't throw
* an error for missing tables. Let's reflect this in the assertions.
*/
$is_strict_integrity_check_supported = version_compare( $this->engine->get_sqlite_version(), '3.33.0', '>=' );

// A missing table.
$result = $this->assertQuery( 'CHECK TABLE missing' );
$this->assertEquals(
array(
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'Error',
'Msg_text' => "Table 'missing' doesn't exist",
),
$result = $this->assertQuery( 'CHECK TABLE missing' );
$expected = array(
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'Error',
'Msg_text' => "Table 'missing' doesn't exist",
),
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'Operation failed',
),
);

if ( ! $is_strict_integrity_check_supported ) {
$expected = array(
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'Operation failed',
'Msg_text' => 'OK',
),
);
}

$this->assertEquals( $expected, $result );

// One good and one missing table.
$result = $this->assertQuery( 'CHECK TABLE t1, missing' );
$expected = array(
(object) array(
'Table' => 'wp.t1',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'OK',
),
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'Error',
'Msg_text' => "Table 'missing' doesn't exist",
),
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'Operation failed',
),
$result
);

// One good and one missing table.
$result = $this->assertQuery( 'CHECK TABLE t1, missing' );
$this->assertEquals(
array(
if ( ! $is_strict_integrity_check_supported ) {
$expected = array(
(object) array(
'Table' => 'wp.t1',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'OK',
),
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'Error',
'Msg_text' => "Table 'missing' doesn't exist",
),
(object) array(
'Table' => 'wp.missing',
'Op' => 'check',
'Msg_type' => 'status',
'Msg_text' => 'Operation failed',
'Msg_text' => 'OK',
),
),
$result
);
);
}

$this->assertEquals( $expected, $result );
}

public function testOptimizeTable() {
Expand Down
93 changes: 81 additions & 12 deletions tests/WP_SQLite_Driver_Tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -6768,6 +6768,14 @@ public function testForeignKeyOnDeleteSetDefault(): void {
}

public function testUpdateWithJoinedTables(): void {
$sqlite_version = $this->engine->get_sqlite_version();
if ( version_compare( $sqlite_version, '3.33.0', '<' ) ) {
$this->markTestSkipped(
sprintf( "SQLite version %s doesn't support UPDATE with FROM clause.", $sqlite_version )
);
return;
}

$this->assertQuery( 'CREATE TABLE t1 (id INT, comment TEXT)' );
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
$this->assertQuery( 'CREATE TABLE t3 (id INT, name TEXT)' );
Expand Down Expand Up @@ -6841,6 +6849,14 @@ public function testUpdateWithJoinedTables(): void {
}

public function testUpdateWithJoinedTablesInNonStrictMode(): void {
$sqlite_version = $this->engine->get_sqlite_version();
if ( version_compare( $sqlite_version, '3.33.0', '<' ) ) {
$this->markTestSkipped(
sprintf( "SQLite version %s doesn't support UPDATE with FROM clause.", $sqlite_version )
);
return;
}

$this->assertQuery( "SET SESSION sql_mode = ''" );
$this->assertQuery( 'CREATE TABLE t1 (id INT, comment TEXT)' );
$this->assertQuery( 'CREATE TABLE t2 (id INT, name TEXT)' );
Expand Down Expand Up @@ -6915,6 +6931,14 @@ public function testUpdateWithJoinedTablesInNonStrictMode(): void {
}

public function testUpdateWithJoinComplexQuery(): void {
$sqlite_version = $this->engine->get_sqlite_version();
if ( version_compare( $sqlite_version, '3.33.0', '<' ) ) {
$this->markTestSkipped(
sprintf( "SQLite version %s doesn't support UPDATE with FROM clause.", $sqlite_version )
);
return;
}

$this->assertQuery( "SET SESSION sql_mode = ''" );

$default_date = '0000-00-00 00:00:00';
Expand Down Expand Up @@ -10118,10 +10142,17 @@ public function testCastValuesOnInsert(): void {
$this->assertQuery( "INSERT INTO t VALUES ('2')" );
$this->assertQuery( "INSERT INTO t VALUES ('3.0')" );

// TODO: These are supported in MySQL:
$this->assertQueryError( "INSERT INTO t VALUES ('4.5')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store REAL value in INTEGER column t.value' );
$this->assertQueryError( 'INSERT INTO t VALUES (0x05)', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$this->assertQueryError( "INSERT INTO t VALUES (x'06')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$is_legacy_sqlite = version_compare( $this->engine->get_sqlite_version(), WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' );
if ( $is_legacy_sqlite ) {
$this->assertQuery( "INSERT INTO t VALUES ('4.5')" );
$this->assertQuery( 'INSERT INTO t VALUES (0x05)' );
$this->assertQuery( "INSERT INTO t VALUES (x'06')" );
} else {
// TODO: These are supported in MySQL:
$this->assertQueryError( "INSERT INTO t VALUES ('4.5')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store REAL value in INTEGER column t.value' );
$this->assertQueryError( 'INSERT INTO t VALUES (0x05)', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$this->assertQueryError( "INSERT INTO t VALUES (x'06')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
}

$result = $this->assertQuery( 'SELECT * FROM t' );
$this->assertSame( null, $result[0]->value );
Expand All @@ -10146,8 +10177,13 @@ public function testCastValuesOnInsert(): void {
$this->assertQuery( "INSERT INTO t VALUES ('5')" );

// TODO: These are supported in MySQL:
$this->assertQueryError( 'INSERT INTO t VALUES (0x06)', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
$this->assertQueryError( "INSERT INTO t VALUES (x'07')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
if ( $is_legacy_sqlite ) {
$this->assertQuery( 'INSERT INTO t VALUES (0x06)' );
$this->assertQuery( "INSERT INTO t VALUES (x'07')" );
} else {
$this->assertQueryError( 'INSERT INTO t VALUES (0x06)', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
$this->assertQueryError( "INSERT INTO t VALUES (x'07')", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
}

$result = $this->assertQuery( 'SELECT * FROM t' );
$this->assertSame( null, $result[0]->value );
Expand Down Expand Up @@ -10613,10 +10649,17 @@ public function testCastValuesOnUpdate(): void {
$this->assertQuery( "UPDATE t SET value = '3.0'" );
$this->assertSame( '3', $this->assertQuery( 'SELECT * FROM t' )[0]->value );

// TODO: These are supported in MySQL:
$this->assertQueryError( "UPDATE t SET value = '4.5'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store REAL value in INTEGER column t.value' );
$this->assertQueryError( 'UPDATE t SET value = 0x05', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$this->assertQueryError( "UPDATE t SET value = x'06'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$is_legacy_sqlite = version_compare( $this->engine->get_sqlite_version(), WP_PDO_MySQL_On_SQLite::MINIMUM_SQLITE_VERSION, '<' );
if ( $is_legacy_sqlite ) {
$this->assertQuery( "UPDATE t SET value = '4.5'" );
$this->assertQuery( 'UPDATE t SET value = 0x05' );
$this->assertQuery( "UPDATE t SET value = x'06'" );
} else {
// TODO: These are supported in MySQL:
$this->assertQueryError( "UPDATE t SET value = '4.5'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store REAL value in INTEGER column t.value' );
$this->assertQueryError( 'UPDATE t SET value = 0x05', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
$this->assertQueryError( "UPDATE t SET value = x'06'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in INTEGER column t.value' );
}

$this->assertQuery( 'DROP TABLE t' );

Expand Down Expand Up @@ -10652,8 +10695,13 @@ public function testCastValuesOnUpdate(): void {
$this->assertSame( PHP_VERSION_ID < 80100 ? '5.0' : '5', $this->assertQuery( 'SELECT * FROM t' )[0]->value );

// TODO: These are supported in MySQL:
$this->assertQueryError( 'UPDATE t SET value = 0x06', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
$this->assertQueryError( "UPDATE t SET value = x'07'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
if ( $is_legacy_sqlite ) {
$this->assertQuery( 'UPDATE t SET value = 0x06' );
$this->assertQuery( "UPDATE t SET value = x'07'" );
} else {
$this->assertQueryError( 'UPDATE t SET value = 0x06', 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
$this->assertQueryError( "UPDATE t SET value = x'07'", 'SQLSTATE[23000]: Integrity constraint violation: 19 cannot store BLOB value in REAL column t.value' );
}

$this->assertQuery( 'DROP TABLE t' );

Expand Down Expand Up @@ -11231,4 +11279,25 @@ public function testVersionFunction(): void {
$result = $this->engine->query( 'SELECT VERSION()' );
$this->assertSame( '8.0.38', $result[0]->{'VERSION()'} );
}

public function testSubstringFunction(): void {
$result = $this->assertQuery( "SELECT SUBSTRING('abcdef', 1, 3) AS s" );
$this->assertSame( 'abc', $result[0]->s );

$result = $this->assertQuery( "SELECT SUBSTRING('abcdef', 4) AS s" );
$this->assertSame( 'def', $result[0]->s );

$result = $this->assertQuery( "SELECT SUBSTRING('abcdef' FROM 1 FOR 3) AS s" );
$this->assertSame( 'abc', $result[0]->s );

$result = $this->assertQuery( "SELECT SUBSTRING('abcdef' FROM 4) AS s" );
$this->assertSame( 'def', $result[0]->s );
}

public function testAsdf(): void {
$this->assertQuery( 'CREATE TABLE t (key1 INT, key2 INT)' );
$this->assertQuery( 'CREATE UNIQUE INDEX idx_key1_key2 ON t (key1, key2)' );
$this->assertQuery( 'INSERT INTO t (key1, key2) VALUES (1, 2) ON DUPLICATE KEY UPDATE key1 = 1, key2 = 2' );
$this->assertQuery( 'INSERT INTO t (key1, key2) VALUES (1, 2) ON DUPLICATE KEY UPDATE key1 = 1, key2 = 2' );
}
}
Loading
Loading