@@ -258,88 +258,102 @@ public static function dataTypeProvider(): array
258258 {
259259 return [
260260 // Integer Types
261- ['TINYINT ' , 127 , 127 ],
262- ['TINYINT UNSIGNED ' , 255 , -1 ], // MySQL binlog represents max unsigned as -1
263- ['SMALLINT ' , 32767 , 32767 ],
264- ['SMALLINT UNSIGNED ' , 65535 , -1 ], // MySQL binlog represents max unsigned as -1
265- ['MEDIUMINT ' , 8388607 , 8388607 ],
266- ['MEDIUMINT UNSIGNED ' , 16777215 , -1 ], // MySQL binlog represents max unsigned as -1
267- ['INT ' , 2147483647 , 2147483647 ],
268- ['INT UNSIGNED ' , 4294967295 , -1 ], // MySQL binlog represents max unsigned as -1
269- ['BIGINT ' , '9223372036854775807 ' , '9223372036854775807 ' ],
270- ['BIGINT UNSIGNED ' , '18446744073709551615 ' , -1 ], // MySQL binlog represents max unsigned as -1
271- ['BIT(8) ' , 'b \'11111111 \'' , 255 ],
272- ['BIT(1) ' , 'b \'1 \'' , 1 ],
261+ ['TINYINT ' , 127 , 127 , null ],
262+ ['TINYINT UNSIGNED ' , 255 , -1 , null ], // MySQL binlog represents max unsigned as -1
263+ ['SMALLINT ' , 32767 , 32767 , null ],
264+ ['SMALLINT UNSIGNED ' , 65535 , -1 , null ], // MySQL binlog represents max unsigned as -1
265+ ['MEDIUMINT ' , 8388607 , 8388607 , null ],
266+ ['MEDIUMINT UNSIGNED ' , 16777215 , -1 , null ], // MySQL binlog represents max unsigned as -1
267+ ['INT ' , 2147483647 , 2147483647 , null ],
268+ ['INT UNSIGNED ' , 4294967295 , -1 , null ], // MySQL binlog represents max unsigned as -1
269+ ['BIGINT ' , '9223372036854775807 ' , '9223372036854775807 ' , null ],
270+ ['BIGINT UNSIGNED ' , '18446744073709551615 ' , -1 , null ], // MySQL binlog represents max unsigned as -1
271+ ['BIT(8) ' , 'b \'11111111 \'' , 255 , null ],
272+ ['BIT(1) ' , 'b \'1 \'' , 1 , null ],
273273
274274 // Fixed-Point Types
275- ['DECIMAL(10,2) ' , '123.45 ' , '123.45 ' ],
276- ['DECIMAL(5,0) ' , '12345 ' , '12345 ' ],
277- ['NUMERIC(8,3) ' , '12345.678 ' , '12345.678 ' ],
275+ ['DECIMAL(10,2) ' , '123.45 ' , '123.45 ' , null ],
276+ ['DECIMAL(5,0) ' , '12345 ' , '12345 ' , null ],
277+ ['NUMERIC(8,3) ' , '12345.678 ' , '12345.678 ' , null ],
278278
279279 // Floating-Point Types
280- ['FLOAT ' , 123.456 , 123.456 ],
281- ['DOUBLE ' , 123.456789 , 123.456789 ],
280+ ['FLOAT ' , 123.456 , 123.456 , null ],
281+ ['DOUBLE ' , 123.456789 , 123.456789 , null ],
282282
283283 // Character Types
284- ['CHAR(10) ' , '\'Hello \'' , 'Hello ' ],
285- ['VARCHAR(50) ' , '\'Variable length \'' , 'Variable length ' ],
286- ['BINARY(5) ' , 'X \'48656c6c6f \'' , 'Hello ' ], // Use hex notation for binary data
287- ['VARBINARY(10) ' , 'X \'48656c6c6f \'' , 'Hello ' ], // Use hex notation for binary data
284+ ['CHAR(10) ' , '\'Hello \'' , 'Hello ' , null ],
285+ ['VARCHAR(50) ' , '\'Variable length \'' , 'Variable length ' , null ],
286+ ['BINARY(5) ' , 'X \'48656c6c6f \'' , 'Hello ' , null ], // Use hex notation for binary data
287+ ['VARBINARY(10) ' , 'X \'48656c6c6f \'' , 'Hello ' , null ], // Use hex notation for binary data
288288
289289 // Text Types - these are base64 encoded in binlog
290- ['TINYTEXT ' , '\'Tiny text \'' , 'VGlueSB0ZXh0 ' ],
291- ['TEXT ' , '\'Regular text content \'' , 'UmVndWxhciB0ZXh0IGNvbnRlbnQ= ' ],
292- ['MEDIUMTEXT ' , '\'Medium text content \'' , 'TWVkaXVtIHRleHQgY29udGVudA== ' ],
293- ['LONGTEXT ' , '\'Long text content \'' , 'TG9uZyB0ZXh0IGNvbnRlbnQ= ' ],
290+ ['TINYTEXT ' , '\'Tiny text \'' , 'VGlueSB0ZXh0 ' , null ],
291+ ['TEXT ' , '\'Regular text content \'' , 'UmVndWxhciB0ZXh0IGNvbnRlbnQ= ' , null ],
292+ ['MEDIUMTEXT ' , '\'Medium text content \'' , 'TWVkaXVtIHRleHQgY29udGVudA== ' , null ],
293+ ['LONGTEXT ' , '\'Long text content \'' , 'TG9uZyB0ZXh0IGNvbnRlbnQ= ' , null ],
294294
295295 // Binary Large Object Types - these are base64 encoded in binlog
296- ['TINYBLOB ' , 'X \'48656c6c6f \'' , 'SGVsbG8= ' ], // "Hello" in base64
297- ['BLOB ' , 'X \'48656c6c6f20576f726c64 \'' , 'SGVsbG8gV29ybGQ= ' ], // "Hello World" in base64
298- ['MEDIUMBLOB ' , 'X \'48656c6c6f204d656469756d \'' , 'SGVsbG8gTWVkaXVt ' ], // "Hello Medium" in base64
299- ['LONGBLOB ' , 'X \'48656c6c6f204c6f6e67 \'' , 'SGVsbG8gTG9uZw== ' ], // "Hello Long" in base64
296+ ['TINYBLOB ' , 'X \'48656c6c6f \'' , 'SGVsbG8= ' , null ], // "Hello" in base64
297+ ['BLOB ' , 'X \'48656c6c6f20576f726c64 \'' , 'SGVsbG8gV29ybGQ= ' , null ], // "Hello World" in base64
298+ ['MEDIUMBLOB ' , 'X \'48656c6c6f204d656469756d \'' , 'SGVsbG8gTWVkaXVt ' , null ], // "Hello Medium" in base64
299+ ['LONGBLOB ' , 'X \'48656c6c6f204c6f6e67 \'' , 'SGVsbG8gTG9uZw== ' , null ], // "Hello Long" in base64
300300
301301 // Special String Types - now return actual string values with fix-enum-set-metadata branch
302- ['ENUM( \'red \', \'green \', \'blue \') ' , '\'red \'' , 'red ' ], // ENUM returns actual string value
303- ['SET( \'read \', \'write \', \'execute \') ' , '\'read,write \'' , ['read ' , 'write ' ]], // SET returns array of strings
302+ ['ENUM( \'red \', \'green \', \'blue \') ' , '\'red \'' , 'red ' , null ], // ENUM returns actual string value
303+ ['SET( \'read \', \'write \', \'execute \') ' , '\'read,write \'' , ['read ' , 'write ' ], null ], // SET returns array of strings
304304
305305 // Date and Time Data Types
306- ['DATE ' , '\'2024-01-15 \'' , '2024-01-15 ' ],
307- ['TIME ' , '\'14:30:45 \'' , '14:30:45.000000 ' ], // TIME includes microseconds
308- ['DATETIME ' , '\'2024-01-15 14:30:45 \'' , new \DateTimeImmutable ('2024-01-15 14:30:45.000000 ' )], // DATETIME as DateTimeImmutable
309- ['TIMESTAMP ' , '\'2024-01-15 14:30:45 \'' , new \DateTimeImmutable ('2024-01-15 14:30:45 ' )], // TIMESTAMP as DateTimeImmutable
310- ['YEAR ' , '2024 ' , '2024 ' ],
306+ ['DATE ' , '\'2024-01-15 \'' , '2024-01-15 ' , null ],
307+ ['TIME ' , '\'14:30:45 \'' , '14:30:45.000000 ' , null ], // TIME includes microseconds
308+ ['DATETIME ' , '\'2024-01-15 14:30:45 \'' , new \DateTimeImmutable ('2024-01-15 14:30:45.000000 ' ), null ], // DATETIME as DateTimeImmutable
309+ ['TIMESTAMP ' , '\'2024-01-15 14:30:45 \'' , new \DateTimeImmutable ('2024-01-15 14:30:45 ' ), null ], // TIMESTAMP as DateTimeImmutable
310+ ['YEAR ' , '2024 ' , '2024 ' , null ],
311311
312- // JSON Data Type - now returns parsed stdClass objects
313- ['JSON ' , '\'{"key": "value", "number": 42} \'' , (object )['key ' => 'value ' , 'number ' => 42 ]],
312+ // JSON Data Type - MySQL returns parsed stdClass objects
313+ ['JSON ' , '\'{"key": "value", "number": 42} \'' , (object )['key ' => 'value ' , 'number ' => 42 ], 'mysql ' ],
314+ // JSON Data Type - MariaDB returns base64 encoded strings
315+ ['JSON ' , '\'{"key": "value", "number": 42} \'' , 'eyJrZXkiOiAidmFsdWUiLCAibnVtYmVyIjogNDJ9 ' , 'mariadb ' ],
314316
315317 // NULL values for various types
316- ['VARCHAR(50) ' , 'NULL ' , null ],
317- ['INT ' , 'NULL ' , null ],
318- ['DATE ' , 'NULL ' , null ],
319- ['JSON ' , 'NULL ' , null ],
318+ ['VARCHAR(50) ' , 'NULL ' , null , null ],
319+ ['INT ' , 'NULL ' , null , null ],
320+ ['DATE ' , 'NULL ' , null , null ],
321+ ['JSON ' , 'NULL ' , null , null ],
320322
321323 // Zero and empty values
322- ['INT ' , '0 ' , 0 ],
323- ['VARCHAR(50) ' , '\'\'' , '' ],
324- ['TEXT ' , '\'\'' , '' ],
324+ ['INT ' , '0 ' , 0 , null ],
325+ ['VARCHAR(50) ' , '\'\'' , '' , null ],
326+ ['TEXT ' , '\'\'' , '' , null ],
325327
326328 // Negative numbers
327- ['TINYINT ' , '-128 ' , -128 ],
328- ['SMALLINT ' , '-32768 ' , -32768 ],
329- ['MEDIUMINT ' , '-8388608 ' , -8388608 ],
330- ['INT ' , '-2147483648 ' , -2147483648 ],
331- ['BIGINT ' , '-9223372036854775808 ' , '-9223372036854775808 ' ],
332- ['DECIMAL(10,2) ' , '-123.45 ' , '-123.45 ' ],
333- ['FLOAT ' , '-123.456 ' , -123.456 ],
334- ['DOUBLE ' , '-123.456789 ' , -123.456789 ],
329+ ['TINYINT ' , '-128 ' , -128 , null ],
330+ ['SMALLINT ' , '-32768 ' , -32768 , null ],
331+ ['MEDIUMINT ' , '-8388608 ' , -8388608 , null ],
332+ ['INT ' , '-2147483648 ' , -2147483648 , null ],
333+ ['BIGINT ' , '-9223372036854775808 ' , '-9223372036854775808 ' , null ],
334+ ['DECIMAL(10,2) ' , '-123.45 ' , '-123.45 ' , null ],
335+ ['FLOAT ' , '-123.456 ' , -123.456 , null ],
336+ ['DOUBLE ' , '-123.456789 ' , -123.456789 , null ],
335337 ];
336338 }
337339
338340 #[DataProvider('dataTypeProvider ' )]
339- public function testDataTypeConversion (string $ columnType , $ insertValue , $ expectedPhpValue ): void
341+ public function testDataTypeConversion (string $ columnType , $ insertValue , $ expectedPhpValue, ? string $ databaseFlavor ): void
340342 {
341343 $ this ->requireDatabase ();
342344
345+ // Skip test if database flavor doesn't match
346+ if ($ databaseFlavor !== null ) {
347+ $ stmt = $ this ->pdo ->query ("SELECT VERSION() " );
348+ $ version = $ stmt ->fetchColumn ();
349+ $ isMariaDB = stripos ($ version , 'mariadb ' ) !== false ;
350+
351+ if (($ databaseFlavor === 'mariadb ' && !$ isMariaDB ) ||
352+ ($ databaseFlavor === 'mysql ' && $ isMariaDB )) {
353+ $ this ->markTestSkipped ("Test only runs on {$ databaseFlavor }" );
354+ }
355+ }
356+
343357 $ stream = null ;
344358 $ testTableName = 'test_data_types_ ' . md5 ($ columnType . serialize ($ insertValue ));
345359
@@ -353,11 +367,6 @@ public function testDataTypeConversion(string $columnType, $insertValue, $expect
353367 $ this ->dbConfig ['password ' ]
354368 );
355369
356- // Detect database type
357- $ stmt = $ testPdo ->query ("SELECT VERSION() " );
358- $ version = $ stmt ->fetchColumn ();
359- $ isMariaDB = stripos ($ version , 'mariadb ' ) !== false ;
360-
361370 $ createTableSql = "CREATE TABLE IF NOT EXISTS ` {$ testTableName }` (
362371 id INT AUTO_INCREMENT PRIMARY KEY,
363372 test_column {$ columnType }
@@ -383,12 +392,8 @@ public function testDataTypeConversion(string $columnType, $insertValue, $expect
383392 $ this ->assertEquals ($ testTableName , $ insertEvent ->table );
384393 $ this ->assertIsObject ($ insertEvent ->after );
385394
386- // Adjust expectations for MariaDB JSON handling
395+ // Use expected value as-is since database flavor filtering handles different expectations
387396 $ actualExpectedValue = $ expectedPhpValue ;
388- if ($ isMariaDB && strpos ($ columnType , 'JSON ' ) === 0 && is_object ($ expectedPhpValue ) && get_class ($ expectedPhpValue ) === 'stdClass ' ) {
389- // MariaDB returns JSON as base64 encoded string, not parsed object
390- $ actualExpectedValue = 'eyJrZXkiOiAidmFsdWUiLCAibnVtYmVyIjogNDJ9 ' ; // Base64 encoded
391- }
392397
393398 if ($ actualExpectedValue === null ) {
394399 $ this ->assertNull ($ insertEvent ->after ->test_column );
0 commit comments