diff options
Diffstat (limited to 'Frameworks/SPMySQLFramework')
25 files changed, 843 insertions, 163 deletions
diff --git a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h index f2345be6..8a8c019d 100644 --- a/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h +++ b/Frameworks/SPMySQLFramework/MySQL Client Libraries/include/mysql_com.h @@ -348,7 +348,11 @@ enum enum_field_types { MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, MYSQL_TYPE_BIT, - MYSQL_TYPE_NEWDECIMAL=246, + MYSQL_TYPE_TIMESTAMP2, + MYSQL_TYPE_DATETIME2, + MYSQL_TYPE_TIME2, + MYSQL_TYPE_JSON=245, + MYSQL_TYPE_NEWDECIMAL=246, MYSQL_TYPE_ENUM=247, MYSQL_TYPE_SET=248, MYSQL_TYPE_TINY_BLOB=249, diff --git a/Frameworks/SPMySQLFramework/Resources/Info.plist b/Frameworks/SPMySQLFramework/Resources/Info.plist index 84b64d04..0932b287 100644 --- a/Frameworks/SPMySQLFramework/Resources/Info.plist +++ b/Frameworks/SPMySQLFramework/Resources/Info.plist @@ -7,7 +7,7 @@ <key>CFBundleExecutable</key> <string>SPMySQL</string> <key>CFBundleIdentifier</key> - <string>com.sequelpro.spmysql</string> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundlePackageType</key> diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m new file mode 100644 index 00000000..b8256a5c --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/DataConversion_Tests.m @@ -0,0 +1,77 @@ +// +// DataConversion_Tests.m +// SPMySQLFramework +// +// Created by Max Lohrmann on 01.10.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> + +// this function is inaccessible outside of unit tests +extern NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); + +@interface DataConversion_Tests : XCTestCase + +- (void)test_bitStringWithBytes; + +@end + +@implementation DataConversion_Tests + +- (void)test_bitStringWithBytes +{ + // BIT(1) + { + const char y = '\1'; + const char n = '\0'; + XCTAssertEqualObjects(_bitStringWithBytes(&y,sizeof(y),1), @"1"); + XCTAssertEqualObjects(_bitStringWithBytes(&n,sizeof(n),1), @"0"); + } + // BIT(3) + { + const char input[] = {5}; + NSUInteger bitSize = 3; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"101"); + } + // BIT(16) + { + const char input[] = {0xcc,0xf0}; + NSUInteger bitSize = 16; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"1100110011110000"); + } + // BIT(20) + { + const char input[] = {0x0f,0xcc,0xf0}; + NSUInteger bitSize = 20; + NSString *res = _bitStringWithBytes(input,sizeof(input),bitSize); + XCTAssertEqualObjects(res, @"11111100110011110000"); + } +} + +@end diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/Info.plist @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m new file mode 100644 index 00000000..cc1f44dd --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQL Unit Tests/SPMySQLStringAdditions_Tests.m @@ -0,0 +1,89 @@ +// +// SPMySQLStringAdditions_Tests.m +// SPMySQLFramework +// +// Created by Max Lohrmann on 04.10.15. +// +// + +#import <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> +#import <SPMySQL/SPMySQL.h> + +@interface SPMySQLStringAdditions_Tests : XCTestCase + +- (void)test_mySQLBacktickQuotedString; +- (void)test_mySQLTickQuotedString; +- (void)test_stringForDataBytesLengthEncoding; + +@end + +@implementation SPMySQLStringAdditions_Tests + +- (void)test_mySQLBacktickQuotedString +{ + XCTAssertEqualObjects([@"" mySQLBacktickQuotedString], @"``",@"empty string"); + + XCTAssertEqualObjects([@"tbl1" mySQLBacktickQuotedString], @"`tbl1`", @"regular string"); + + XCTAssertEqualObjects([@"tbl`1" mySQLBacktickQuotedString], @"`tbl``1`",@"string with control character"); + + XCTAssertEqualObjects([@"tbl``" mySQLBacktickQuotedString], @"`tbl`````",@"string with escaped control character at end"); +} + +- (void)test_mySQLTickQuotedString +{ + XCTAssertEqualObjects([@"" mySQLTickQuotedString], @"''",@"empty string"); + + XCTAssertEqualObjects([@"tbl1" mySQLTickQuotedString], @"'tbl1'", @"regular string"); + + XCTAssertEqualObjects([@"tbl'1" mySQLTickQuotedString], @"'tbl''1'",@"string with control character"); + + XCTAssertEqualObjects([@"tbl''" mySQLTickQuotedString], @"'tbl'''''",@"string with escaped control character at end"); +} + +- (void)test_stringForDataBytesLengthEncoding +{ + { + const char chr = '\0'; + NSString *conv = [NSString stringForDataBytes:&chr length:0 encoding:NSISOLatin1StringEncoding]; + XCTAssertEqualObjects(conv, @"",@"empty string test"); + } + { + const char *cstr = "an ASCII C string"; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSASCIIStringEncoding]; + XCTAssertEqualObjects(conv, @"an ASCII C string", @"simple ASCII string test"); + } + { + // the euro sign is the tricky part + // ISO-8859-1 (aka Latin1): not supported, codepoint 0x80 is not in use + // ISO-8859-1 + ISO/IEC 6429: not supported, codepoint 0x80 is PAD control character + // ISO-8859-15 (aka Latin9): € is at 0xA4, codepoint 0x80 is PAD control character + // Windows cp1252 (aka latin1 in mysql): € is at 0x80, codepoint 0xA4 is "¤" + const char cstr[] = {'\xE4','-','\xDF','-','\x80','\0'}; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSWindowsCP1252StringEncoding]; + XCTAssertEqualObjects(conv, @"ä-ß-€",@"handling of cp1252 special characters"); + + unsigned char latin9 = 0xA4; + NSString *conv2 = [NSString stringForDataBytes:&latin9 length:1 encoding:CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingISOLatin9)]; + XCTAssertEqualObjects(conv2, @"€",@"handling of iso-8859-15 special characters"); + } + { + const char *cstr = "エスキューエル"; + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSUTF8StringEncoding]; + XCTAssertEqualObjects(conv, @"エスキューエル",@"handling of valid utf-8 string"); + } + { + // this is a test for a certain mysql issue: + // mysql limits field names to 255 characters and will even cut multibyte chars in the middle, + // if neccesary. This will create invalid characters which cause NSString + // to fail and return nil on the whole string. Since we know that, we can + // at least try to return something. + char cstr[] = {'\xE3','\x82','\xA8','\xE3','\x82','\xB9','\xE3','\x82','\xAD','\xE3','\x83','\xA5','\xE3','\x83','\xBC','\xE3','\x82','\xA8','\xE3','\x83','\xAB','\0'}; // エスキューエル + cstr[strlen(cstr)-2] = '\0'; //simulate cutting off the string + NSString *conv = [NSString stringForDataBytes:cstr length:strlen(cstr) encoding:NSUTF8StringEncoding]; + XCTAssertNotNil(conv, @"handling of invalid utf8 sequences"); + } +} + +@end diff --git a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h index def1e988..31acf6b8 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h +++ b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.h @@ -72,3 +72,4 @@ extern NSString *SPMySQLMultiPointType; extern NSString *SPMySQLMultiLineStringType; extern NSString *SPMySQLMultiPolygonType; extern NSString *SPMySQLGeometryCollectionType; +extern NSString *SPMySQLJsonType; diff --git a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m index 06708f1b..d7f54c0e 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m +++ b/Frameworks/SPMySQLFramework/SPMySQLDataTypes.m @@ -74,3 +74,4 @@ NSString *SPMySQLMultiPointType = @"MULTIPOINT"; NSString *SPMySQLMultiLineStringType = @"MULTILINESTRING"; NSString *SPMySQLMultiPolygonType = @"MULTIPOLYGON"; NSString *SPMySQLGeometryCollectionType = @"GEOMETRYCOLLECTION"; +NSString *SPMySQLJsonType = @"JSON"; diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj index 9acf9417..778b97f9 100644 --- a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 17E3A57B1885A286009CF372 /* SPMySQLDataTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 17E3A5791885A286009CF372 /* SPMySQLDataTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17E3A57C1885A286009CF372 /* SPMySQLDataTypes.m in Sources */ = {isa = PBXBuildFile; fileRef = 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */; }; + 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF1811BC0C64100104523 /* DataConversion_Tests.m */; }; + 507FF23B1BC0E8CA00104523 /* SPMySQL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */; }; + 507FF23D1BC157B500104523 /* SPMySQLStringAdditions_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */; }; 580A331E14D75CF7000D6933 /* SPMySQLGeometryData.h in Headers */ = {isa = PBXBuildFile; fileRef = 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 580A331F14D75CF7000D6933 /* SPMySQLGeometryData.m in Sources */ = {isa = PBXBuildFile; fileRef = 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */; }; 583C734A17A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -75,6 +78,16 @@ 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 507FF2391BC0E8AF00104523 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = SPMySQL.framework; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; }; 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; }; @@ -83,6 +96,10 @@ 17E3A5791885A286009CF372 /* SPMySQLDataTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPMySQLDataTypes.h; sourceTree = "<group>"; }; 17E3A57A1885A286009CF372 /* SPMySQLDataTypes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLDataTypes.m; sourceTree = "<group>"; }; 32DBCF5E0370ADEE00C91783 /* SPMySQLFramework_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLFramework_Prefix.pch; path = Source/SPMySQLFramework_Prefix.pch; sourceTree = "<group>"; }; + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DataConversion_Tests.m; sourceTree = "<group>"; }; + 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SPMySQL Unit Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 507FF1D81BC0D7D300104523 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPMySQLStringAdditions_Tests.m; sourceTree = "<group>"; }; 580A331C14D75CF7000D6933 /* SPMySQLGeometryData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLGeometryData.h; path = Source/SPMySQLGeometryData.h; sourceTree = "<group>"; }; 580A331D14D75CF7000D6933 /* SPMySQLGeometryData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SPMySQLGeometryData.m; path = Source/SPMySQLGeometryData.m; sourceTree = "<group>"; }; 583C734917A489CC0056B284 /* SPMySQLStreamingResultStoreDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPMySQLStreamingResultStoreDelegate.h; path = Source/SPMySQLStreamingResultStoreDelegate.h; sourceTree = "<group>"; }; @@ -153,6 +170,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 507FF1D21BC0D7D300104523 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 507FF23B1BC0E8CA00104523 /* SPMySQL.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF560486A6940098B216 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -171,6 +196,7 @@ isa = PBXGroup; children = ( 8DC2EF5B0486A6940098B216 /* SPMySQL.framework */, + 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */, ); name = Products; sourceTree = "<group>"; @@ -184,6 +210,7 @@ 08FB77AEFE84172EC02AAC07 /* Classes */, 58C009D214E31D1300AC489A /* Category Additions */, 32C88DFF0371C24200C91783 /* Other Sources */, + 507FF1801BC0C64100104523 /* Unit Tests */, 089C1665FE841158C02AAC07 /* Resources */, 58428DF514BA5A03000F8438 /* Scripts */, 0867D69AFE84028FC02AAC07 /* Linked Frameworks */, @@ -252,6 +279,17 @@ name = "Other Sources"; sourceTree = "<group>"; }; + 507FF1801BC0C64100104523 /* Unit Tests */ = { + isa = PBXGroup; + children = ( + 507FF1D81BC0D7D300104523 /* Info.plist */, + 507FF1811BC0C64100104523 /* DataConversion_Tests.m */, + 507FF23C1BC157B500104523 /* SPMySQLStringAdditions_Tests.m */, + ); + name = "Unit Tests"; + path = "SPMySQL Unit Tests"; + sourceTree = "<group>"; + }; 580A331B14D75CCF000D6933 /* Result types */ = { isa = PBXGroup; children = ( @@ -419,6 +457,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 507FF1D41BC0D7D300104523 /* SPMySQL Unit Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 507FF1DE1BC0D7D300104523 /* Build configuration list for PBXNativeTarget "SPMySQL Unit Tests" */; + buildPhases = ( + 507FF1D11BC0D7D300104523 /* Sources */, + 507FF1D21BC0D7D300104523 /* Frameworks */, + 507FF1D31BC0D7D300104523 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 507FF23A1BC0E8AF00104523 /* PBXTargetDependency */, + ); + name = "SPMySQL Unit Tests"; + productName = "SPMySQL Unit Tests"; + productReference = 507FF1D51BC0D7D300104523 /* SPMySQL Unit Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */ = { isa = PBXNativeTarget; buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "SPMySQL.framework" */; @@ -444,7 +500,12 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0510; + LastUpgradeCheck = 0720; + TargetAttributes = { + 507FF1D41BC0D7D300104523 = { + CreatedOnToolsVersion = 6.2; + }; + }; }; buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "SPMySQLFramework" */; compatibilityVersion = "Xcode 3.2"; @@ -462,11 +523,19 @@ projectRoot = ""; targets = ( 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */, + 507FF1D41BC0D7D300104523 /* SPMySQL Unit Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 507FF1D31BC0D7D300104523 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF520486A6940098B216 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -478,6 +547,15 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 507FF1D11BC0D7D300104523 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 507FF23D1BC157B500104523 /* SPMySQLStringAdditions_Tests.m in Sources */, + 507FF1E51BC0D82300104523 /* DataConversion_Tests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8DC2EF540486A6940098B216 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -510,6 +588,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 507FF23A1BC0E8AF00104523 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* SPMySQL.framework */; + targetProxy = 507FF2391BC0E8AF00104523 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -532,7 +618,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_DYNAMIC_NO_PIC = NO; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; GENERATE_PKGINFO_FILE = YES; @@ -543,6 +628,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -559,7 +645,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -568,6 +653,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -585,6 +671,7 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_OPTIMIZATION_LEVEL = 0; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -644,6 +731,203 @@ }; name = Release; }; + 507FF1DF1BC0D7D300104523 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 507FF1E11BC0D7D300104523 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 507FF1E21BC0D7D300104523 /* Distribution */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Distribution; + }; + 507FF2361BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_64_BIT)"; + CLANG_LINK_OBJC_RUNTIME = NO; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ""; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.6; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + VALID_ARCHS = "i386 x86_64"; + }; + name = "Unit Testing"; + }; + 507FF2371BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; + GCC_PREPROCESSOR_DEFINITIONS = SPMYSQL_FOR_UNIT_TESTING; + GENERATE_PKGINFO_FILE = YES; + INFOPLIST_FILE = Resources/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/MySQL Client Libraries/lib\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; + PRODUCT_NAME = SPMySQL; + SKIP_INSTALL = YES; + WRAPPER_EXTENSION = framework; + }; + name = "Unit Testing"; + }; + 507FF2381BC0E0A800104523 /* Unit Testing */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(DEVELOPER_FRAMEWORKS_DIR)", + "$(inherited)", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = "SPMySQL Unit Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.9; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.sequelpro.spmysql-unittests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = "Unit Testing"; + }; 586AA55214F5D599007F82BF /* Distribution */ = { isa = XCBuildConfiguration; buildSettings = { @@ -688,7 +972,6 @@ DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; - GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = Source/SPMySQLFramework_Prefix.pch; INFOPLIST_FILE = Resources/Info.plist; @@ -697,6 +980,7 @@ "$(inherited)", "\"$(SRCROOT)/MySQL Client Libraries/lib\"", ); + PRODUCT_BUNDLE_IDENTIFIER = com.sequelpro.spmysql; PRODUCT_NAME = SPMySQL; SKIP_INSTALL = YES; WRAPPER_EXTENSION = framework; @@ -710,6 +994,7 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91AE08733DA50010E9CD /* Debug */, + 507FF2371BC0E0A800104523 /* Unit Testing */, 1DEB91AF08733DA50010E9CD /* Release */, 586AA55314F5D599007F82BF /* Distribution */, ); @@ -720,12 +1005,24 @@ isa = XCConfigurationList; buildConfigurations = ( 1DEB91B208733DA50010E9CD /* Debug */, + 507FF2361BC0E0A800104523 /* Unit Testing */, 1DEB91B308733DA50010E9CD /* Release */, 586AA55214F5D599007F82BF /* Distribution */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 507FF1DE1BC0D7D300104523 /* Build configuration list for PBXNativeTarget "SPMySQL Unit Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 507FF1DF1BC0D7D300104523 /* Debug */, + 507FF2381BC0E0A800104523 /* Unit Testing */, + 507FF1E11BC0D7D300104523 /* Release */, + 507FF1E21BC0D7D300104523 /* Distribution */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 0867D690FE84028FC02AAC07 /* Project object */; diff --git a/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme new file mode 100644 index 00000000..6b85f25b --- /dev/null +++ b/Frameworks/SPMySQLFramework/SPMySQLFramework.xcodeproj/xcshareddata/xcschemes/SPMySQL.framework.xcscheme @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0720" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Unit Testing" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "507FF1D41BC0D7D300104523" + BuildableName = "SPMySQL Unit Tests.xctest" + BlueprintName = "SPMySQL Unit Tests" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "8DC2EF4F0486A6940098B216" + BuildableName = "SPMySQL.framework" + BlueprintName = "SPMySQL.framework" + ReferencedContainer = "container:SPMySQLFramework.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h index 2419d7a9..1e2a8c14 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQL Private APIs.h @@ -82,6 +82,7 @@ @interface SPMySQLConnection (Querying_and_Preparation_Private_API) - (void)_flushMultipleResultSets; +- (void)_updateLastErrorInfos; - (void)_updateLastErrorMessage:(NSString *)theErrorMessage; - (void)_updateLastErrorID:(NSUInteger)theErrorID; - (void)_updateLastSqlstate:(NSString *)theSqlstate; @@ -99,12 +100,7 @@ @end // SPMySQLResult Data Conversion Private API -@interface SPMySQLResult (Data_Conversion_Private_API) - -+ (void)_initializeDataConversion; -- (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength; - -@end +#import "Data Conversion.h" /** * Set up a static function to allow fast calling of SPMySQLResult data conversion with cached selectors diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m index fb949679..76f323bc 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Encoding.m @@ -213,7 +213,7 @@ if (!strcmp(mysqlCharset, "utf8")) { return NSUTF8StringEncoding; } else if (!strcmp(mysqlCharset, "latin1")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; // Warning: This is NOT the same as ISO-8859-1 (aka "ISO Latin 1") } else if (!strcmp(mysqlCharset, "ascii")) { return NSASCIIStringEncoding; @@ -289,7 +289,7 @@ } else if (!strcmp(mysqlCharset, "dos")) { return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatin1); } else if (!strcmp(mysqlCharset, "german1")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; } else if (!strcmp(mysqlCharset, "usa7")) { return NSASCIIStringEncoding; } else if (!strcmp(mysqlCharset, "danish")) { @@ -313,7 +313,7 @@ } else if (!strcmp(mysqlCharset, "croat")) { return NSISOLatin2StringEncoding; } else if (!strcmp(mysqlCharset, "latin1_de")) { - return NSISOLatin1StringEncoding; + return NSWindowsCP1252StringEncoding; } /** diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h index cff8d43b..a3f34817 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.h @@ -32,8 +32,8 @@ typedef struct { MYSQL *mySQLConnection; - BOOL *keepAlivePingActivePointer; - BOOL *keepAliveLastPingSuccessPointer; + volatile BOOL *keepAlivePingThreadActivePointer; + volatile BOOL *keepAliveLastPingSuccessPointer; } SPMySQLConnectionPingDetails; @interface SPMySQLConnection (Ping_and_KeepAlive) @@ -51,6 +51,6 @@ void _forceThreadExit(int signalNumber); void _pingThreadCleanup(void *pingDetails); // Cancellation -- (void)_cancelKeepAlives; +- (BOOL)_cancelKeepAlives; @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m index e8338bb4..7940b483 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Ping & KeepAlive.m @@ -80,6 +80,10 @@ */ - (void)_threadedKeepAlive { + if(keepAliveThread) { + NSLog(@"warning: overwriting existing keepAliveThread: %@, results may be unpredictable!",keepAliveThread); + } + keepAliveThread = [NSThread currentThread]; [keepAliveThread setName:@"SPMySQL connection keepalive monitor thread"]; @@ -90,16 +94,15 @@ // attempt a single reconnection in the background if (_elapsedSecondsSinceAbsoluteTime(lastConnectionUsedTime) < 60 * 15) { [self _reconnectAfterBackgroundConnectionLoss]; - + } // Otherwise set the state to connection lost for automatic reconnect on // next use. - } else { + else { state = SPMySQLConnectionLostInBackground; } // Return as no further ping action required this cycle. - keepAliveThread = nil; - return; + goto end_cleanup; } // Otherwise, perform a background ping. @@ -109,6 +112,7 @@ } else { keepAlivePingFailures++; } +end_cleanup: keepAliveThread = nil; } @@ -135,8 +139,13 @@ // Set up a query lock [self _lockConnection]; + //we might find ourselves at the losing end of a contest with -[self _disconnect] + if(!mySQLConnection) { + [self _unlockConnection]; + return NO; + } - keepAliveLastPingSuccess = NO; + volatile BOOL keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; keepAlivePingThreadActive = YES; @@ -148,12 +157,14 @@ SPMySQLConnectionPingDetails *pingDetails = malloc(sizeof(SPMySQLConnectionPingDetails)); pingDetails->mySQLConnection = mySQLConnection; pingDetails->keepAliveLastPingSuccessPointer = &keepAliveLastPingSuccess; - pingDetails->keepAlivePingActivePointer = &keepAlivePingThreadActive; + pingDetails->keepAlivePingThreadActivePointer = &keepAlivePingThreadActive; // Create a pthread for the ping + pthread_t keepAlivePingThread_t; + pthread_attr_t attr; pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); pthread_create(&keepAlivePingThread_t, &attr, (void *)&_backgroundPingTask, pingDetails); // Record the ping start time @@ -166,7 +177,7 @@ // If the ping timeout has been exceeded, or the ping thread has been // cancelled, force a timeout; double-check that the thread is still active. - if (([keepAliveThread isCancelled] || pingElapsedTime > pingTimeout) + if (([[NSThread currentThread] isCancelled] || pingElapsedTime > pingTimeout) && keepAlivePingThreadActive && !threadCancelled) { @@ -182,6 +193,9 @@ keepAliveLastPingBlocked = YES; } } while (keepAlivePingThreadActive); + + //wait for thread to go away, otherwise our free() below might run before _pingThreadCleanup() + pthread_join(keepAlivePingThread_t, NULL); // Clean up keepAlivePingThread_t = NULL; @@ -238,7 +252,7 @@ void _forceThreadExit(int signalNumber) void _pingThreadCleanup(void *pingDetails) { SPMySQLConnectionPingDetails *pingDetailsStruct = pingDetails; - *(pingDetailsStruct->keepAlivePingActivePointer) = NO; + *(pingDetailsStruct->keepAlivePingThreadActivePointer) = NO; // Clean up MySQL variables and handlers mysql_thread_end(); @@ -250,24 +264,28 @@ void _pingThreadCleanup(void *pingDetails) /** * If a keepalive thread is active, cancel it, and wait a short time for it * to exit. + * + * @return YES, if the thread exited within 10 seconds after canceling it */ -- (void)_cancelKeepAlives +- (BOOL)_cancelKeepAlives { // If no keepalive thread is active, return - if (!keepAliveThread) { - return; - } + if (keepAliveThread) { - // Mark the thread as cancelled - [keepAliveThread cancel]; + // Mark the thread as cancelled + [keepAliveThread cancel]; - // Wait inside a time limit of ten seconds for it to exit - uint64_t threadCancelStartTime_t = mach_absolute_time(); - do { - usleep(100000); - if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) break; - } while (keepAliveThread); + // Wait inside a time limit of ten seconds for it to exit + uint64_t threadCancelStartTime_t = mach_absolute_time(); + do { + usleep(100000); + if (_elapsedSecondsSinceAbsoluteTime(threadCancelStartTime_t) > 10) return NO; + } while (keepAliveThread); + + } + + return YES; } @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m index 48f4fc1e..d96ebe52 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Querying & Preparation.m @@ -58,6 +58,9 @@ * Take a string and escapes any special character for safe use within a query; correctly * escapes any characters within the string using the current connection encoding. * Allows control over whether to also wrap the string in single quotes. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (NSString *)escapeString:(NSString *)theString includingQuotes:(BOOL)includeQuotes { @@ -72,11 +75,12 @@ } return nil; } - if (![self _checkConnectionIfNecessary]) return nil; // Ensure per-thread variables are set up [self _validateThreadSetup]; + if (![self _checkConnectionIfNecessary]) return nil; + // Perform a lossy conversion to bytes, using NSData to do the hard work. Preserves // nul characters correctly. NSData *cData = [theString dataUsingEncoding:stringEncoding allowLossyConversion:YES]; @@ -221,6 +225,9 @@ * the connection encoding. * The result type desired can be specified, supporting either standard or streaming * result sets. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (id)queryString:(NSString *)theQueryString usingEncoding:(NSStringEncoding)theEncoding withResultType:(SPMySQLResultType)theReturnType { @@ -288,7 +295,8 @@ // Lock the connection while it's actively in use [self _lockConnection]; - while (queryAttemptsAllowed > 0) { + unsigned long long theAffectedRowCount; + do { // While recording the overall execution time (including network lag!), run // the raw query @@ -296,6 +304,11 @@ queryStatus = mysql_real_query(mySQLConnection, queryBytes, queryBytesLength); queryExecutionTime = _elapsedSecondsSinceAbsoluteTime(queryStartTime); lastConnectionUsedTime = mach_absolute_time(); + + // "An integer greater than zero indicates the number of rows affected or retrieved. + // Zero indicates that no records were updated for an UPDATE statement, no rows matched the WHERE clause in the query or that no query has yet been executed. + // -1 indicates that the query returned an error or that, for a SELECT query, mysql_affected_rows() was called prior to calling mysql_store_result()." + theAffectedRowCount = mysql_affected_rows(mySQLConnection); // If the query succeeded, no need to re-attempt. if (!queryStatus) { @@ -313,7 +326,7 @@ theSqlstate = [self _stringForCString:mysql_sqlstate(mySQLConnection)]; // Prevent retries if the query was cancelled or not a connection error - if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:mysql_errno(mySQLConnection)]) { + if (lastQueryWasCancelled || ![SPMySQLConnection isErrorIDConnectionError:theErrorID]) { break; } } @@ -327,11 +340,10 @@ return nil; } [self _lockConnection]; + NSAssert(mySQLConnection != NULL, @"mySQLConnection has disappeared while checking it!"); - queryAttemptsAllowed--; - } + } while (--queryAttemptsAllowed > 0); - unsigned long long theAffectedRowCount = mysql_affected_rows(mySQLConnection); id theResult = nil; // On success, if there is a query result, retrieve the result data type @@ -660,6 +672,16 @@ } /** + * Update lastErrorID, lastErrorMessage and lastSqlstate from connection + */ +- (void)_updateLastErrorInfos +{ + [self _updateLastErrorID:NSNotFound]; + [self _updateLastErrorMessage:nil]; + [self _updateLastSqlstate:nil]; +} + +/** * Update the MySQL error message for this connection. If an error is supplied * it will be stored and returned to anything asking the instance for the last * error; if no error is supplied, the connection will be used to derive (or clear) diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h index 82607cdb..8ec6c9e0 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.h @@ -45,4 +45,14 @@ - (SPMySQLResult *)listProcesses; - (BOOL)killQueryOnThreadID:(unsigned long)theThreadID; +/** + * mysql_shutdown() - If the user has the permission, will shutdown the (remote) server + * @return Whether the command was executed successfully + * Note: this can also be NO if the user denied a reconnect attempt. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! + */ +- (BOOL)serverShutdown; + @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m index dd684c78..db846929 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection Categories/Server Info.m @@ -46,54 +46,34 @@ return [NSString stringWithString:serverVariableVersion]; } -#warning FIXME: There is probably a race condition here with -[self _disconnect] - if(mySQLConnection) { - return [self _stringForCString:mysql_get_server_info(mySQLConnection)]; - } - return nil; } /** - * Return the server major version or NSNotFound on failure + * Return the server major version or 0 on failure */ - (NSUInteger)serverMajorVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:0]; - return (NSUInteger)[s integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 / 10'000 => 5.0533 => 5 + return (serverVersionNumber / 10000); } /** - * Return the server minor version or NSNotFound on failure + * Return the server minor version or 0 on failure */ - (NSUInteger)serverMinorVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:1]; - return (NSUInteger)[s integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 - (5*10'000) => 533 / 100 => 5.33 => 5 + return ((serverVersionNumber - [self serverMajorVersion]*10000) / 100); } /** - * Return the server release version or NSNotFound on failure + * Return the server release version or 0 on failure */ - (NSUInteger)serverReleaseVersion { - NSString *ver; - if ((ver = [self serverVersionString]) != nil) { - NSString *s = [[ver componentsSeparatedByString:@"."] objectAtIndex:2]; - return (NSUInteger)[[[s componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; - } - - return NSNotFound; + // 5.5.33 => 50533 - (5*10'000 + 5*100) => 33 + return (serverVersionNumber - ([self serverMajorVersion]*10000 + [self serverMinorVersion]*100)); } #pragma mark - @@ -105,23 +85,9 @@ */ - (BOOL)serverVersionIsGreaterThanOrEqualTo:(NSUInteger)aMajorVersion minorVersion:(NSUInteger)aMinorVersion releaseVersion:(NSUInteger)aReleaseVersion { - NSString *ver; - if (!(ver = [self serverVersionString])) return NO; - - NSArray *serverVersionParts = [ver componentsSeparatedByString:@"."]; - - NSUInteger serverMajorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:0] integerValue]; - if (serverMajorVersion < aMajorVersion) return NO; - if (serverMajorVersion > aMajorVersion) return YES; + unsigned long myver = aMajorVersion * 10000 + aMinorVersion * 100 + aReleaseVersion; - NSUInteger serverMinorVersion = (NSUInteger)[[serverVersionParts objectAtIndex:1] integerValue]; - if (serverMinorVersion < aMinorVersion) return NO; - if (serverMinorVersion > aMinorVersion) return YES; - - NSString *serverReleasePart = [serverVersionParts objectAtIndex:2]; - NSUInteger serverReleaseVersion = (NSUInteger)[[[serverReleasePart componentsSeparatedByString:@"-"] objectAtIndex:0] integerValue]; - if (serverReleaseVersion < aReleaseVersion) return NO; - return YES; + return (serverVersionNumber >= myver); } #pragma mark - @@ -132,6 +98,9 @@ * the resulting process list defaults to the short form; run a manual SHOW FULL PROCESSLIST * to retrieve tasks in non-truncated form. * Returns nil on error. + * + * WARNING: This method may return nil if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (SPMySQLResult *)listProcesses { @@ -182,4 +151,21 @@ return ![self queryErrored]; } +- (BOOL)serverShutdown +{ + if([self _checkConnectionIfNecessary]) { + [self _lockConnection]; + // Ensure per-thread variables are set up + [self _validateThreadSetup]; + //only SHUTDOWN_DEFAULT is supported right now + int res = mysql_shutdown(mySQLConnection, SHUTDOWN_DEFAULT); + //update or clear error + [self _updateLastErrorInfos]; + [self _unlockConnection]; + + return (res == 0); + } + return NO; +} + @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h index c65ec2fb..15b809f1 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.h @@ -85,10 +85,8 @@ CGFloat keepAliveInterval; uint64_t lastKeepAliveTime; NSUInteger keepAlivePingFailures; - NSThread *keepAliveThread; - pthread_t keepAlivePingThread_t; - BOOL keepAlivePingThreadActive; - BOOL keepAliveLastPingSuccess; + volatile NSThread *keepAliveThread; + volatile BOOL keepAlivePingThreadActive; BOOL keepAliveLastPingBlocked; // Encoding details - and also a record of any previous encoding to allow @@ -101,6 +99,7 @@ // Server details NSString *serverVariableVersion; + unsigned long serverVersionNumber; // Error state for the last query or connection state NSUInteger queryErrorID; @@ -129,6 +128,8 @@ BOOL retryQueriesOnConnectionFailure; SPMySQLClientFlags clientFlags; + + NSString *_debugLastConnectedEvent; } #pragma mark - diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m index bcb4e031..286296af 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLConnection.m @@ -145,9 +145,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS keepAlivePingFailures = 0; lastKeepAliveTime = 0; keepAliveThread = nil; - keepAlivePingThread_t = NULL; keepAlivePingThreadActive = NO; - keepAliveLastPingSuccess = NO; keepAliveLastPingBlocked = NO; // Set up default encoding variables @@ -176,6 +174,7 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // Ensure the server detail records are initialised serverVariableVersion = nil; + serverVersionNumber = 0; // Start with a blank error state queryErrorID = 0; @@ -200,6 +199,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS // while running them retryQueriesOnConnectionFailure = YES; + _debugLastConnectedEvent = nil; + // Start the ping keepalive timer keepAliveTimer = [[SPMySQLKeepAliveTimer alloc] initWithInterval:10 target:self selector:@selector(_keepAlive)]; @@ -254,6 +255,8 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS if (querySqlstate) [querySqlstate release], querySqlstate = nil; [delegateDecisionLock release]; + [_debugLastConnectedEvent release]; + [NSObject cancelPreviousPerformRequestsWithTarget:self]; [super dealloc]; @@ -279,6 +282,9 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS * Error checks extensively - if this method fails, it will ask how to proceed and loop depending * on the status, not returning control until either a connection has been established or * the connection and document have been closed. + * + * WARNING: This method may exit early returning NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)reconnect { @@ -327,10 +333,12 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS * Checks whether the connection to the server is still active. This verifies * the connection using a ping, and if the connection is found to be down attempts * to quickly restore it, including the previous state. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)checkConnection { - // If the connection is not seen as active, don't proceed if (state != SPMySQLConnected) return NO; @@ -429,6 +437,14 @@ const char *SPMySQLSSLPermissibleCiphers = "DHE-RSA-AES256-SHA:AES256-SHA:DHE-RS const char *__crashreporter_info__ = NULL; asm(".desc ___crashreporter_info__, 0x10"); +static uint64_t _elapsedMicroSecondsSinceAbsoluteTime(uint64_t comparisonTime) +{ + uint64_t elapsedTime_t = mach_absolute_time() - comparisonTime; + Nanoseconds elapsedTime = AbsoluteToNanoseconds(*(AbsoluteTime *)&(elapsedTime_t)); + + return (UnsignedWideToUInt64(elapsedTime) / 1000ULL); +} + @implementation SPMySQLConnection (PrivateAPI) /** @@ -439,8 +455,11 @@ asm(".desc ___crashreporter_info__, 0x10"); // If a connection is already active in some form, throw an exception if (state != SPMySQLDisconnected && state != SPMySQLConnectionLostInBackground) { - asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state); - __builtin_trap(); + @synchronized (self) { + uint64_t diff = _elapsedMicroSecondsSinceAbsoluteTime(initialConnectTime); + asprintf(&__crashreporter_info__, "Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).\nIf state==2: Previous connection made %lluµs ago from: %s", state, diff, [_debugLastConnectedEvent cStringUsingEncoding:NSUTF8StringEncoding]); + __builtin_trap(); + } [NSException raise:NSInternalInconsistencyException format:@"Attempted to connect a connection that is not disconnected (SPMySQLConnectionState=%d).", state]; return NO; } @@ -466,17 +485,36 @@ asm(".desc ___crashreporter_info__, 0x10"); // If the connection was cancelled, clean up and don't continue if (userTriggeredDisconnect) { mysql_close(mySQLConnection); - [self _unlockConnection]; mySQLConnection = NULL; + [self _unlockConnection]; return NO; } // Successfully connected - record connected state and reset tracking variables state = SPMySQLConnected; - initialConnectTime = mach_absolute_time(); + + @synchronized (self) { + initialConnectTime = mach_absolute_time(); + [_debugLastConnectedEvent release]; + _debugLastConnectedEvent = [[NSString alloc] initWithFormat:@"thread=%@ stack=%@",[NSThread currentThread],[NSThread callStackSymbols]]; + } + mysqlConnectionThreadId = mySQLConnection->thread_id; lastConnectionUsedTime = initialConnectTime; + // Copy the server version string to the instance variable + if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; + // the mysql_get_server_info() function + // * returns the version name that is part of the initial connection handshake. + // * Unless the connection failed, it will always return a non-null buffer containing at least a '\0'. + // * It will never affect the error variables (since it only returns a struct member) + // + // At that point (handshake) there is no charset and it's highly unlikely this will ever contain something other than ASCII, + // but to be safe, we'll use the Latin1 encoding which won't bail on invalid chars. + serverVariableVersion = [[NSString alloc] initWithCString:mysql_get_server_info(mySQLConnection) encoding:NSISOLatin1StringEncoding]; + // this one can actually change the error state, but only if the server version string is not set (ie. no connection) + serverVersionNumber = mysql_get_server_version(mySQLConnection); + // Update SSL state connectedWithSSL = NO; if (useSSL) connectedWithSSL = (mysql_get_ssl_cipher(mySQLConnection))?YES:NO; @@ -491,9 +529,7 @@ asm(".desc ___crashreporter_info__, 0x10"); keepAlivePingFailures = 0; // Clear the connection error record - [self _updateLastErrorID:NSNotFound]; - [self _updateLastErrorMessage:nil]; - [self _updateLastSqlstate:nil]; + [self _updateLastErrorInfos]; // Unlock the connection [self _unlockConnection]; @@ -616,6 +652,9 @@ asm(".desc ___crashreporter_info__, 0x10"); * the connection and document have been closed. * Runs its own autorelease pool as sometimes called in a thread following proxy changes * (where the return code doesn't matter). + * + * WARNING: This method may exit early returning NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)_reconnectAllowingRetries:(BOOL)canRetry { @@ -889,11 +928,15 @@ asm(".desc ___crashreporter_info__, 0x10"); uint64_t disconnectStartTime_t = mach_absolute_time(); while (![self _tryLockConnection]) { usleep(100000); - if (_elapsedSecondsSinceAbsoluteTime(disconnectStartTime_t) > 10) break; + if (_elapsedSecondsSinceAbsoluteTime(disconnectStartTime_t) > 10) { + NSLog(@"%s: Could not acquire connection lock within time limit (10s). Forcing unlock!",__PRETTY_FUNCTION__); + break; + } } [self _unlockConnection]; [self _cancelKeepAlives]; + [self _lockConnection]; // Close the underlying MySQL connection if it still appears to be active, and not reading // or writing. While this may result in a leak of the MySQL object, it prevents crashes // due to attempts to close a blocked/stuck connection. @@ -901,17 +944,16 @@ asm(".desc ___crashreporter_info__, 0x10"); mysql_close(mySQLConnection); } mySQLConnection = NULL; + if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; + serverVersionNumber = 0; + if (database) [database release], database = nil; + state = SPMySQLDisconnected; + [self _unlockConnection]; // If using a connection proxy, disconnect that too if (proxy) { [proxy performSelectorOnMainThread:@selector(disconnect) withObject:nil waitUntilDone:YES]; } - - // Clear host-specific information - if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; - if (database) [database release], database = nil; - - state = SPMySQLDisconnected; } /** @@ -936,19 +978,32 @@ asm(".desc ___crashreporter_info__, 0x10"); [variables setObject:[variableRow objectAtIndex:1] forKey:[variableRow objectAtIndex:0]]; } - // Copy the server version string to the instance variable - if (serverVariableVersion) [serverVariableVersion release], serverVariableVersion = nil; - serverVariableVersion = [[variables objectForKey:@"version"] retain]; - // Get the connection encoding. Although a specific encoding may have been requested on // connection, it may be overridden by init_connect commands or connection state changes. // Default to latin1 for older server versions. NSString *retrievedEncoding = @"latin1"; + // character_set_results is the charset the strings received from the server will be in if ([variables objectForKey:@"character_set_results"]) { retrievedEncoding = [variables objectForKey:@"character_set_results"]; - } else if ([variables objectForKey:@"character_set"]) { + } + // not used in 4.1+ (?) + else if ([variables objectForKey:@"character_set"]) { retrievedEncoding = [variables objectForKey:@"character_set"]; } + // character_set_client is the charset the server expects strings transmitted by us to be in + else if ([variables objectForKey:@"character_set_client"]) { + retrievedEncoding = [variables objectForKey:@"character_set_client"]; // fallback for sphinxql + } + // character_set_connection is used internally by the server for comparisons. + // String literals (without a cast) will always be converted from character_set_client to character_set_connection first. + // As an example: + // * Use a client with "SET NAMES utf8" + // * Do a "set @@session.character_set_connection = 'latin1';" + // * Finally try a "SELECT '犬';" (also try "select _utf8'犬';" for completeness) + // * The result will just show a "?" + // So even though we told the server that the client uses utf8 and the results + // should be encoded in utf8, too, the character got lost. + // This happened because the server did a roundtrip of utf8 -> latin1 -> utf8. // Update instance variables if (encoding) [encoding release]; @@ -992,6 +1047,9 @@ asm(".desc ___crashreporter_info__, 0x10"); * each of which requires a round trip to the server - but handles most * network issues. * Returns whether the connection is considered still valid. + * + * WARNING: This method may return NO if the current thread is cancelled! + * You MUST check the isCancelled flag before using the result! */ - (BOOL)_checkConnectionIfNecessary { @@ -1020,7 +1078,6 @@ asm(".desc ___crashreporter_info__, 0x10"); */ - (void)_validateThreadSetup { - // Check to see whether the handler has already been installed if (pthread_getspecific(mySQLThreadInitFlagKey)) return; @@ -1031,7 +1088,7 @@ asm(".desc ___crashreporter_info__, 0x10"); pthread_setspecific(mySQLThreadInitFlagKey, &mySQLThreadFlag); // Set up the notification handler to deregister it - [(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; + [[NSNotificationCenter defaultCenter] addObserver:[self class] selector:@selector(_removeThreadVariables:) name:NSThreadWillExitNotification object:[NSThread currentThread]]; } /** diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m index 930180da..53ab116f 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLFastStreamingResult.m @@ -392,9 +392,7 @@ typedef struct st_spmysqlstreamingrowdata { } // Update the connection's error statuses to reflect any errors during the content download - [parentConnection _updateLastErrorID:NSNotFound]; - [parentConnection _updateLastErrorMessage:nil]; - [parentConnection _updateLastSqlstate:nil]; + [parentConnection _updateLastErrorInfos]; // Unlock the parent connection now all data has been retrieved [parentConnection _unlockConnection]; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h index 817d2cb7..7d865226 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.h @@ -30,12 +30,7 @@ @interface SPMySQLResult (Data_Conversion_Private_API) ++ (void)_initializeDataConversion; - (id)_getObjectFromBytes:(char *)bytes ofLength:(NSUInteger)length fieldDefinitionIndex:(NSUInteger)fieldIndex previewLength:(NSUInteger)previewLength; -static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField); - -static inline NSString * _stringWithBytes(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); -static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); -static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); - @end diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m index 639ff0b9..3b29fb5e 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Data Conversion.m @@ -31,6 +31,16 @@ #import "Data Conversion.h" +#ifdef SPMYSQL_FOR_UNIT_TESTING +#define PRIVATE /* public */ +#else +#define PRIVATE static inline +#endif + +PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField); +PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength); +PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength); + static SPMySQLResultFieldProcessor fieldProcessingMap[256]; static id NSNullPointer; static NSStringEncoding NSFromCFStringEncodingBig5; @@ -68,6 +78,7 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95; fieldProcessingMap[MYSQL_TYPE_NEWDATE] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_VARCHAR] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_BIT] = SPMySQLResultFieldAsBit; + fieldProcessingMap[MYSQL_TYPE_JSON] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_NEWDECIMAL] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_ENUM] = SPMySQLResultFieldAsString; fieldProcessingMap[MYSQL_TYPE_SET] = SPMySQLResultFieldAsString; @@ -161,10 +172,12 @@ static NSStringEncoding NSFromCFStringEncodingGBK_95; return nil; } +@end + /** * Returns the field processor to use for a specified field. */ -static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) +PRIVATE SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) { // Determine the default field processor to use SPMySQLResultFieldProcessor dataProcessor = fieldProcessingMap[aField.type]; @@ -200,7 +213,7 @@ static inline SPMySQLResultFieldProcessor _processorForField(MYSQL_FIELD aField) * field length. * MySQL stores bit data as string data stored in an 8-bit wide character set. */ -static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength) +PRIVATE NSString * _bitStringWithBytes(const char *bytes, NSUInteger length, NSUInteger padLength) { NSUInteger i = 0; NSUInteger bitLength = length << 3; @@ -209,27 +222,26 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt return nil; } - // Ensure padLength is never lower than the length - if (padLength < bitLength) { - padLength = bitLength; - } - + // use whatever is smaller. padLength comes from BIT(x), bitLength from the actual bytes transmitted. + // if bitLength < padLength it means the value is smaller than what the field can accomodate. + // if bitLength > padLength it means BIT(x) is not a full n bytes long and was extended by mysqls storage. + // In that case the additional bits should still be 0 as mysql does not allow to set bits over the size of x. + bitLength = MIN(bitLength,padLength); // Generate a nul-terminated C string representation of the binary data char *cStringBuffer = malloc(padLength + 1); - cStringBuffer[padLength] = '\0'; + memset(cStringBuffer, '0', padLength); while (i < bitLength) { + // start with the least significant bit (the rightmost bit in the last byte) and move left + unsigned char bitInByteMask = i % 8; // 0-7, the cycle is 0,1,...,7,0,... + unsigned long bytesOffset = (length - 1) - (i >> 3); // i>>3 == floor(i/8) ++i; - - cStringBuffer[padLength - i] = ((bytes[length - 1 - (i >> 3)] >> (i & 0x7)) & 1 ) ? '1' : '0'; + cStringBuffer[padLength - i] = ((bytes[bytesOffset] & (1 << bitInByteMask)) != 0) ? '1' : '0'; } - - while (i++ < padLength) - { - cStringBuffer[padLength - i] = '0'; - } - + + cStringBuffer[padLength] = '\0'; + // Convert to a string NSString *returnString = [NSString stringWithUTF8String:cStringBuffer]; @@ -243,7 +255,7 @@ static inline NSString * _bitStringWithBytes(const char *bytes, NSUInteger lengt * Converts stored string data - which may contain nul bytes - to a native * Objective-C string, using the current class encoding. */ -static inline NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength) +PRIVATE NSString * _convertStringData(const void *dataBytes, NSUInteger dataLength, NSStringEncoding aStringEncoding, NSUInteger previewLength) { // Fast case - if not using a preview length, or if the data length is shorter, return the requested data. @@ -414,5 +426,4 @@ static inline NSString * _convertStringData(const void *dataBytes, NSUInteger da return previewString; } - -@end +#undef PRIVATE diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m index c61b9140..ec52e0e3 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult Categories/Field Definitions.m @@ -29,6 +29,7 @@ // More info at <https://github.com/sequelpro/sequelpro> #import "Field Definitions.h" +#import "SPMySQL Private APIs.h" @interface SPMySQLResult (Field_Definitions_Private_API) @@ -40,14 +41,6 @@ @end -// Import a private declaration from the SPMySQLResult file for use -@interface SPMySQLResult (Private_API) - -- (NSString *)_stringWithBytes:(const void *)bytes length:(NSUInteger)length; -- (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy; - -@end - #define MAGIC_BINARY_CHARSET_NR 63 const SPMySQLResultCharset SPMySQLCharsetMap[] = @@ -379,7 +372,7 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] = switch (type) { - case FIELD_TYPE_BIT: + case MYSQL_TYPE_BIT: return @"BIT"; case MYSQL_TYPE_DECIMAL: @@ -482,6 +475,9 @@ const SPMySQLResultCharset SPMySQLCharsetMap[] = case MYSQL_TYPE_GEOMETRY: return @"GEOMETRY"; + + case MYSQL_TYPE_JSON: + return @"JSON"; default: return @"UNKNOWN"; diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m index 5f54960c..2e1cb2ba 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLResult.m @@ -318,7 +318,7 @@ static id NSNullPointer; { return [[[NSString alloc] initWithBytes:bytes length:length encoding:stringEncoding] autorelease]; } - +#warning duplicate code with Data Conversion.m stringForDataBytes:length:encoding: (↑, ↓) - (NSString *)_lossyStringWithBytes:(const void *)bytes length:(NSUInteger)length wasLossy:(BOOL *)outLossy { if(!bytes || !length) return @""; //to match -[NSString initWithBytes:length:encoding:] diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h index d66dfd81..5fa6f406 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.h @@ -79,7 +79,7 @@ static inline unsigned long long SPMySQLResultStoreGetRowCount(SPMySQLStreamingR { typedef unsigned long long (*SPMSRSRowCountMethodPtr)(SPMySQLStreamingResultStore*, SEL); static SPMSRSRowCountMethodPtr SPMSRSRowCount; - if (!SPMSRSRowCount) SPMSRSRowCount = (SPMSRSRowCountMethodPtr)[self methodForSelector:@selector(numberOfRows)]; + if (!SPMSRSRowCount) SPMSRSRowCount = (SPMSRSRowCountMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(numberOfRows)]; return SPMSRSRowCount(self, @selector(numberOfRows)); } @@ -87,7 +87,7 @@ static inline id SPMySQLResultStoreGetRow(SPMySQLStreamingResultStore* self, NSU { typedef id (*SPMSRSRowFetchMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger); static SPMSRSRowFetchMethodPtr SPMSRSRowFetch; - if (!SPMSRSRowFetch) SPMSRSRowFetch = (SPMSRSRowFetchMethodPtr)[self methodForSelector:@selector(rowContentsAtIndex:)]; + if (!SPMSRSRowFetch) SPMSRSRowFetch = (SPMSRSRowFetchMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(rowContentsAtIndex:)]; return SPMSRSRowFetch(self, @selector(rowContentsAtIndex:), rowIndex); } @@ -95,7 +95,7 @@ static inline id SPMySQLResultStoreObjectAtRowAndColumn(SPMySQLStreamingResultSt { typedef id (*SPMSRSObjectFetchMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger, NSUInteger); static SPMSRSObjectFetchMethodPtr SPMSRSObjectFetch; - if (!SPMSRSObjectFetch) SPMSRSObjectFetch = (SPMSRSObjectFetchMethodPtr)[self methodForSelector:@selector(cellDataAtRow:column:)]; + if (!SPMSRSObjectFetch) SPMSRSObjectFetch = (SPMSRSObjectFetchMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(cellDataAtRow:column:)]; return SPMSRSObjectFetch(self, @selector(cellDataAtRow:column:), rowIndex, colIndex); } @@ -103,6 +103,6 @@ static inline id SPMySQLResultStorePreviewAtRowAndColumn(SPMySQLStreamingResultS { typedef id (*SPMSRSObjectPreviewMethodPtr)(SPMySQLStreamingResultStore*, SEL, NSUInteger, NSUInteger, NSUInteger); static SPMSRSObjectPreviewMethodPtr SPMSRSObjectPreview; - if (!SPMSRSObjectPreview) SPMSRSObjectPreview = (SPMSRSObjectPreviewMethodPtr)[self methodForSelector:@selector(cellPreviewAtRow:column:previewLength:)]; + if (!SPMSRSObjectPreview) SPMSRSObjectPreview = (SPMSRSObjectPreviewMethodPtr)[SPMySQLStreamingResultStore instanceMethodForSelector:@selector(cellPreviewAtRow:column:previewLength:)]; return SPMSRSObjectPreview(self, @selector(cellPreviewAtRow:column:previewLength:), rowIndex, colIndex, previewLength); } diff --git a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m index 86a6b2b5..29f83e0e 100644 --- a/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m +++ b/Frameworks/SPMySQLFramework/Source/SPMySQLStreamingResultStore.m @@ -809,9 +809,7 @@ static inline void SPMySQLStreamingResultStoreFreeRowData(SPMySQLStreamingResult } // Update the connection's error statuses to reflect any errors during the content download - [parentConnection _updateLastErrorID:NSNotFound]; - [parentConnection _updateLastErrorMessage:nil]; - [parentConnection _updateLastSqlstate:nil]; + [parentConnection _updateLastErrorInfos]; // Unlock the parent connection now all data has been retrieved [parentConnection _unlockConnection]; |