Skip to content

Commit 6c33b13

Browse files
committed
Expand concurrency tests for write and read-only transaction locking
Pin BEGIN IMMEDIATE for write statements (INSERT, UPDATE, DELETE, REPLACE, CREATE/ALTER/DROP/TRUNCATE TABLE) so a future regression that accidentally flags a write as read-only — downgrading the lock to a deferred BEGIN — is caught immediately. Mirror the existing SELECT two-connection contention test for SHOW and DESCRIBE, proving they succeed under a concurrent RESERVED lock instead of only asserting the logged BEGIN string. Factor out a couple of small helpers to remove the repeated driver and query-logger setup boilerplate.
1 parent c559b3a commit 6c33b13

1 file changed

Lines changed: 52 additions & 58 deletions

File tree

packages/mysql-on-sqlite/tests/WP_SQLite_Driver_Concurrency_Tests.php

Lines changed: 52 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -32,84 +32,72 @@ public function tearDown(): void {
3232
$this->db_path = null;
3333
}
3434

35-
/**
36-
* A SELECT should not be wrapped in a transaction — no BEGIN at all.
37-
*/
3835
public function testSelectQueryIsNotWrappedInTransaction(): void {
39-
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
40-
$pdo = new $pdo_class( 'sqlite::memory:' );
41-
42-
$connection = new WP_SQLite_Connection( array( 'pdo' => $pdo ) );
43-
$driver = new WP_SQLite_Driver( $connection, 'wp' );
36+
$driver = $this->create_in_memory_driver();
4437
$driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' );
4538

46-
// Capture SQLite queries. The logger must be set on the driver's
47-
// internal connection, not the original one passed to the constructor.
48-
$logged_queries = array();
49-
$driver->get_connection()->set_query_logger(
50-
function ( string $sql, array $params ) use ( &$logged_queries ): void {
51-
$logged_queries[] = $sql;
52-
}
53-
);
54-
5539
$driver->query( 'SELECT * FROM t' );
5640

57-
$this->assertStringStartsNotWith( 'BEGIN', $logged_queries[0] );
41+
$this->assertStringStartsNotWith( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] );
5842
}
5943

60-
/**
61-
* A SHOW statement should use a deferred BEGIN (SHARED lock), not
62-
* BEGIN IMMEDIATE (RESERVED/write lock).
63-
*/
6444
public function testShowQueryOpensReadOnlyTransaction(): void {
65-
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
66-
$pdo = new $pdo_class( 'sqlite::memory:' );
67-
68-
$connection = new WP_SQLite_Connection( array( 'pdo' => $pdo ) );
69-
$driver = new WP_SQLite_Driver( $connection, 'wp' );
45+
$driver = $this->create_in_memory_driver();
7046
$driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' );
7147

72-
$logged_queries = array();
73-
$driver->get_connection()->set_query_logger(
74-
function ( string $sql, array $params ) use ( &$logged_queries ): void {
75-
$logged_queries[] = $sql;
76-
}
77-
);
78-
7948
$driver->query( 'SHOW TABLES' );
8049

81-
$this->assertSame( 'BEGIN', $logged_queries[0] );
50+
$this->assertSame( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] );
8251
}
8352

84-
/**
85-
* A DESCRIBE statement should use a deferred BEGIN (SHARED lock), not
86-
* BEGIN IMMEDIATE (RESERVED/write lock).
87-
*/
8853
public function testDescribeQueryOpensReadOnlyTransaction(): void {
89-
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
90-
$pdo = new $pdo_class( 'sqlite::memory:' );
91-
92-
$connection = new WP_SQLite_Connection( array( 'pdo' => $pdo ) );
93-
$driver = new WP_SQLite_Driver( $connection, 'wp' );
54+
$driver = $this->create_in_memory_driver();
9455
$driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' );
9556

96-
$logged_queries = array();
97-
$driver->get_connection()->set_query_logger(
98-
function ( string $sql, array $params ) use ( &$logged_queries ): void {
99-
$logged_queries[] = $sql;
100-
}
101-
);
102-
10357
$driver->query( 'DESCRIBE t' );
10458

105-
$this->assertSame( 'BEGIN', $logged_queries[0] );
59+
$this->assertSame( 'BEGIN', $driver->get_last_sqlite_queries()[0]['sql'] );
10660
}
10761

10862
/**
109-
* A SELECT on one connection should succeed even when another connection
110-
* holds an open write transaction (RESERVED lock).
63+
* @dataProvider provideWriteStatements
11164
*/
65+
public function testWriteQueryOpensWriteTransaction( string $query ): void {
66+
$driver = $this->create_in_memory_driver();
67+
$driver->query( 'CREATE TABLE t (id INT, name VARCHAR(255))' );
68+
$driver->query( "INSERT INTO t VALUES (1, 'Alice')" );
69+
70+
$driver->query( $query );
71+
72+
$this->assertSame( 'BEGIN IMMEDIATE', $driver->get_last_sqlite_queries()[0]['sql'] );
73+
}
74+
75+
public function provideWriteStatements(): array {
76+
return array(
77+
'INSERT' => array( "INSERT INTO t VALUES (2, 'Bob')" ),
78+
'UPDATE' => array( "UPDATE t SET name = 'Carol' WHERE id = 1" ),
79+
'DELETE' => array( 'DELETE FROM t WHERE id = 1' ),
80+
'REPLACE' => array( "REPLACE INTO t VALUES (1, 'Dan')" ),
81+
'CREATE TABLE' => array( 'CREATE TABLE u (id INT)' ),
82+
'ALTER TABLE' => array( 'ALTER TABLE t ADD COLUMN x INT' ),
83+
'DROP TABLE' => array( 'DROP TABLE t' ),
84+
'TRUNCATE TABLE' => array( 'TRUNCATE TABLE t' ),
85+
);
86+
}
87+
11288
public function testSelectQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void {
89+
$this->assertReadOnlyQuerySucceedsUnderWriteLock( 'SELECT * FROM t' );
90+
}
91+
92+
public function testShowQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void {
93+
$this->assertReadOnlyQuerySucceedsUnderWriteLock( 'SHOW TABLES' );
94+
}
95+
96+
public function testDescribeQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): void {
97+
$this->assertReadOnlyQuerySucceedsUnderWriteLock( 'DESCRIBE t' );
98+
}
99+
100+
private function assertReadOnlyQuerySucceedsUnderWriteLock( string $query ): void {
113101
// Connection A: set up the database.
114102
$conn_a = new WP_SQLite_Connection( array( 'path' => $this->db_path ) );
115103
$driver_a = new WP_SQLite_Driver( $conn_a, 'wp' );
@@ -130,13 +118,19 @@ public function testSelectQuerySucceedsWhileAnotherConnectionHoldsWriteLock(): v
130118
$driver_b = new WP_SQLite_Driver( $conn_b, 'wp' );
131119
$conn_b->get_pdo()->setAttribute( PDO::ATTR_TIMEOUT, 0 );
132120

133-
$result = $driver_b->query( 'SELECT * FROM t' );
121+
$result = $driver_b->query( $query );
134122

135-
$this->assertCount( 1, $result );
136-
$this->assertSame( '1', $result[0]->id );
137-
$this->assertSame( 'Alice', $result[0]->name );
123+
$this->assertIsArray( $result );
124+
$this->assertNotEmpty( $result );
138125
} finally {
139126
$conn_a->get_pdo()->exec( 'ROLLBACK' );
140127
}
141128
}
129+
130+
private function create_in_memory_driver(): WP_SQLite_Driver {
131+
$pdo_class = PHP_VERSION_ID >= 80400 ? PDO\SQLite::class : PDO::class;
132+
$pdo = new $pdo_class( 'sqlite::memory:' );
133+
$connection = new WP_SQLite_Connection( array( 'pdo' => $pdo ) );
134+
return new WP_SQLite_Driver( $connection, 'wp' );
135+
}
142136
}

0 commit comments

Comments
 (0)