Skip to content

Commit cc3ac6b

Browse files
committed
Add support for NO_BACKSLASH_ESCAPES sql_mode during SQL import (#3005)
1 parent 5fa6847 commit cc3ac6b

File tree

2 files changed

+49
-26
lines changed

2 files changed

+49
-26
lines changed

Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
* the server_status flags that were received most recently (i.e. usually
6161
* in return to the last executed query).
6262
*
63+
* Calling this method will never affect the mysql error state.
64+
*
6365
* THIS METHOD IS NOT THREAD-SAFE!
6466
*
6567
* @return YES, unless the MySQL connection is invalid (in which case the passed-in struct remains unchanged)

Source/SPDataImport.m

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -431,9 +431,25 @@ - (void)importSQLFile:(NSString *)filename
431431
sqlEncoding = [importEncodingPopup selectedTag];
432432
}
433433

434+
//store the sqlMode to restore, if the import changes it
435+
NSString *sqlModeToRestore = nil;
436+
{
437+
// this query should work in ≥ 4.1.0 (which is also the first version that allows setting sql_mode at runtime)
438+
SPMySQLResult *res = [mySQLConnection queryString:@"SELECT @@sql_mode"];
439+
[res setReturnDataAsStrings:YES]; //TODO #2700: The framework misinterprets binary collation as binary data, so in order to be safe force it to use strings
440+
441+
sqlModeToRestore = [[res getRowAsArray] objectAtIndex:0];
442+
}
443+
444+
SPMySQLServerStatusBits serverStatus;
445+
// initialize
446+
serverStatus.noBackslashEscapes = 0; // for the moment we only care about that flag
447+
434448
// Read in the file in a loop
435449
sqlParser = [[SPSQLParser alloc] init];
436450
[sqlParser setDelimiterSupport:YES];
451+
[mySQLConnection updateServerStatusBits:&serverStatus];
452+
[sqlParser setNoBackslashEscapes:serverStatus.noBackslashEscapes];
437453
sqlDataBuffer = [[NSMutableData alloc] init];
438454
importPool = [[NSAutoreleasePool alloc] init];
439455
while (1) {
@@ -442,12 +458,14 @@ - (void)importSQLFile:(NSString *)filename
442458
@try {
443459
fileChunk = [sqlFileHandle readDataOfLength:fileChunkMaxLength];
444460
}
445-
446461
// Report file read errors, and bail
447462
@catch (NSException *exception) {
448463
if (connectionEncodingToRestore) {
449464
[mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", connectionEncodingToRestore]];
450465
}
466+
if (sqlModeToRestore) {
467+
[mySQLConnection queryString:[NSString stringWithFormat:@"SET SQL_MODE=%@", [sqlModeToRestore tickQuotedString]]];
468+
}
451469
[self closeAndStopProgressSheet];
452470
SPOnewayAlertSheet(
453471
SP_FILE_READ_ERROR_STRING,
@@ -458,8 +476,7 @@ - (void)importSQLFile:(NSString *)filename
458476
[sqlDataBuffer release];
459477
[importPool drain];
460478
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
461-
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix])
462-
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
479+
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
463480
return;
464481
}
465482

@@ -488,11 +505,14 @@ - (void)importSQLFile:(NSString *)filename
488505

489506
// Try to generate a NSString with the resulting data
490507
sqlString = [[NSString alloc] initWithData:[sqlDataBuffer subdataWithRange:NSMakeRange(dataBufferLastQueryEndPosition, dataBufferPosition - dataBufferLastQueryEndPosition)]
491-
encoding:sqlEncoding];
508+
encoding:sqlEncoding];
492509
if (!sqlString) {
493510
if (connectionEncodingToRestore) {
494511
[mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", connectionEncodingToRestore]];
495512
}
513+
if (sqlModeToRestore) {
514+
[mySQLConnection queryString:[NSString stringWithFormat:@"SET SQL_MODE=%@", [sqlModeToRestore tickQuotedString]]];
515+
}
496516
[self closeAndStopProgressSheet];
497517
NSString *displayEncoding;
498518
if (![importEncodingPopup indexOfSelectedItem]) {
@@ -509,8 +529,7 @@ - (void)importSQLFile:(NSString *)filename
509529
[sqlDataBuffer release];
510530
[importPool drain];
511531
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
512-
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix])
513-
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
532+
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
514533
return;
515534
}
516535

@@ -531,12 +550,11 @@ - (void)importSQLFile:(NSString *)filename
531550
dataBufferPosition -= dataBufferLastQueryEndPosition;
532551
dataBufferLastQueryEndPosition = 0;
533552
}
534-
553+
535554
// Before entering the following loop, check that we actually have a connection.
536555
// If not, check the connection if appropriate and then clean up and exit if appropriate.
537556
if (![mySQLConnection isConnected] && ([mySQLConnection userTriggeredDisconnect] || ![mySQLConnection checkConnection])) {
538-
if ([filename hasPrefix:SPImportClipboardTempFileNamePrefix])
539-
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
557+
if ([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
540558
[self closeAndStopProgressSheet];
541559
[errors appendString:NSLocalizedString(@"The connection to the server was lost during the import. The import is only partially complete.", @"Connection lost during import error message")];
542560
[self showErrorSheetWithMessage:errors];
@@ -560,30 +578,33 @@ - (void)importSQLFile:(NSString *)filename
560578

561579
// Skip blank or whitespace-only queries to avoid errors
562580
if (![query length]) continue;
563-
581+
564582
// Run the query
565583
[mySQLConnection queryString:query usingEncoding:sqlEncoding withResultType:SPMySQLResultAsResult];
566584

585+
// in case the query was a "SET @@sql_mode = ...", the server_status may have changed
586+
if([mySQLConnection updateServerStatusBits:&serverStatus]) [sqlParser setNoBackslashEscapes:serverStatus.noBackslashEscapes];
587+
567588
// Check for any errors
568589
if ([mySQLConnection queryErrored] && ![[mySQLConnection lastErrorMessage] isEqualToString:@"Query was empty"]) {
569590
[errors appendFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(queriesPerformed+1), [mySQLConnection lastErrorMessage]];
570591

571592
// if the error is about utf8mb4 not being supported by the server display a more helpful message.
572593
// Note: the same error will occur when doing CREATE TABLE... with utf8mb4.
573-
if([mySQLConnection lastErrorID] == 1115 && [[mySQLConnection lastErrorMessage] rangeOfString:@"utf8mb4" options:NSCaseInsensitiveSearch].location != NSNotFound && [query rangeOfString:@"SET NAMES" options:NSCaseInsensitiveSearch].location != NSNotFound) {
594+
if([mySQLConnection lastErrorID] == 1115 /* ER_UNKNOWN_CHARACTER_SET */ && [[mySQLConnection lastErrorMessage] rangeOfString:@"utf8mb4" options:NSCaseInsensitiveSearch].location != NSNotFound && [query rangeOfString:@"SET NAMES" options:NSCaseInsensitiveSearch].location != NSNotFound) {
574595
if(!ignoreCharsetError) {
575596
__block NSInteger charsetErrorSheetReturnCode;
576-
597+
577598
SPMainQSync(^{
578599
NSAlert *charsetErrorAlert = [NSAlert alertWithMessageText:NSLocalizedString(@"Incompatible encoding in SQL file", @"sql import error message")
579600
defaultButton:NSLocalizedString(@"Import Anyway", @"sql import : charset error alert : continue button")
580601
alternateButton:NSLocalizedString(@"Cancel Import", @"sql import : charset error alert : cancel button")
581-
otherButton:nil
602+
otherButton:nil
582603
informativeTextWithFormat:NSLocalizedString(@"The SQL file uses utf8mb4 encoding, but your MySQL version only supports the limited utf8 subset.\n\nYou can continue the import, but any non-BMP characters in the SQL file (eg. some typographic and scientific special characters, archaic CJK logograms, emojis) will be unrecoverably lost!", @"sql import : charset error alert : detail message")];
583604
[charsetErrorAlert setAlertStyle:NSWarningAlertStyle];
584605
charsetErrorSheetReturnCode = [charsetErrorAlert runModal];
585606
});
586-
607+
587608
switch (charsetErrorSheetReturnCode) {
588609
// don't display the message a second time
589610
case NSAlertDefaultReturn:
@@ -603,25 +624,22 @@ - (void)importSQLFile:(NSString *)filename
603624

604625
SPMainQSync(^{
605626
NSAlert *sqlErrorAlert = [NSAlert alertWithMessageText:NSLocalizedString(@"An error occurred while importing SQL", @"sql import error message")
606-
defaultButton:NSLocalizedString(@"Continue", @"continue button")
607-
alternateButton:NSLocalizedString(@"Ignore All Errors", @"ignore errors button")
608-
otherButton:NSLocalizedString(@"Stop", @"stop button")
609-
informativeTextWithFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(queriesPerformed+1), [mySQLConnection lastErrorMessage]];
627+
defaultButton:NSLocalizedString(@"Continue", @"continue button")
628+
alternateButton:NSLocalizedString(@"Ignore All Errors", @"ignore errors button")
629+
otherButton:NSLocalizedString(@"Stop", @"stop button")
630+
informativeTextWithFormat:NSLocalizedString(@"[ERROR in query %ld] %@\n", @"error text when multiple custom query failed"), (long)(queriesPerformed+1), [mySQLConnection lastErrorMessage]];
610631
[sqlErrorAlert setAlertStyle:NSWarningAlertStyle];
611632
sqlImportErrorSheetReturnCode = [sqlErrorAlert runModal];
612633
});
613-
634+
614635
switch (sqlImportErrorSheetReturnCode) {
615-
616636
// On "continue", no additional action is required
617637
case NSAlertDefaultReturn:
618638
break;
619-
620639
// Ignore all future errors if asked to
621640
case NSAlertAlternateReturn:
622641
ignoreSQLErrors = YES;
623642
break;
624-
625643
// Otherwise, stop
626644
default:
627645
[errors appendString:NSLocalizedString(@"Import cancelled!\n", @"import cancelled message")];
@@ -637,14 +655,14 @@ - (void)importSQLFile:(NSString *)filename
637655
if (fileIsCompressed) {
638656
[singleProgressBar setDoubleValue:[sqlFileHandle realDataReadLength]];
639657
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of SQL", @"SQL import progress text where total size is unknown"),
640-
[NSString stringForByteSize:fileProcessedLength]]];
658+
[NSString stringForByteSize:fileProcessedLength]]];
641659
} else {
642660
[singleProgressBar setDoubleValue:fileProcessedLength];
643661
[singleProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Imported %@ of %@", @"SQL import progress text"),
644662
[NSString stringForByteSize:fileProcessedLength], [NSString stringForByteSize:fileTotalLength]]];
645663
}
646664
}
647-
665+
648666
// If all the data has been read, break out of the processing loop
649667
if (allDataRead) break;
650668

@@ -659,6 +677,7 @@ - (void)importSQLFile:(NSString *)filename
659677

660678
// Run the query
661679
[mySQLConnection queryString:query usingEncoding:sqlEncoding withResultType:SPMySQLResultAsResult];
680+
// we don't care for the server_status that is set AFTER the last query has been executed
662681

663682
// Check for any errors
664683
if ([mySQLConnection queryErrored] && ![[mySQLConnection lastErrorMessage] isEqualToString:@"Query was empty"]) {
@@ -673,12 +692,14 @@ - (void)importSQLFile:(NSString *)filename
673692
if (connectionEncodingToRestore) {
674693
[mySQLConnection queryString:[NSString stringWithFormat:@"SET NAMES '%@'", connectionEncodingToRestore]];
675694
}
695+
if (sqlModeToRestore) {
696+
[mySQLConnection queryString:[NSString stringWithFormat:@"SET SQL_MODE=%@", [sqlModeToRestore tickQuotedString]]];
697+
}
676698
[sqlParser release];
677699
[sqlDataBuffer release];
678700
[importPool drain];
679701
[tableDocumentInstance setQueryMode:SPInterfaceQueryMode];
680-
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix])
681-
[[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
702+
if([filename hasPrefix:SPImportClipboardTempFileNamePrefix]) [[NSFileManager defaultManager] removeItemAtPath:filename error:nil];
682703

683704
// Close progress sheet
684705
[self closeAndStopProgressSheet];

0 commit comments

Comments
 (0)