@@ -267,6 +267,23 @@ private function generate_column_definition( string $table_name, array $column_i
267
267
if ( null === $ mysql_type ) {
268
268
$ mysql_type = $ this ->get_mysql_column_type ( $ column_info ['type ' ] );
269
269
}
270
+
271
+ /**
272
+ * Correct some column types based on their default values:
273
+ * 1. In MySQL, non-datetime columns can't have a timestamp default.
274
+ * Let's use DATETIME when default is set to CURRENT_TIMESTAMP.
275
+ * 2. In MySQL, TEXT and BLOB columns can't have a default value.
276
+ * Let's use VARCHAR(65535) and VARBINARY(65535) when default is set.
277
+ */
278
+ $ default = $ this ->generate_column_default ( $ mysql_type , $ column_info ['dflt_value ' ] );
279
+ if ( 'CURRENT_TIMESTAMP ' === $ default ) {
280
+ $ mysql_type = 'datetime ' ;
281
+ } elseif ( 'text ' === $ mysql_type && null !== $ default ) {
282
+ $ mysql_type = 'varchar(65535) ' ;
283
+ } elseif ( 'blob ' === $ mysql_type && null !== $ default ) {
284
+ $ mysql_type = 'varbinary(65535) ' ;
285
+ }
286
+
270
287
$ definition [] = $ mysql_type ;
271
288
272
289
// NULL/NOT NULL.
@@ -288,8 +305,8 @@ private function generate_column_definition( string $table_name, array $column_i
288
305
}
289
306
290
307
// Default value.
291
- if ( $ this -> column_has_default ( $ mysql_type , $ column_info [ ' dflt_value ' ] ) && ! $ is_auto_increment ) {
292
- $ definition [] = 'DEFAULT ' . $ column_info [ ' dflt_value ' ] ;
308
+ if ( null !== $ default && ! $ is_auto_increment ) {
309
+ $ definition [] = 'DEFAULT ' . $ default ;
293
310
}
294
311
295
312
return implode ( ' ' , $ definition );
@@ -392,15 +409,15 @@ private function generate_key_definition( string $table_name, array $key_info, a
392
409
}
393
410
394
411
/**
395
- * Determine if a column has a default value.
412
+ * Generate a MySQL default value from an SQLite default value.
396
413
*
397
414
* @param string $mysql_type The MySQL data type of the column.
398
415
* @param string|null $default_value The default value of the SQLite column.
399
- * @return bool True if the column has a default value, false otherwise .
416
+ * @return string|null The default value, or null if the column has no default value.
400
417
*/
401
- private function column_has_default ( string $ mysql_type , ?string $ default_value ): bool {
418
+ private function generate_column_default ( string $ mysql_type , ?string $ default_value ): ? string {
402
419
if ( null === $ default_value || '' === $ default_value ) {
403
- return false ;
420
+ return null ;
404
421
}
405
422
$ mysql_type = strtolower ( $ mysql_type );
406
423
@@ -411,7 +428,7 @@ private function column_has_default( string $mysql_type, ?string $default_value
411
428
* of the SQLite driver, TEXT columns were assigned a default value of ''.
412
429
*/
413
430
if ( 'geomcollection ' === $ mysql_type || 'geometrycollection ' === $ mysql_type ) {
414
- return false ;
431
+ return null ;
415
432
}
416
433
417
434
/*
@@ -424,9 +441,69 @@ private function column_has_default( string $mysql_type, ?string $default_value
424
441
"'' " === $ default_value
425
442
&& in_array ( $ mysql_type , array ( 'datetime ' , 'date ' , 'time ' , 'timestamp ' , 'year ' ), true )
426
443
) {
427
- return false ;
444
+ return null ;
445
+ }
446
+
447
+ /**
448
+ * Convert SQLite default values to MySQL default values.
449
+ *
450
+ * See:
451
+ * - https://www.sqlite.org/syntax/column-constraint.html
452
+ * - https://www.sqlite.org/syntax/literal-value.html
453
+ * - https://www.sqlite.org/lang_expr.html#literal_values_constants_
454
+ */
455
+
456
+ // Quoted string literal. E.g.: 'abc', "abc", `abc`
457
+ $ first_byte = $ default_value [0 ] ?? null ;
458
+ if ( '" ' === $ first_byte || "' " === $ first_byte || '` ' === $ first_byte ) {
459
+ return $ this ->escape_mysql_string_literal ( substr ( $ default_value , 1 , -1 ) );
460
+ }
461
+
462
+ // Normalize the default value to for easier comparison.
463
+ $ uppercase_default_value = strtoupper ( $ default_value );
464
+
465
+ // NULL, TRUE, FALSE.
466
+ if ( 'NULL ' === $ uppercase_default_value ) {
467
+ // DEFAULT NULL is the same as no default value.
468
+ return null ;
469
+ } elseif ( 'TRUE ' === $ uppercase_default_value ) {
470
+ return '1 ' ;
471
+ } elseif ( 'FALSE ' === $ uppercase_default_value ) {
472
+ return '0 ' ;
473
+ }
474
+
475
+ // Date/time values.
476
+ if ( 'CURRENT_TIMESTAMP ' === $ uppercase_default_value ) {
477
+ return 'CURRENT_TIMESTAMP ' ;
478
+ } elseif ( 'CURRENT_DATE ' === $ uppercase_default_value ) {
479
+ return null ; // Not supported in MySQL.
480
+ } elseif ( 'CURRENT_TIME ' === $ uppercase_default_value ) {
481
+ return null ; // Not supported in MySQL.
482
+ }
483
+
484
+ // SQLite supports underscores in all numeric literals.
485
+ $ no_underscore_default_value = str_replace ( '_ ' , '' , $ default_value );
486
+
487
+ // Numeric literals. E.g.: 123, 1.23, -1.23, 1e3, 1.2e-3
488
+ if ( is_numeric ( $ no_underscore_default_value ) ) {
489
+ return $ no_underscore_default_value ;
490
+ }
491
+
492
+ // HEX literals (numeric). E.g.: 0x1a2f, 0X1A2F
493
+ $ value = filter_var ( $ no_underscore_default_value , FILTER_VALIDATE_INT , FILTER_FLAG_ALLOW_HEX );
494
+ if ( false !== $ value ) {
495
+ return $ value ;
496
+ }
497
+
498
+ // BLOB literals (string). E.g.: x'1a2f', X'1A2F'
499
+ // Checking the prefix is enough as SQLite doesn't allow malformed values.
500
+ if ( str_starts_with ( $ uppercase_default_value , "X' " ) ) {
501
+ // Convert the hex string to ASCII bytes.
502
+ return "' " . pack ( 'H* ' , substr ( $ default_value , 2 , -1 ) ) . "' " ;
428
503
}
429
- return true ;
504
+
505
+ // Unquoted string literal. E.g.: abc
506
+ return $ this ->escape_mysql_string_literal ( $ default_value );
430
507
}
431
508
432
509
/**
@@ -525,6 +602,17 @@ private function get_mysql_column_type( string $column_type ): string {
525
602
return 'text ' ;
526
603
}
527
604
605
+ /**
606
+ * Escape a string literal for MySQL DEFAULT values.
607
+ *
608
+ * @param string $literal The string literal to escape.
609
+ * @return string The escaped string literal.
610
+ */
611
+ private function escape_mysql_string_literal ( string $ literal ): string {
612
+ // See: https://www.php.net/manual/en/mysqli.real-escape-string.php
613
+ return "' " . addcslashes ( $ literal , "\0\n\r' \"\Z " ) . "' " ;
614
+ }
615
+
528
616
/**
529
617
* Quote an SQLite identifier.
530
618
*
0 commit comments