@@ -267,6 +267,23 @@ private function generate_column_definition( string $table_name, array $column_i
267267 if ( null === $ mysql_type ) {
268268 $ mysql_type = $ this ->get_mysql_column_type ( $ column_info ['type ' ] );
269269 }
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+
270287 $ definition [] = $ mysql_type ;
271288
272289 // NULL/NOT NULL.
@@ -288,8 +305,8 @@ private function generate_column_definition( string $table_name, array $column_i
288305 }
289306
290307 // 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 ;
293310 }
294311
295312 return implode ( ' ' , $ definition );
@@ -392,15 +409,15 @@ private function generate_key_definition( string $table_name, array $key_info, a
392409 }
393410
394411 /**
395- * Determine if a column has a default value.
412+ * Generate a MySQL default value from an SQLite default value.
396413 *
397414 * @param string $mysql_type The MySQL data type of the column.
398415 * @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.
400417 */
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 {
402419 if ( null === $ default_value || '' === $ default_value ) {
403- return false ;
420+ return null ;
404421 }
405422 $ mysql_type = strtolower ( $ mysql_type );
406423
@@ -411,7 +428,7 @@ private function column_has_default( string $mysql_type, ?string $default_value
411428 * of the SQLite driver, TEXT columns were assigned a default value of ''.
412429 */
413430 if ( 'geomcollection ' === $ mysql_type || 'geometrycollection ' === $ mysql_type ) {
414- return false ;
431+ return null ;
415432 }
416433
417434 /*
@@ -424,9 +441,69 @@ private function column_has_default( string $mysql_type, ?string $default_value
424441 "'' " === $ default_value
425442 && in_array ( $ mysql_type , array ( 'datetime ' , 'date ' , 'time ' , 'timestamp ' , 'year ' ), true )
426443 ) {
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 ) ) . "' " ;
428503 }
429- return true ;
504+
505+ // Unquoted string literal. E.g.: abc
506+ return $ this ->escape_mysql_string_literal ( $ default_value );
430507 }
431508
432509 /**
@@ -525,6 +602,17 @@ private function get_mysql_column_type( string $column_type ): string {
525602 return 'text ' ;
526603 }
527604
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+
528616 /**
529617 * Quote an SQLite identifier.
530618 *
0 commit comments