From 81dc9fe9dbaa3accbfc366d62403566042fcc0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Smutn=C3=BD?= Date: Tue, 13 Jan 2026 10:19:38 +0100 Subject: [PATCH 1/2] MDEV-38735 mysqldump: Support --hex-blob with --tab/--dir for binary data Problem: When using mysqldump with --tab or --dir to export table data to separate .txt files, BLOB and BINARY columns containing null bytes (0x00) or other binary data get corrupted or truncated because the tab-separated text format cannot properly represent binary data. Solution: When both --hex-blob and --tab/--dir are specified, automatically wrap BLOB and BINARY columns with HEX() in the SELECT statement. This ensures binary data is exported as hexadecimal strings that can be safely stored in text files and later imported without data loss. Implementation: - Query INFORMATION_SCHEMA.COLUMNS to get data_type and character_set_name - Detect BLOB/BINARY columns (binary charset + blob/binary data types) - Wrap detected columns with HEX(column_name) AS column_name in SELECT - Works for: BINARY, VARBINARY, TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB This enables proper round-trip export/import of binary data when used with mariadb-import --hex-blob. Example usage: mysqldump --hex-blob --tab=/tmp/backup test mytable # Creates mytable.txt with hex-encoded BLOB data --- client/mysqldump.cc | 29 +- mysql-test/main/mysqldump-hexblob.result | 411 +++++++++++++++++++++++ mysql-test/main/mysqldump-hexblob.test | 213 ++++++++++++ 3 files changed, 652 insertions(+), 1 deletion(-) create mode 100644 mysql-test/main/mysqldump-hexblob.result create mode 100644 mysql-test/main/mysqldump-hexblob.test diff --git a/client/mysqldump.cc b/client/mysqldump.cc index fc5d990478e34..83f5f1360be57 100644 --- a/client/mysqldump.cc +++ b/client/mysqldump.cc @@ -3473,7 +3473,7 @@ static uint get_table_structure(const char *table, const char *db, char *table_t mysql_free_result(result); } my_snprintf(query_buff, sizeof(query_buff), - "select column_name, extra, generation_expression, data_type " + "select column_name, extra, generation_expression, data_type, character_set_name " "from information_schema.columns where table_schema=database() " "and table_name=%s order by ordinal_position", quote_for_equal(table, temp_buff)); @@ -3506,6 +3506,26 @@ static uint get_table_structure(const char *table, const char *db, char *table_t dynstr_append_checked(&select_field_names_for_header, ", "); } init=1; + my_bool is_blob_field= 0; + /* + Check if this is a binary/blob field that should be hex-encoded. + For multi_file_output (--tab/--dir), we need to wrap with HEX() in SELECT. + Binary fields have character_set_name = NULL or 'binary'. + */ + if (opt_hex_blob && multi_file_output && row[3]) + { + /* Check for blob/binary types with binary charset */ + if ((row[4] == NULL || strcmp(row[4], "binary") == 0) && + (strcmp(row[3], "binary") == 0 || + strcmp(row[3], "varbinary") == 0 || + strcmp(row[3], "tinyblob") == 0 || + strcmp(row[3], "blob") == 0 || + strcmp(row[3], "mediumblob") == 0 || + strcmp(row[3], "longblob") == 0)) + { + is_blob_field= 1; + } + } last_name= quote_name(row[0], name_buff, 0); if (opt_dump_history && *versioned && opt_update_history && @@ -3520,6 +3540,13 @@ static uint get_table_structure(const char *table, const char *db, char *table_t dynstr_append_checked(&select_field_names, ") as "); dynstr_append_checked(&select_field_names, last_name); } + else if (is_blob_field) + { + dynstr_append_checked(&select_field_names, "HEX("); + dynstr_append_checked(&select_field_names, last_name); + dynstr_append_checked(&select_field_names, ") AS "); + dynstr_append_checked(&select_field_names, last_name); + } else dynstr_append_checked(&select_field_names, last_name); dynstr_append_checked(&insert_field_names, last_name); diff --git a/mysql-test/main/mysqldump-hexblob.result b/mysql-test/main/mysqldump-hexblob.result new file mode 100644 index 0000000000000..12b2fee5e9499 --- /dev/null +++ b/mysql-test/main/mysqldump-hexblob.result @@ -0,0 +1,411 @@ +# +# Test --hex-blob with --tab (--dir) +# Verify that BLOB and BINARY columns are hex-encoded in tab-separated output +# +USE test; +# Test 1: Basic BLOB types with hex-blob +CREATE TABLE t_hex_blob ( +id INT PRIMARY KEY, +tiny_blob TINYBLOB, +normal_blob BLOB, +medium_blob MEDIUMBLOB, +long_blob LONGBLOB +); +INSERT INTO t_hex_blob VALUES +(1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), +(2, NULL, NULL, NULL, NULL), +(3, '', '', '', ''), +(4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'); +# Check SQL file +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_hex_blob`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_hex_blob` ( + `id` int(11) NOT NULL, + `tiny_blob` tinyblob DEFAULT NULL, + `normal_blob` blob DEFAULT NULL, + `medium_blob` mediumblob DEFAULT NULL, + `long_blob` longblob DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - blob data should be hex-encoded +1 74696E790064617461 6E6F726D616C00626C6F62 6D656469756D00626C6F62 6C6F6E6700626C6F62 +2 \N \N \N \N +3 +4 DEADBEEF CAFEBABE FEEDFACE BAADF00D +DROP TABLE t_hex_blob; +# Test 2: BINARY and VARBINARY types with hex-blob +CREATE TABLE t_hex_binary ( +id INT PRIMARY KEY, +bin_col BINARY(10), +varbin_col VARBINARY(100) +); +INSERT INTO t_hex_binary VALUES +(1, X'0102030405', 'varbinary\0test'), +(2, NULL, NULL), +(3, X'00', ''), +(4, X'FFFFFFFFFFFFFF', X'DEADBEEFCAFEBABE'); +# Check SQL file +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_hex_binary`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_hex_binary` ( + `id` int(11) NOT NULL, + `bin_col` binary(10) DEFAULT NULL, + `varbin_col` varbinary(100) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - binary data should be hex-encoded +1 01020304050000000000 76617262696E6172790074657374 +2 \N \N +3 00000000000000000000 +4 FFFFFFFFFFFFFF000000 DEADBEEFCAFEBABE +DROP TABLE t_hex_binary; +# Test 3: Mixed table with BLOB and non-BLOB columns +CREATE TABLE t_hex_mixed ( +id INT PRIMARY KEY, +name VARCHAR(50), +data BLOB, +description TEXT, +binary_data VARBINARY(100) +); +INSERT INTO t_hex_mixed VALUES +(1, 'First', X'ABCD', 'Description 1', X'1234'), +(2, 'Second', NULL, 'Description 2', NULL), +(3, 'Third', '', 'Description 3', ''), +(4, 'Special\tChars', X'00FF00FF', 'Desc\nWith\nNewlines', X'CAFEBABE'); +# Check SQL file - should have HEX() wrapper for BLOB/BINARY columns +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_hex_mixed`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_hex_mixed` ( + `id` int(11) NOT NULL, + `name` varchar(50) DEFAULT NULL, + `data` blob DEFAULT NULL, + `description` text DEFAULT NULL, + `binary_data` varbinary(100) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - only BLOB/BINARY columns should be hex-encoded +1 First ABCD Description 1 1234 +2 Second \N Description 2 \N +3 Third Description 3 +4 Special\ Chars 00FF00FF Desc\ +With\ +Newlines CAFEBABE +DROP TABLE t_hex_mixed; +# Test 4: Verify --tab without --hex-blob (default behavior) +CREATE TABLE t_no_hex ( +id INT, +blob_col BLOB +); +INSERT INTO t_no_hex VALUES (1, 'test\0data'), (2, NULL); +# Check SQL file - should NOT have HEX() wrapper +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_no_hex`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_no_hex` ( + `id` int(11) DEFAULT NULL, + `blob_col` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - blob data NOT hex-encoded (binary content) +1 test\0data +2 \N +DROP TABLE t_no_hex; +# Test 5: Table with only BLOB columns +CREATE TABLE t_all_blobs ( +blob1 BLOB, +blob2 TINYBLOB, +blob3 MEDIUMBLOB +); +INSERT INTO t_all_blobs VALUES +(X'ABCDEF', X'123456', X'FEDCBA'), +(NULL, NULL, NULL); +# Check SQL file +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_all_blobs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_all_blobs` ( + `blob1` blob DEFAULT NULL, + `blob2` tinyblob DEFAULT NULL, + `blob3` mediumblob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file +ABCDEF 123456 FEDCBA +\N \N \N +DROP TABLE t_all_blobs; +# Test 6: Empty table with BLOB columns +CREATE TABLE t_empty_blobs ( +id INT, +data BLOB +); +# Check SQL file +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_empty_blobs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_empty_blobs` ( + `id` int(11) DEFAULT NULL, + `data` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - should be empty +DROP TABLE t_empty_blobs; +# +# Test --hex-blob with --dir option +# --dir creates subdirectories for each database +# +# Test 7: Basic BLOB types with --hex-blob and --dir +CREATE TABLE t_dir_hex_blob ( +id INT PRIMARY KEY, +tiny_blob TINYBLOB, +normal_blob BLOB, +medium_blob MEDIUMBLOB, +long_blob LONGBLOB +); +INSERT INTO t_dir_hex_blob VALUES +(1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), +(2, NULL, NULL, NULL, NULL), +(3, '', '', '', ''), +(4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'); +# Check SQL file in test subdirectory +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_dir_hex_blob`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_dir_hex_blob` ( + `id` int(11) NOT NULL, + `tiny_blob` tinyblob DEFAULT NULL, + `normal_blob` blob DEFAULT NULL, + `medium_blob` mediumblob DEFAULT NULL, + `long_blob` longblob DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - blob data should be hex-encoded +1 74696E790064617461 6E6F726D616C00626C6F62 6D656469756D00626C6F62 6C6F6E6700626C6F62 +2 \N \N \N \N +3 +4 DEADBEEF CAFEBABE FEEDFACE BAADF00D +DROP TABLE t_dir_hex_blob; +# Test 8: Mixed table with --dir and --hex-blob +CREATE TABLE t_dir_mixed ( +id INT PRIMARY KEY, +name VARCHAR(50), +data BLOB, +binary_data VARBINARY(50) +); +INSERT INTO t_dir_mixed VALUES +(1, 'First', X'ABCD', X'1234'), +(2, 'Second', NULL, NULL), +(3, 'Third', '', ''); +# Check SQL file +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_dir_mixed`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_dir_mixed` ( + `id` int(11) NOT NULL, + `name` varchar(50) DEFAULT NULL, + `data` blob DEFAULT NULL, + `binary_data` varbinary(50) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - only BLOB/BINARY columns should be hex-encoded +1 First ABCD 1234 +2 Second \N \N +3 Third +DROP TABLE t_dir_mixed; +# Test 9: Verify --dir without --hex-blob (default behavior) +CREATE TABLE t_dir_no_hex ( +id INT, +blob_col BLOB +); +INSERT INTO t_dir_no_hex VALUES (1, 'test\0data'), (2, NULL); +# Check SQL file - should NOT have HEX() wrapper +/*M!999999\- enable the sandbox mode */ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='' */; +/*M!100616 SET @OLD_NOTE_VERBOSITY=@@NOTE_VERBOSITY, NOTE_VERBOSITY=0 */; +DROP TABLE IF EXISTS `t_dir_no_hex`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8mb4 */; +CREATE TABLE `t_dir_no_hex` ( + `id` int(11) DEFAULT NULL, + `blob_col` blob DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; + +# Check data file - blob data NOT hex-encoded +1 test\0data +2 \N +DROP TABLE t_dir_no_hex; diff --git a/mysql-test/main/mysqldump-hexblob.test b/mysql-test/main/mysqldump-hexblob.test new file mode 100644 index 0000000000000..3c370904f2e2e --- /dev/null +++ b/mysql-test/main/mysqldump-hexblob.test @@ -0,0 +1,213 @@ +# Test --hex-blob with --tab (--dir) +# Verify that BLOB and BINARY columns are hex-encoded in tab-separated output + +--source include/not_embedded.inc + +--echo # +--echo # Test --hex-blob with --tab (--dir) +--echo # Verify that BLOB and BINARY columns are hex-encoded in tab-separated output +--echo # + +USE test; + +--echo # Test 1: Basic BLOB types with hex-blob +CREATE TABLE t_hex_blob ( + id INT PRIMARY KEY, + tiny_blob TINYBLOB, + normal_blob BLOB, + medium_blob MEDIUMBLOB, + long_blob LONGBLOB +); + +INSERT INTO t_hex_blob VALUES + (1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), + (2, NULL, NULL, NULL, NULL), + (3, '', '', '', ''), + (4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_hex_blob +--echo # Check SQL file +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_blob.sql +--echo # Check data file - blob data should be hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_blob.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_blob.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_blob.txt + +DROP TABLE t_hex_blob; + +--echo # Test 2: BINARY and VARBINARY types with hex-blob +CREATE TABLE t_hex_binary ( + id INT PRIMARY KEY, + bin_col BINARY(10), + varbin_col VARBINARY(100) +); + +INSERT INTO t_hex_binary VALUES + (1, X'0102030405', 'varbinary\0test'), + (2, NULL, NULL), + (3, X'00', ''), + (4, X'FFFFFFFFFFFFFF', X'DEADBEEFCAFEBABE'); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_hex_binary +--echo # Check SQL file +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_binary.sql +--echo # Check data file - binary data should be hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_binary.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_binary.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_binary.txt + +DROP TABLE t_hex_binary; + +--echo # Test 3: Mixed table with BLOB and non-BLOB columns +CREATE TABLE t_hex_mixed ( + id INT PRIMARY KEY, + name VARCHAR(50), + data BLOB, + description TEXT, + binary_data VARBINARY(100) +); + +INSERT INTO t_hex_mixed VALUES + (1, 'First', X'ABCD', 'Description 1', X'1234'), + (2, 'Second', NULL, 'Description 2', NULL), + (3, 'Third', '', 'Description 3', ''), + (4, 'Special\tChars', X'00FF00FF', 'Desc\nWith\nNewlines', X'CAFEBABE'); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_hex_mixed +--echo # Check SQL file - should have HEX() wrapper for BLOB/BINARY columns +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_mixed.sql +--echo # Check data file - only BLOB/BINARY columns should be hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/t_hex_mixed.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_mixed.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_hex_mixed.txt + +DROP TABLE t_hex_mixed; + +--echo # Test 4: Verify --tab without --hex-blob (default behavior) +CREATE TABLE t_no_hex ( + id INT, + blob_col BLOB +); + +INSERT INTO t_no_hex VALUES (1, 'test\0data'), (2, NULL); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --tab=$MYSQLTEST_VARDIR/tmp/ test t_no_hex +--echo # Check SQL file - should NOT have HEX() wrapper +--cat_file $MYSQLTEST_VARDIR/tmp/t_no_hex.sql +--echo # Check data file - blob data NOT hex-encoded (binary content) +--cat_file $MYSQLTEST_VARDIR/tmp/t_no_hex.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_no_hex.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_no_hex.txt + +DROP TABLE t_no_hex; + +--echo # Test 5: Table with only BLOB columns +CREATE TABLE t_all_blobs ( + blob1 BLOB, + blob2 TINYBLOB, + blob3 MEDIUMBLOB +); + +INSERT INTO t_all_blobs VALUES + (X'ABCDEF', X'123456', X'FEDCBA'), + (NULL, NULL, NULL); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_all_blobs +--echo # Check SQL file +--cat_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.sql +--echo # Check data file +--cat_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.txt + +DROP TABLE t_all_blobs; + +--echo # Test 6: Empty table with BLOB columns +CREATE TABLE t_empty_blobs ( + id INT, + data BLOB +); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_empty_blobs +--echo # Check SQL file +--cat_file $MYSQLTEST_VARDIR/tmp/t_empty_blobs.sql +--echo # Check data file - should be empty +--cat_file $MYSQLTEST_VARDIR/tmp/t_empty_blobs.txt +--remove_file $MYSQLTEST_VARDIR/tmp/t_empty_blobs.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_empty_blobs.txt + +DROP TABLE t_empty_blobs; + +--echo # +--echo # Test --hex-blob with --dir option +--echo # --dir creates subdirectories for each database +--echo # + +--echo # Test 7: Basic BLOB types with --hex-blob and --dir +CREATE TABLE t_dir_hex_blob ( + id INT PRIMARY KEY, + tiny_blob TINYBLOB, + normal_blob BLOB, + medium_blob MEDIUMBLOB, + long_blob LONGBLOB +); + +INSERT INTO t_dir_hex_blob VALUES + (1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), + (2, NULL, NULL, NULL, NULL), + (3, '', '', '', ''), + (4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'); + +--mkdir $MYSQLTEST_VARDIR/tmp/backup_dir +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --dir=$MYSQLTEST_VARDIR/tmp/backup_dir test +--echo # Check SQL file in test subdirectory +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_hex_blob.sql +--echo # Check data file - blob data should be hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_hex_blob.txt +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_hex_blob.sql +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_hex_blob.txt + +DROP TABLE t_dir_hex_blob; + +--echo # Test 8: Mixed table with --dir and --hex-blob +CREATE TABLE t_dir_mixed ( + id INT PRIMARY KEY, + name VARCHAR(50), + data BLOB, + binary_data VARBINARY(50) +); + +INSERT INTO t_dir_mixed VALUES + (1, 'First', X'ABCD', X'1234'), + (2, 'Second', NULL, NULL), + (3, 'Third', '', ''); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --dir=$MYSQLTEST_VARDIR/tmp/backup_dir test +--echo # Check SQL file +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_mixed.sql +--echo # Check data file - only BLOB/BINARY columns should be hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_mixed.txt +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_mixed.sql +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_mixed.txt + +DROP TABLE t_dir_mixed; + +--echo # Test 9: Verify --dir without --hex-blob (default behavior) +CREATE TABLE t_dir_no_hex ( + id INT, + blob_col BLOB +); + +INSERT INTO t_dir_no_hex VALUES (1, 'test\0data'), (2, NULL); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --dir=$MYSQLTEST_VARDIR/tmp/backup_dir test +--echo # Check SQL file - should NOT have HEX() wrapper +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_no_hex.sql +--echo # Check data file - blob data NOT hex-encoded +--cat_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_no_hex.txt +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_no_hex.sql +--remove_file $MYSQLTEST_VARDIR/tmp/backup_dir/test/t_dir_no_hex.txt +--rmdir $MYSQLTEST_VARDIR/tmp/backup_dir/test +--rmdir $MYSQLTEST_VARDIR/tmp/backup_dir + +DROP TABLE t_dir_no_hex; From cfa4739280f45412a771e846c5e08df15d019f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Smutn=C3=BD?= Date: Tue, 13 Jan 2026 10:29:22 +0100 Subject: [PATCH 2/2] MDEV-38735 mariadb-import: Implement --hex-blob for importing binary data Problem: When exporting tables with mysqldump --hex-blob --tab/--dir, BLOB and BINARY columns are hex-encoded to preserve binary data (including null bytes) in text files. However, mariadb-import had no corresponding option to decode this hex data back to binary during import, making the round-trip export/import workflow impossible for binary data. Solution: Implement --hex-blob option for mariadb-import that automatically detects BLOB and BINARY columns and applies UNHEX() transformation during import. Implementation: When --hex-blob is specified: 1. Query server's INFORMATION_SCHEMA.COLUMNS to get table structure: - column_name, data_type, character_set_name 2. Identify BLOB/BINARY columns (binary charset + blob/binary types) 3. Modify LOAD DATA statement to use user variables for hex data: - LOAD DATA INFILE ... (id, @blob_col_hex, varchar_col) 4. Add SET clause to convert hex to binary: - SET blob_col=UNHEX(@blob_col_hex) Supports all binary column types: - BLOB family: TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB - BINARY family: BINARY(n), VARBINARY(n) Works with: - Explicit --columns parameter (subset of columns) - All columns (default) - Mixed tables (BLOB and non-BLOB columns) - NULL and empty values - Both standalone .txt files and --dir mode Example workflow: # Export with hex-encoded BLOBs mysqldump --hex-blob --tab=/tmp/backup test mytable # Import with automatic UNHEX() conversion mariadb-import --hex-blob test /tmp/backup/mytable.txt # Binary data including null bytes preserved perfectly Test coverage (mysql-test/main/mariadb-import.test): - Round-trip for all BLOB types with embedded null bytes - BINARY and VARBINARY types - Mixed tables (BLOB + non-BLOB columns) - Import with --columns parameter - NULL and empty blob handling - Comparison with/without --hex-blob (data mismatch detection) - Directory-based import with --dir option - Data integrity verification via direct SQL comparison This completes the --hex-blob round-trip functionality, enabling reliable backup and restore of tables containing binary data. --- client/import_util.cc | 104 ++++++++- client/import_util.h | 7 + client/mysqldump.cc | 2 +- client/mysqlimport.cc | 208 +++++++++++++++-- mysql-test/main/mariadb-import.result | 251 +++++++++++++++++++++ mysql-test/main/mariadb-import.test | 309 ++++++++++++++++++++++++++ 6 files changed, 865 insertions(+), 16 deletions(-) diff --git a/client/import_util.cc b/client/import_util.cc index baade1535c69d..47f39c913d173 100644 --- a/client/import_util.cc +++ b/client/import_util.cc @@ -23,6 +23,7 @@ */ #include +#include #include #include @@ -58,10 +59,18 @@ std::string extract_first_create_table(const std::string &script) TableDDLInfo::TableDDLInfo(const std::string &create_table_stmt) { regex_t primary_key_regex, constraint_regex, index_regex, engine_regex, - table_name_regex; + table_name_regex, column_regex; constexpr size_t MAX_MATCHES= 10; regmatch_t match[10]; + // Extract just the CREATE TABLE statement if the input contains other SQL + std::string actual_create_table = extract_first_create_table(create_table_stmt); + if (actual_create_table.empty()) + { + // Input might already be just the CREATE TABLE statement + actual_create_table = create_table_stmt; + } + regcomp(&primary_key_regex, "\\n\\s*(PRIMARY\\s+KEY\\s+(.*?)),?\\n", REG_EXTENDED); regcomp(&constraint_regex, @@ -73,8 +82,13 @@ TableDDLInfo::TableDDLInfo(const std::string &create_table_stmt) regcomp(&engine_regex, "\\bENGINE\\s*=\\s*(\\w+)", REG_EXTENDED); regcomp(&table_name_regex, "CREATE\\s+TABLE\\s+(`?(?:[^`]|``)+`?)\\s*\\(", REG_EXTENDED); + // Column regex: matches lines starting with column name followed by type + // Must be inside parentheses, not starting with constraint/key keywords + regcomp(&column_regex, + "\\n\\s*(`[^`]+`|[a-zA-Z_][a-zA-Z0-9_]*)\\s+([a-zA-Z]+[a-zA-Z0-9()]*)", + REG_EXTENDED); - const char *stmt= create_table_stmt.c_str(); + const char *stmt= actual_create_table.c_str(); const char *search_start= stmt; // Extract primary key @@ -129,11 +143,97 @@ TableDDLInfo::TableDDLInfo(const std::string &create_table_stmt) } } } + +// Extract column definitions - find the column definition block + const char *col_start = strchr(stmt, '('); + if (col_start) + { + col_start++; // Move past the opening '(' + + // Find the matching closing parenthesis + int depth = 1; + const char *col_end = col_start; + bool in_string = false; + char string_delim = 0; + + while (*col_end && depth > 0) + { + // Handle strings + if (!in_string && (*col_end == '\'' || *col_end == '"' || *col_end == '`')) + { + in_string = true; + string_delim = *col_end; + } + else if (in_string && *col_end == string_delim) + { + // Check for escaped delimiter + if (*(col_end + 1) == string_delim) + col_end++; // Skip escaped + else + in_string = false; + } + else if (!in_string) + { + if (*col_end == '(') + depth++; + else if (*col_end == ')') + { + depth--; + if (depth == 0) + break; // Found the matching closing paren + } + } + col_end++; + } + + if (depth == 0 && col_end > col_start) + { + // col_end now points to the closing ')' + std::string columns_block(col_start, col_end - col_start); + + // Now parse column definitions from this block only + // Pattern matches lines that start with whitespace + identifier + whitespace + type + regcomp(&column_regex, + "\\n[ \\t]*(`[^`]+`|[a-zA-Z_][a-zA-Z0-9_]*)[ \\t]+([a-zA-Z]+)", + REG_EXTENDED); + + const char *block = columns_block.c_str(); + search_start = block; + + while (regexec(&column_regex, search_start, MAX_MATCHES, match, 0) == 0) + { + std::string col_name(search_start + match[1].rm_so, + match[1].rm_eo - match[1].rm_so); + std::string col_type(search_start + match[2].rm_so, + match[2].rm_eo - match[2].rm_so); + + // Remove backticks + if (!col_name.empty() && col_name.front() == '`' && col_name.back() == '`') + col_name = col_name.substr(1, col_name.length() - 2); + + // Convert to uppercase for comparison + std::string col_name_upper = col_name; + for (char &c : col_name_upper) c = toupper(c); + + // Skip constraint/key lines + if (col_name_upper != "PRIMARY" && col_name_upper != "CONSTRAINT" && + col_name_upper != "KEY" && col_name_upper != "INDEX" && + col_name_upper != "UNIQUE" && col_name_upper != "FULLTEXT" && + col_name_upper != "SPATIAL" && col_name_upper != "VECTOR") + { + columns.push_back({col_name, col_type}); + } + + search_start += match[0].rm_eo - 1; + } + } + } regfree(&primary_key_regex); regfree(&constraint_regex); regfree(&index_regex); regfree(&engine_regex); regfree(&table_name_regex); + regfree(&column_regex); } /** diff --git a/client/import_util.h b/client/import_util.h index 27e4decc77fcd..13063dfe93fd0 100644 --- a/client/import_util.h +++ b/client/import_util.h @@ -40,6 +40,12 @@ struct KeyDefinition std::string name; }; +struct ColumnInfo +{ + std::string name; + std::string type; +}; + /** Information about keys and constraints, extracted from CREATE TABLE statement @@ -50,6 +56,7 @@ struct TableDDLInfo KeyDefinition primary_key; std::vector constraints; std::vector secondary_indexes; + std::vector columns; std::string storage_engine; std::string table_name; /* Innodb is using first UNIQUE key for clustering, if no PK is set*/ diff --git a/client/mysqldump.cc b/client/mysqldump.cc index 83f5f1360be57..70e69e9b2bee1 100644 --- a/client/mysqldump.cc +++ b/client/mysqldump.cc @@ -3506,7 +3506,7 @@ static uint get_table_structure(const char *table, const char *db, char *table_t dynstr_append_checked(&select_field_names_for_header, ", "); } init=1; - my_bool is_blob_field= 0; + my_bool is_blob_field= 0; /* Check if this is a binary/blob field that should be hex-encoded. For multi_file_output (--tab/--dir), we need to wrap with HEX() in SELECT. diff --git a/client/mysqlimport.cc b/client/mysqlimport.cc index 8f92b8b86e1d7..88c4dcce7eb35 100644 --- a/client/mysqlimport.cc +++ b/client/mysqlimport.cc @@ -61,7 +61,7 @@ static std::string parse_sql_script(const char *filepath, bool *tz_utc, static my_bool verbose=0,lock_tables=0,ignore_errors=0,opt_delete=0, replace, silent, ignore, ignore_foreign_keys, - opt_compress, opt_low_priority, tty_password; + opt_compress, opt_low_priority, tty_password, opt_hex_blob; static my_bool debug_info_flag= 0, debug_check_flag= 0; static uint opt_use_threads=0, opt_local_file=0, my_end_arg= 0; static char *opt_password=0, *current_user=0, @@ -259,7 +259,12 @@ static struct my_option my_long_options[] = &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, - { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} + {"hex-blob", 0, + "Treat BLOB and BINARY column data as hex-encoded strings. " + "During import, hex strings are converted to binary using UNHEX(). " + "This is useful when importing data exported with HEX() encoding.", + &opt_hex_blob, &opt_hex_blob, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, }; @@ -644,6 +649,57 @@ int table_load_params::create_table_or_view(MYSQL* mysql) return 0; } + +/** + Parse comma-separated column list into vector + + @param columns_str - comma-separated column names (e.g., "col1,col2,col3") + @param result - vector to store parsed column names +*/ +static void parse_column_list(const char* columns_str, + std::vector& result) +{ + if (!columns_str || !*columns_str) + return; + + std::string cols = columns_str; + size_t pos = 0; + + while ((pos = cols.find(',')) != std::string::npos) + { + std::string col = cols.substr(0, pos); + // Trim whitespace + col.erase(0, col.find_first_not_of(" \t\n\r")); + col.erase(col.find_last_not_of(" \t\n\r") + 1); + if (!col.empty()) + result.push_back(col); + cols.erase(0, pos + 1); + } + + // Handle last column + if (!cols.empty()) + { + cols.erase(0, cols.find_first_not_of(" \t\n\r")); + cols.erase(cols.find_last_not_of(" \t\n\r") + 1); + if (!cols.empty()) + result.push_back(cols); + } +} + + +/** + Check if a column type is a BLOB or BINARY type + + @param type - column type string (e.g., "BLOB", "VARBINARY(100)") + @return true if the type is BLOB or BINARY variant +*/ +static bool is_blob_or_binary_type(const std::string& type) +{ + return type.find("blob") != std::string::npos || + type.find("binary") != std::string::npos; +} + + int table_load_params::load_data(MYSQL *mysql) { char tablename[FN_REFLEN], hard_path[FN_REFLEN], @@ -705,7 +761,6 @@ int table_load_params::load_data(MYSQL *mysql) DBUG_RETURN(1); } - bool recreate_secondary_keys= false; if (opt_innodb_optimize_keys && ddl_info.storage_engine == "InnoDB") { @@ -745,15 +800,144 @@ int table_load_params::load_data(MYSQL *mysql) end= strmov(end, " FIELDS"); end= add_load_option(end, fields_terminated, " TERMINATED BY"); end= add_load_option(end, enclosed, " ENCLOSED BY"); - end= add_load_option(end, opt_enclosed, - " OPTIONALLY ENCLOSED BY"); + end= add_load_option(end, opt_enclosed, " OPTIONALLY ENCLOSED BY"); end= add_load_option(end, escaped, " ESCAPED BY"); end= add_load_option(end, lines_terminated, " LINES TERMINATED BY"); + if (opt_ignore_lines >= 0) - end= strmov(longlong10_to_str(opt_ignore_lines, + end= strmov(longlong10_to_str(opt_ignore_lines, strmov(end, " IGNORE "),10), " LINES"); - if (opt_columns) + + // If --hex-blob is enabled but ddl_info.columns is empty (table already exists), + // query the server for table structure + if (opt_hex_blob && ddl_info.columns.empty()) + { + char query_buff[512]; + MYSQL_RES *result; + MYSQL_ROW row; + + // Query INFORMATION_SCHEMA.COLUMNS for table structure + // This is similar to what mysqldump does in get_table_structure() + my_snprintf(query_buff, sizeof(query_buff), + "SELECT column_name, data_type, character_set_name " + "FROM information_schema.columns " + "WHERE table_schema=DATABASE() AND table_name='%s' " + "ORDER BY ordinal_position", tablename); + + if (mysql_query(mysql, query_buff)) + { + db_error_with_table(mysql, tablename); + DBUG_RETURN(1); + } + + result = mysql_store_result(mysql); + if (result) + { + while ((row= mysql_fetch_row(result))) + { + if (row[0] && row[1]) // column_name and data_type + { + std::string col_name = row[0]; + std::string col_type = row[1]; + std::string charset = row[2] ? row[2] : ""; + + // Convert data_type to lowercase for comparison + for (char &c : col_type) c = tolower(c); + + // Store column info + ddl_info.columns.push_back({col_name, col_type}); + } + } + mysql_free_result(result); + + if (verbose && !ddl_info.columns.empty()) + { + fprintf(stdout, "Queried table structure from server: %zu columns found\n", + ddl_info.columns.size()); + } + } + } + + if (opt_hex_blob && !ddl_info.columns.empty()) + { + // Build list of columns to load + std::vector load_columns; + std::vector blob_column_names; // Original column names + + if (opt_columns) + { + // User specified columns - parse them + parse_column_list(opt_columns, load_columns); + } + else + { + // No columns specified - use all columns from table definition + for (const auto& col : ddl_info.columns) { + load_columns.push_back(col.name); + } + } + + // Identify BLOB/BINARY columns and modify their names in load list + for (size_t i = 0; i < load_columns.size(); i++) + { + bool modified = false; + // Check if this column is a BLOB/BINARY type + for (const auto& col : ddl_info.columns) + { + // fprintf(stdout, "col.name %s\n", col.name.c_str()); + if (col.name == load_columns[i] && is_blob_or_binary_type(col.type)) + { + // Replace column name with @var_hex variable + blob_column_names.push_back(col.name); + load_columns[i] = "@" + col.name + "_hex"; + modified = true; + break; + } + } + if (!modified) + load_columns[i] = "`" + load_columns[i] + "`"; + } + + // Build the column specification: (col1, col2, @blob_col_hex, ...) + end= strmov(end, " ("); + for (size_t i = 0; i < load_columns.size(); i++) + { + if (i > 0) + end= strmov(end, ","); + end= strmov(end, load_columns[i].c_str()); + } + end= strmov(end, ")"); + + // Add SET clause to convert hex to binary: SET blob_col=UNHEX(@blob_col_hex) + if (!blob_column_names.empty()) + { + if (verbose) + { + fprintf(stdout, "Converting %zu hex-encoded BLOB/BINARY column(s) to binary\n", + blob_column_names.size()); + } + + end= strmov(end, " SET "); + for (size_t i = 0; i < blob_column_names.size(); i++) + { + if (i > 0) + end= strmov(end, ","); + + // Quote the column name + std::string quoted_col = quote_identifier(blob_column_names[i].c_str()); + end= strmov(end, quoted_col.c_str()); + end= strmov(end, "=UNHEX(@"); + end= strmov(end, blob_column_names[i].c_str()); + end= strmov(end, "_hex)"); + } + } + } + else if (opt_columns) + { + // Original behavior: just use the columns as-is end= strmov(strmov(strmov(end, " ("), opt_columns), ")"); + } + *end= '\0'; if (mysql_query(mysql, sql_statement)) @@ -768,7 +952,6 @@ int table_load_params::load_data(MYSQL *mysql) fprintf(stdout, "%s.%s: %s\n", db, tablename, info); } - if (exec_sql(mysql, std::string("ALTER TABLE ") + full_tablename + " ENABLE KEYS;")) DBUG_RETURN(1); @@ -816,7 +999,6 @@ int table_load_params::load_data(MYSQL *mysql) } - static void lock_table(MYSQL *mysql, int tablecount, char **raw_tablename) { DYNAMIC_STRING query; @@ -1032,19 +1214,19 @@ static char *add_load_option(char *ptr, const char *object, ** "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline) ** This is done by doubling ' and add a end -\ if needed to avoid ** syntax errors from the SQL parser. -*/ +*/ static char *field_escape(char *to,const char *from,uint length) { const char *end; - uint end_backslashes=0; + uint end_backslashes=0; for (end= from+length; from != end; from++) { *to++= *from; if (*from == '\\') end_backslashes^=1; /* find odd number of backslashes */ - else + else { if (*from == '\'' && !end_backslashes) *to++= *from; /* We want a duplicate of "'" for MySQL */ @@ -1053,7 +1235,7 @@ static char *field_escape(char *to,const char *from,uint length) } /* Add missing backslashes if user has specified odd number of backs.*/ if (end_backslashes) - *to++= '\\'; + *to++= '\\'; return to; } diff --git a/mysql-test/main/mariadb-import.result b/mysql-test/main/mariadb-import.result index fb869105bb1be..da265a41354de 100644 --- a/mysql-test/main/mariadb-import.result +++ b/mysql-test/main/mariadb-import.result @@ -387,3 +387,254 @@ use test; mariadb-import: Path 'MYSQLTEST_VARDIR/tmp/non_existing' specified by option '--dir' does not exist # Test too many threads, builtin limit 256 Too many connections, max value for --parallel is 256 +# +# Test --hex-blob option for mariadb-import +# Round-trip tests: export with mysqldump --hex-blob, import with mariadb-import --hex-blob +# +USE test; +# Test 1: Round-trip basic BLOB types with --hex-blob +CREATE TABLE t_blob_roundtrip ( +id INT PRIMARY KEY, +tiny_blob TINYBLOB, +normal_blob BLOB, +medium_blob MEDIUMBLOB, +long_blob LONGBLOB +); +INSERT INTO t_blob_roundtrip VALUES +(1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), +(2, NULL, NULL, NULL, NULL), +(3, '', '', '', ''), +(4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'), +(5, X'00FF00FF', X'A5A5A5A5', X'12345678', X'FFFFFFFF'); +# Export with mysqldump --hex-blob --tab +# Save original data for comparison +CREATE TABLE t_blob_original LIKE t_blob_roundtrip; +INSERT INTO t_blob_original SELECT * FROM t_blob_roundtrip; +# Drop table and recreate from .sql file +DROP TABLE t_blob_roundtrip; +# Import with mariadb-import --hex-blob +test.t_blob_roundtrip: Records: 5 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify data integrity - should return 0 rows (all data matches) +SELECT * FROM t_blob_roundtrip WHERE id NOT IN (SELECT id FROM t_blob_original); +id tiny_blob normal_blob medium_blob long_blob +SELECT * FROM t_blob_original WHERE id NOT IN (SELECT id FROM t_blob_roundtrip); +id tiny_blob normal_blob medium_blob long_blob +SELECT o.id FROM t_blob_original o, t_blob_roundtrip r +WHERE o.id = r.id +AND (o.tiny_blob IS NOT NULL AND r.tiny_blob IS NOT NULL AND o.tiny_blob != r.tiny_blob); +id +# Show successful import +SELECT id, HEX(tiny_blob), HEX(normal_blob) FROM t_blob_roundtrip ORDER BY id; +id HEX(tiny_blob) HEX(normal_blob) +1 74696E790064617461 6E6F726D616C00626C6F62 +2 NULL NULL +3 +4 DEADBEEF CAFEBABE +5 00FF00FF A5A5A5A5 +DROP TABLE t_blob_roundtrip; +DROP TABLE t_blob_original; +# Test 2: Round-trip BINARY and VARBINARY types +CREATE TABLE t_binary_roundtrip ( +id INT PRIMARY KEY, +bin_col BINARY(10), +varbin_col VARBINARY(100) +); +INSERT INTO t_binary_roundtrip VALUES +(1, X'0102030405', 'varbinary\0test'), +(2, NULL, NULL), +(3, X'00', ''), +(4, X'FFFFFFFFFFFFFF', X'DEADBEEFCAFEBABE'); +# Export with mysqldump --hex-blob --tab +CREATE TABLE t_binary_original LIKE t_binary_roundtrip; +INSERT INTO t_binary_original SELECT * FROM t_binary_roundtrip; +DROP TABLE t_binary_roundtrip; +# Import with mariadb-import --hex-blob +test.t_binary_roundtrip: Records: 4 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify data integrity +SELECT o.id FROM t_binary_original o, t_binary_roundtrip r +WHERE o.id = r.id +AND ((o.bin_col IS NOT NULL AND r.bin_col IS NOT NULL AND o.bin_col != r.bin_col) +OR (o.varbin_col IS NOT NULL AND r.varbin_col IS NOT NULL AND o.varbin_col != r.varbin_col)); +id +# Show successful import +SELECT id, HEX(bin_col), HEX(varbin_col) FROM t_binary_roundtrip ORDER BY id; +id HEX(bin_col) HEX(varbin_col) +1 01020304050000000000 76617262696E6172790074657374 +2 NULL NULL +3 00000000000000000000 +4 FFFFFFFFFFFFFF000000 DEADBEEFCAFEBABE +DROP TABLE t_binary_roundtrip; +DROP TABLE t_binary_original; +# Test 3: Round-trip mixed table (BLOB and non-BLOB columns) +CREATE TABLE t_mixed_roundtrip ( +id INT PRIMARY KEY, +name VARCHAR(50), +data BLOB, +description TEXT, +binary_data VARBINARY(100) +); +INSERT INTO t_mixed_roundtrip VALUES +(1, 'First', X'ABCD', 'Description 1', X'1234'), +(2, 'Second', NULL, 'Description 2', NULL), +(3, 'Third', '', 'Description 3', ''), +(4, 'Special\tChars', X'00FF00FF', 'Desc\nWith\nNewlines', X'CAFEBABE'); +# Export with mysqldump --hex-blob --tab +CREATE TABLE t_mixed_original LIKE t_mixed_roundtrip; +INSERT INTO t_mixed_original SELECT * FROM t_mixed_roundtrip; +DROP TABLE t_mixed_roundtrip; +# Import with mariadb-import --hex-blob +test.t_mixed_roundtrip: Records: 4 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify all columns match +SELECT o.id FROM t_mixed_original o, t_mixed_roundtrip r +WHERE o.id = r.id +AND (o.name != r.name OR o.description != r.description +OR (o.data IS NOT NULL AND r.data IS NOT NULL AND o.data != r.data) +OR (o.binary_data IS NOT NULL AND r.binary_data IS NOT NULL AND o.binary_data != r.binary_data)); +id +# Show successful import +SELECT id, name, HEX(data), description, HEX(binary_data) FROM t_mixed_roundtrip ORDER BY id; +id name HEX(data) description HEX(binary_data) +1 First ABCD Description 1 1234 +2 Second NULL Description 2 NULL +3 Third Description 3 +4 Special Chars 00FF00FF Desc +With +Newlines CAFEBABE +DROP TABLE t_mixed_roundtrip; +DROP TABLE t_mixed_original; +# Test 4: Round-trip with --columns parameter (subset of columns) +CREATE TABLE t_columns_test ( +id INT PRIMARY KEY, +name VARCHAR(50), +blob1 BLOB, +blob2 BLOB, +text_col TEXT +); +INSERT INTO t_columns_test VALUES +(1, 'Row1', X'ABCD', X'1234', 'Text1'), +(2, 'Row2', NULL, NULL, 'Text2'), +(3, 'Row3', X'BEEF', X'CAFE', 'Text3'); +# Export with mysqldump --hex-blob --tab +CREATE TABLE t_columns_original LIKE t_columns_test; +INSERT INTO t_columns_original SELECT * FROM t_columns_test; +DROP TABLE t_columns_test; +# Import with --hex-blob and --columns (id, blob1, blob2) +test.t_columns_test: Records: 3 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify blob columns match +SELECT o.id FROM t_columns_original o, t_columns_test r +WHERE o.id = r.id +AND ((o.blob1 IS NOT NULL AND r.blob1 IS NOT NULL AND o.blob1 != r.blob1) +OR (o.blob2 IS NOT NULL AND r.blob2 IS NOT NULL AND o.blob2 != r.blob2)); +id +# Show successful import +SELECT id, name, HEX(blob1), HEX(blob2), text_col FROM t_columns_test ORDER BY id; +id name HEX(blob1) HEX(blob2) text_col +1 Row1 ABCD 1234 Text1 +2 Row2 NULL NULL Text2 +3 Row3 BEEF CAFE Text3 +DROP TABLE t_columns_test; +DROP TABLE t_columns_original; +# Test 5: Import WITHOUT --hex-blob (default behavior - should fail or produce wrong data) +CREATE TABLE t_no_hex_import ( +id INT, +data BLOB +); +INSERT INTO t_no_hex_import VALUES (1, X'DEADBEEF'), (2, NULL); +CREATE TABLE t_no_hex_original LIKE t_no_hex_import; +INSERT INTO t_no_hex_original SELECT * FROM t_no_hex_import; +DROP TABLE t_no_hex_import; +# Import WITHOUT --hex-blob - data will be interpreted as ASCII string, not binary +test.t_no_hex_import: Records: 2 Deleted: 0 Skipped: 0 Warnings: 0 +# Show data mismatch - imported data is ASCII "DEADBEEF" not binary 0xDEADBEEF +SELECT id, HEX(data) as hex_data, LENGTH(data) as data_length FROM t_no_hex_import ORDER BY id; +id hex_data data_length +1 4445414442454546 8 +2 NULL NULL +# Original was binary (4 bytes), imported is ASCII string (8 bytes) +SELECT id, HEX(data) as hex_data, LENGTH(data) as data_length FROM t_no_hex_original ORDER BY id; +id hex_data data_length +1 DEADBEEF 4 +2 NULL NULL +DROP TABLE t_no_hex_import; +DROP TABLE t_no_hex_original; +# Test 6: NULL and empty blob handling +CREATE TABLE t_null_empty ( +id INT PRIMARY KEY, +blob_null BLOB, +blob_empty BLOB, +blob_data BLOB +); +INSERT INTO t_null_empty VALUES +(1, NULL, '', X'ABCD'), +(2, NULL, '', X'1234'); +CREATE TABLE t_null_empty_original LIKE t_null_empty; +INSERT INTO t_null_empty_original SELECT * FROM t_null_empty; +DROP TABLE t_null_empty; +test.t_null_empty: Records: 2 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify NULL remains NULL and empty remains empty +SELECT id, blob_null IS NULL as is_null, LENGTH(blob_empty) as empty_length, HEX(blob_data) FROM t_null_empty ORDER BY id; +id is_null empty_length HEX(blob_data) +1 1 0 ABCD +2 1 0 1234 +SELECT o.id FROM t_null_empty_original o, t_null_empty r +WHERE o.id = r.id +AND ((o.blob_null IS NULL) != (r.blob_null IS NULL) +OR o.blob_empty != r.blob_empty +OR o.blob_data != r.blob_data); +id +DROP TABLE t_null_empty; +DROP TABLE t_null_empty_original; +# Test 7: Table with only BLOB columns +CREATE TABLE t_all_blobs ( +blob1 BLOB, +blob2 TINYBLOB, +blob3 MEDIUMBLOB +); +INSERT INTO t_all_blobs VALUES +(X'ABCDEF', X'123456', X'FEDCBA'), +(NULL, NULL, NULL), +(X'FF', X'00', X'AA'); +CREATE TABLE t_all_blobs_original LIKE t_all_blobs; +INSERT INTO t_all_blobs_original SELECT * FROM t_all_blobs; +DROP TABLE t_all_blobs; +test.t_all_blobs: Records: 3 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify all blob columns match +SELECT HEX(blob1), HEX(blob2), HEX(blob3) FROM t_all_blobs ORDER BY blob1; +HEX(blob1) HEX(blob2) HEX(blob3) +NULL NULL NULL +ABCDEF 123456 FEDCBA +FF 00 AA +SELECT o.blob1 FROM t_all_blobs_original o +WHERE NOT EXISTS ( +SELECT * FROM t_all_blobs r +WHERE (o.blob1 IS NULL AND r.blob1 IS NULL) OR o.blob1 = r.blob1 +); +blob1 +DROP TABLE t_all_blobs; +DROP TABLE t_all_blobs_original; +# Test 8: Round-trip with --dir option +CREATE TABLE t_dir_import ( +id INT PRIMARY KEY, +data BLOB +); +INSERT INTO t_dir_import VALUES +(1, X'DEADBEEF'), +(2, 'test\0data'), +(3, NULL); +CREATE TABLE t_dir_import_original LIKE t_dir_import; +INSERT INTO t_dir_import_original SELECT * FROM t_dir_import; +DROP TABLE t_dir_import; +# Import with mariadb-import --hex-blob from --dir export +test.t_dir_import: Records: 3 Deleted: 0 Skipped: 0 Warnings: 0 +# Verify data integrity +SELECT o.id FROM t_dir_import_original o, t_dir_import r +WHERE o.id = r.id +AND ((o.data IS NOT NULL AND r.data IS NOT NULL AND o.data != r.data)); +id +SELECT id, HEX(data) FROM t_dir_import ORDER BY id; +id HEX(data) +1 DEADBEEF +2 746573740064617461 +3 NULL +DROP TABLE t_dir_import; +DROP TABLE t_dir_import_original; diff --git a/mysql-test/main/mariadb-import.test b/mysql-test/main/mariadb-import.test index 29d7ed7f2f379..1d5798ed03645 100644 --- a/mysql-test/main/mariadb-import.test +++ b/mysql-test/main/mariadb-import.test @@ -235,3 +235,312 @@ use test; --exec $MYSQL_IMPORT --dir $MYSQLTEST_VARDIR/tmp/dump --parallel=300 2>&1 --rmdir $MYSQLTEST_VARDIR/tmp/dump + +--echo # +--echo # Test --hex-blob option for mariadb-import +--echo # Round-trip tests: export with mysqldump --hex-blob, import with mariadb-import --hex-blob +--echo # + +USE test; + +--echo # Test 1: Round-trip basic BLOB types with --hex-blob +CREATE TABLE t_blob_roundtrip ( + id INT PRIMARY KEY, + tiny_blob TINYBLOB, + normal_blob BLOB, + medium_blob MEDIUMBLOB, + long_blob LONGBLOB +); + +INSERT INTO t_blob_roundtrip VALUES + (1, 'tiny\0data', 'normal\0blob', 'medium\0blob', 'long\0blob'), + (2, NULL, NULL, NULL, NULL), + (3, '', '', '', ''), + (4, X'DEADBEEF', X'CAFEBABE', X'FEEDFACE', X'BAADF00D'), + (5, X'00FF00FF', X'A5A5A5A5', X'12345678', X'FFFFFFFF'); + +--echo # Export with mysqldump --hex-blob --tab +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_blob_roundtrip + +--echo # Save original data for comparison +CREATE TABLE t_blob_original LIKE t_blob_roundtrip; +INSERT INTO t_blob_original SELECT * FROM t_blob_roundtrip; + +--echo # Drop table and recreate from .sql file +DROP TABLE t_blob_roundtrip; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_blob_roundtrip.sql + +--echo # Import with mariadb-import --hex-blob +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/t_blob_roundtrip.txt + +--echo # Verify data integrity - should return 0 rows (all data matches) +SELECT * FROM t_blob_roundtrip WHERE id NOT IN (SELECT id FROM t_blob_original); +SELECT * FROM t_blob_original WHERE id NOT IN (SELECT id FROM t_blob_roundtrip); +SELECT o.id FROM t_blob_original o, t_blob_roundtrip r +WHERE o.id = r.id +AND (o.tiny_blob IS NOT NULL AND r.tiny_blob IS NOT NULL AND o.tiny_blob != r.tiny_blob); + +--echo # Show successful import +SELECT id, HEX(tiny_blob), HEX(normal_blob) FROM t_blob_roundtrip ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/t_blob_roundtrip.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_blob_roundtrip.txt +DROP TABLE t_blob_roundtrip; +DROP TABLE t_blob_original; + +--echo # Test 2: Round-trip BINARY and VARBINARY types +CREATE TABLE t_binary_roundtrip ( + id INT PRIMARY KEY, + bin_col BINARY(10), + varbin_col VARBINARY(100) +); + +INSERT INTO t_binary_roundtrip VALUES + (1, X'0102030405', 'varbinary\0test'), + (2, NULL, NULL), + (3, X'00', ''), + (4, X'FFFFFFFFFFFFFF', X'DEADBEEFCAFEBABE'); + +--echo # Export with mysqldump --hex-blob --tab +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_binary_roundtrip + +CREATE TABLE t_binary_original LIKE t_binary_roundtrip; +INSERT INTO t_binary_original SELECT * FROM t_binary_roundtrip; + +DROP TABLE t_binary_roundtrip; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_binary_roundtrip.sql + +--echo # Import with mariadb-import --hex-blob +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/t_binary_roundtrip.txt + +--echo # Verify data integrity +SELECT o.id FROM t_binary_original o, t_binary_roundtrip r +WHERE o.id = r.id +AND ((o.bin_col IS NOT NULL AND r.bin_col IS NOT NULL AND o.bin_col != r.bin_col) + OR (o.varbin_col IS NOT NULL AND r.varbin_col IS NOT NULL AND o.varbin_col != r.varbin_col)); + +--echo # Show successful import +SELECT id, HEX(bin_col), HEX(varbin_col) FROM t_binary_roundtrip ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/t_binary_roundtrip.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_binary_roundtrip.txt +DROP TABLE t_binary_roundtrip; +DROP TABLE t_binary_original; + +--echo # Test 3: Round-trip mixed table (BLOB and non-BLOB columns) +CREATE TABLE t_mixed_roundtrip ( + id INT PRIMARY KEY, + name VARCHAR(50), + data BLOB, + description TEXT, + binary_data VARBINARY(100) +); + +INSERT INTO t_mixed_roundtrip VALUES + (1, 'First', X'ABCD', 'Description 1', X'1234'), + (2, 'Second', NULL, 'Description 2', NULL), + (3, 'Third', '', 'Description 3', ''), + (4, 'Special\tChars', X'00FF00FF', 'Desc\nWith\nNewlines', X'CAFEBABE'); + +--echo # Export with mysqldump --hex-blob --tab +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_mixed_roundtrip + +CREATE TABLE t_mixed_original LIKE t_mixed_roundtrip; +INSERT INTO t_mixed_original SELECT * FROM t_mixed_roundtrip; + +DROP TABLE t_mixed_roundtrip; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_mixed_roundtrip.sql + +--echo # Import with mariadb-import --hex-blob +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/t_mixed_roundtrip.txt + +--echo # Verify all columns match +SELECT o.id FROM t_mixed_original o, t_mixed_roundtrip r +WHERE o.id = r.id +AND (o.name != r.name OR o.description != r.description + OR (o.data IS NOT NULL AND r.data IS NOT NULL AND o.data != r.data) + OR (o.binary_data IS NOT NULL AND r.binary_data IS NOT NULL AND o.binary_data != r.binary_data)); + +--echo # Show successful import +SELECT id, name, HEX(data), description, HEX(binary_data) FROM t_mixed_roundtrip ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/t_mixed_roundtrip.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_mixed_roundtrip.txt +DROP TABLE t_mixed_roundtrip; +DROP TABLE t_mixed_original; + +--echo # Test 4: Round-trip with --columns parameter (subset of columns) +CREATE TABLE t_columns_test ( + id INT PRIMARY KEY, + name VARCHAR(50), + blob1 BLOB, + blob2 BLOB, + text_col TEXT +); + +INSERT INTO t_columns_test VALUES + (1, 'Row1', X'ABCD', X'1234', 'Text1'), + (2, 'Row2', NULL, NULL, 'Text2'), + (3, 'Row3', X'BEEF', X'CAFE', 'Text3'); + +--echo # Export with mysqldump --hex-blob --tab +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_columns_test + +CREATE TABLE t_columns_original LIKE t_columns_test; +INSERT INTO t_columns_original SELECT * FROM t_columns_test; + +DROP TABLE t_columns_test; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_columns_test.sql + +--echo # Import with --hex-blob and --columns (id, blob1, blob2) +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob --columns=id,name,blob1,blob2,text_col test $MYSQLTEST_VARDIR/tmp/t_columns_test.txt + +--echo # Verify blob columns match +SELECT o.id FROM t_columns_original o, t_columns_test r +WHERE o.id = r.id +AND ((o.blob1 IS NOT NULL AND r.blob1 IS NOT NULL AND o.blob1 != r.blob1) + OR (o.blob2 IS NOT NULL AND r.blob2 IS NOT NULL AND o.blob2 != r.blob2)); + +--echo # Show successful import +SELECT id, name, HEX(blob1), HEX(blob2), text_col FROM t_columns_test ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/t_columns_test.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_columns_test.txt +DROP TABLE t_columns_test; +DROP TABLE t_columns_original; + +--echo # Test 5: Import WITHOUT --hex-blob (default behavior - should fail or produce wrong data) +CREATE TABLE t_no_hex_import ( + id INT, + data BLOB +); + +INSERT INTO t_no_hex_import VALUES (1, X'DEADBEEF'), (2, NULL); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_no_hex_import + +CREATE TABLE t_no_hex_original LIKE t_no_hex_import; +INSERT INTO t_no_hex_original SELECT * FROM t_no_hex_import; + +DROP TABLE t_no_hex_import; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_no_hex_import.sql + +--echo # Import WITHOUT --hex-blob - data will be interpreted as ASCII string, not binary +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 test $MYSQLTEST_VARDIR/tmp/t_no_hex_import.txt + +--echo # Show data mismatch - imported data is ASCII "DEADBEEF" not binary 0xDEADBEEF +SELECT id, HEX(data) as hex_data, LENGTH(data) as data_length FROM t_no_hex_import ORDER BY id; +--echo # Original was binary (4 bytes), imported is ASCII string (8 bytes) +SELECT id, HEX(data) as hex_data, LENGTH(data) as data_length FROM t_no_hex_original ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/t_no_hex_import.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_no_hex_import.txt +DROP TABLE t_no_hex_import; +DROP TABLE t_no_hex_original; + +--echo # Test 6: NULL and empty blob handling +CREATE TABLE t_null_empty ( + id INT PRIMARY KEY, + blob_null BLOB, + blob_empty BLOB, + blob_data BLOB +); + +INSERT INTO t_null_empty VALUES + (1, NULL, '', X'ABCD'), + (2, NULL, '', X'1234'); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_null_empty + +CREATE TABLE t_null_empty_original LIKE t_null_empty; +INSERT INTO t_null_empty_original SELECT * FROM t_null_empty; + +DROP TABLE t_null_empty; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_null_empty.sql + +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/t_null_empty.txt + +--echo # Verify NULL remains NULL and empty remains empty +SELECT id, blob_null IS NULL as is_null, LENGTH(blob_empty) as empty_length, HEX(blob_data) FROM t_null_empty ORDER BY id; +SELECT o.id FROM t_null_empty_original o, t_null_empty r +WHERE o.id = r.id +AND ((o.blob_null IS NULL) != (r.blob_null IS NULL) + OR o.blob_empty != r.blob_empty + OR o.blob_data != r.blob_data); + +--remove_file $MYSQLTEST_VARDIR/tmp/t_null_empty.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_null_empty.txt +DROP TABLE t_null_empty; +DROP TABLE t_null_empty_original; + +--echo # Test 7: Table with only BLOB columns +CREATE TABLE t_all_blobs ( + blob1 BLOB, + blob2 TINYBLOB, + blob3 MEDIUMBLOB +); + +INSERT INTO t_all_blobs VALUES + (X'ABCDEF', X'123456', X'FEDCBA'), + (NULL, NULL, NULL), + (X'FF', X'00', X'AA'); + +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --tab=$MYSQLTEST_VARDIR/tmp/ test t_all_blobs + +CREATE TABLE t_all_blobs_original LIKE t_all_blobs; +INSERT INTO t_all_blobs_original SELECT * FROM t_all_blobs; + +DROP TABLE t_all_blobs; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/t_all_blobs.sql + +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/t_all_blobs.txt + +--echo # Verify all blob columns match +SELECT HEX(blob1), HEX(blob2), HEX(blob3) FROM t_all_blobs ORDER BY blob1; +SELECT o.blob1 FROM t_all_blobs_original o +WHERE NOT EXISTS ( + SELECT * FROM t_all_blobs r + WHERE (o.blob1 IS NULL AND r.blob1 IS NULL) OR o.blob1 = r.blob1 +); + +--remove_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.sql +--remove_file $MYSQLTEST_VARDIR/tmp/t_all_blobs.txt +DROP TABLE t_all_blobs; +DROP TABLE t_all_blobs_original; + +--echo # Test 8: Round-trip with --dir option +CREATE TABLE t_dir_import ( + id INT PRIMARY KEY, + data BLOB +); + +INSERT INTO t_dir_import VALUES + (1, X'DEADBEEF'), + (2, 'test\0data'), + (3, NULL); + +--mkdir $MYSQLTEST_VARDIR/tmp/dump_hex +--exec $MYSQL_DUMP --default-character-set=utf8mb4 --skip-comments --hex-blob --dir=$MYSQLTEST_VARDIR/tmp/dump_hex test + +CREATE TABLE t_dir_import_original LIKE t_dir_import; +INSERT INTO t_dir_import_original SELECT * FROM t_dir_import; + +DROP TABLE t_dir_import; +--exec $MYSQL test < $MYSQLTEST_VARDIR/tmp/dump_hex/test/t_dir_import.sql + +--echo # Import with mariadb-import --hex-blob from --dir export +--exec $MYSQL_IMPORT --default-character-set=utf8mb4 --hex-blob test $MYSQLTEST_VARDIR/tmp/dump_hex/test/t_dir_import.txt + +--echo # Verify data integrity +SELECT o.id FROM t_dir_import_original o, t_dir_import r +WHERE o.id = r.id +AND ((o.data IS NOT NULL AND r.data IS NOT NULL AND o.data != r.data)); + +SELECT id, HEX(data) FROM t_dir_import ORDER BY id; + +--remove_file $MYSQLTEST_VARDIR/tmp/dump_hex/test/t_dir_import.sql +--remove_file $MYSQLTEST_VARDIR/tmp/dump_hex/test/t_dir_import.txt +--rmdir $MYSQLTEST_VARDIR/tmp/dump_hex/test +--rmdir $MYSQLTEST_VARDIR/tmp/dump_hex +DROP TABLE t_dir_import; +DROP TABLE t_dir_import_original;