Skip to content

Commit 4b0869d

Browse files
committed
Respect PDO::query() fetch mode arguments
1 parent 90fc34f commit 4b0869d

File tree

2 files changed

+184
-2
lines changed

2 files changed

+184
-2
lines changed

tests/WP_PDO_MySQL_On_SQLite_PDO_API_Tests.php

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,106 @@ public function test_query(): void {
3232
);
3333
}
3434

35+
/**
36+
* @dataProvider data_pdo_fetch_methods
37+
*/
38+
public function test_query_with_fetch_mode( $query, $mode, $expected ): void {
39+
$stmt = $this->driver->query( $query, $mode );
40+
$result = $stmt->fetch();
41+
if ( is_object( $expected ) ) {
42+
$this->assertInstanceOf( get_class( $expected ), $result );
43+
$this->assertEquals( $expected, $result );
44+
} else {
45+
$this->assertSame( $expected, $result );
46+
}
47+
48+
$this->assertFalse( $stmt->fetch() );
49+
}
50+
51+
public function test_query_fetch_mode_not_set(): void {
52+
$result = $this->driver->query( 'SELECT 1' );
53+
$this->assertSame(
54+
array(
55+
'1' => 1,
56+
0 => 1,
57+
),
58+
$result->fetch()
59+
);
60+
$this->assertFalse( $result->fetch() );
61+
}
62+
63+
public function test_query_fetch_mode_invalid_arg_count(): void {
64+
$this->expectException( ArgumentCountError::class );
65+
$this->expectExceptionMessage( 'PDO::query() expects exactly 2 arguments for the fetch mode provided, 3 given' );
66+
$this->driver->query( 'SELECT 1', PDO::FETCH_ASSOC, 0 );
67+
}
68+
69+
public function test_query_fetch_default_mode_allow_any_args(): void {
70+
$expected_result = array(
71+
array(
72+
1 => 1,
73+
0 => 1,
74+
),
75+
);
76+
77+
$result = $this->driver->query( 'SELECT 1' );
78+
$this->assertSame( $expected_result, $result->fetchAll() );
79+
80+
$result = $this->driver->query( 'SELECT 1', null );
81+
$this->assertSame( $expected_result, $result->fetchAll() );
82+
83+
$result = $this->driver->query( 'SELECT 1', null, 1 );
84+
$this->assertSame( $expected_result, $result->fetchAll() );
85+
86+
$result = $this->driver->query( 'SELECT 1', null, 'abc' );
87+
$this->assertSame( $expected_result, $result->fetchAll() );
88+
89+
$result = $this->driver->query( 'SELECT 1', null, 1, 2, 'abc', array(), true );
90+
$this->assertSame( $expected_result, $result->fetchAll() );
91+
}
92+
93+
public function test_query_fetch_class_not_enough_args(): void {
94+
$this->expectException( ArgumentCountError::class );
95+
$this->expectExceptionMessage( 'PDO::query() expects at least 3 arguments for the fetch mode provided, 2 given' );
96+
$this->driver->query( 'SELECT 1', PDO::FETCH_CLASS );
97+
}
98+
99+
public function test_query_fetch_class_too_many_args(): void {
100+
$this->expectException( ArgumentCountError::class );
101+
$this->expectExceptionMessage( 'PDO::query() expects at most 4 arguments for the fetch mode provided, 5 given' );
102+
$this->driver->query( 'SELECT 1', PDO::FETCH_CLASS, '\stdClass', array(), array() );
103+
}
104+
105+
public function test_query_fetch_class_invalid_class_type(): void {
106+
$this->expectException( TypeError::class );
107+
$this->expectExceptionMessage( 'PDO::query(): Argument #3 must be of type string, int given' );
108+
$this->driver->query( 'SELECT 1', PDO::FETCH_CLASS, 1 );
109+
}
110+
111+
public function test_query_fetch_class_invalid_class_name(): void {
112+
$this->expectException( TypeError::class );
113+
$this->expectExceptionMessage( 'PDO::query(): Argument #3 must be a valid class' );
114+
$this->driver->query( 'SELECT 1', PDO::FETCH_CLASS, 'non-existent-class' );
115+
}
116+
117+
public function test_query_fetch_class_invalid_constructor_args_type(): void {
118+
$this->expectException( TypeError::class );
119+
$this->expectExceptionMessage( 'PDO::query(): Argument #4 must be of type ?array, int given' );
120+
$this->driver->query( 'SELECT 1', PDO::FETCH_CLASS, 'stdClass', 1 );
121+
}
122+
123+
public function test_query_fetch_into_invalid_arg_count(): void {
124+
$this->expectException( ArgumentCountError::class );
125+
$this->expectExceptionMessage( 'PDO::query() expects exactly 3 arguments for the fetch mode provided, 2 given' );
126+
$this->driver->query( 'SELECT 1', PDO::FETCH_INTO );
127+
}
128+
129+
public function test_query_fetch_into_invalid_object_type(): void {
130+
$this->expectException( TypeError::class );
131+
$this->expectExceptionMessage( 'PDO::query(): Argument #3 must be of type object, int given' );
132+
$this->driver->query( 'SELECT 1', PDO::FETCH_INTO, 1 );
133+
}
134+
35135
public function test_exec(): void {
36136
$result = $this->driver->exec( 'SELECT 1' );
37137
$this->assertEquals( 0, $result );

wp-includes/sqlite-ast/class-wp-pdo-mysql-on-sqlite.php

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,86 @@ function ( string $sql, array $params ) {
736736
* @throws WP_SQLite_Driver_Exception When the query execution fails.
737737
*/
738738
#[ReturnTypeWillChange]
739-
public function query( string $query, ?int $fetch_mode = PDO::FETCH_COLUMN, ...$fetch_mode_args ) {
739+
public function query( string $query, ?int $fetch_mode = null, ...$fetch_mode_args ) {
740+
// Validate and parse the fetch mode and arguments.
741+
$arg_count = func_num_args();
742+
$arg_colno = 0;
743+
$arg_class = null;
744+
$arg_constructor_args = array();
745+
$arg_into = null;
746+
747+
$get_type = function ( $value ) {
748+
$type = gettype( $value );
749+
if ( 'boolean' === $type ) {
750+
return 'bool';
751+
} elseif ( 'integer' === $type ) {
752+
return 'int';
753+
} elseif ( 'double' === $type ) {
754+
return 'float';
755+
}
756+
return $type;
757+
};
758+
759+
if ( null === $fetch_mode ) {
760+
// When the default FETCH_BOTH is not set explicitly, additional
761+
// arguments are ignored, and the argument count is not validated.
762+
$fetch_mode = PDO::FETCH_BOTH;
763+
} elseif ( PDO::FETCH_COLUMN === $fetch_mode ) {
764+
if ( 3 !== $arg_count ) {
765+
throw new ArgumentCountError(
766+
sprintf( 'PDO::query() expects exactly 3 arguments for the fetch mode provided, %d given', $arg_count )
767+
);
768+
}
769+
if ( ! is_int( $fetch_mode_args[0] ) ) {
770+
throw new TypeError(
771+
sprintf( 'PDO::query(): Argument #3 must be of type int, %s given', $get_type( $fetch_mode_args[0] ) )
772+
);
773+
}
774+
$arg_colno = $fetch_mode_args[0];
775+
} elseif ( PDO::FETCH_CLASS === $fetch_mode ) {
776+
if ( $arg_count < 3 ) {
777+
throw new ArgumentCountError(
778+
sprintf( 'PDO::query() expects at least 3 arguments for the fetch mode provided, %d given', $arg_count )
779+
);
780+
}
781+
if ( $arg_count > 4 ) {
782+
throw new ArgumentCountError(
783+
sprintf( 'PDO::query() expects at most 4 arguments for the fetch mode provided, %d given', $arg_count )
784+
);
785+
}
786+
if ( ! is_string( $fetch_mode_args[0] ) ) {
787+
throw new TypeError(
788+
sprintf( 'PDO::query(): Argument #3 must be of type string, %s given', $get_type( $fetch_mode_args[0] ) )
789+
);
790+
}
791+
if ( ! class_exists( $fetch_mode_args[0] ) ) {
792+
throw new TypeError( 'PDO::query(): Argument #3 must be a valid class' );
793+
}
794+
if ( 4 === $arg_count && ! is_array( $fetch_mode_args[1] ) ) {
795+
throw new TypeError(
796+
sprintf( 'PDO::query(): Argument #4 must be of type ?array, %s given', $get_type( $fetch_mode_args[1] ) )
797+
);
798+
}
799+
$arg_class = $fetch_mode_args[0];
800+
$arg_constructor_args = $fetch_mode_args[1] ?? array();
801+
} elseif ( PDO::FETCH_INTO === $fetch_mode ) {
802+
if ( 3 !== $arg_count ) {
803+
throw new ArgumentCountError(
804+
sprintf( 'PDO::query() expects exactly 3 arguments for the fetch mode provided, %d given', $arg_count )
805+
);
806+
}
807+
if ( ! is_object( $fetch_mode_args[0] ) ) {
808+
throw new TypeError(
809+
sprintf( 'PDO::query(): Argument #3 must be of type object, %s given', $get_type( $fetch_mode_args[0] ) )
810+
);
811+
}
812+
$arg_into = $fetch_mode_args[0];
813+
} elseif ( $arg_count > 2 ) {
814+
throw new ArgumentCountError(
815+
sprintf( 'PDO::query() expects exactly 2 arguments for the fetch mode provided, %d given', $arg_count )
816+
);
817+
}
818+
740819
$this->flush();
741820
$this->last_mysql_query = $query;
742821

@@ -794,7 +873,10 @@ public function query( string $query, ?int $fetch_mode = PDO::FETCH_COLUMN, ...$
794873
$columns = is_array( $this->last_column_meta ) ? $this->last_column_meta : array();
795874
$rows = is_array( $this->last_result ) ? $this->last_result : array();
796875
$affected_rows = is_int( $this->last_return_value ) ? $this->last_return_value : 0;
797-
return new WP_PDO_Synthetic_Statement( $this, $columns, $rows, $affected_rows );
876+
877+
$stmt = new WP_PDO_Synthetic_Statement( $this, $columns, $rows, $affected_rows );
878+
$stmt->setFetchMode( $fetch_mode, ...$fetch_mode_args );
879+
return $stmt;
798880
} catch ( Throwable $e ) {
799881
try {
800882
$this->rollback_user_transaction();

0 commit comments

Comments
 (0)