diff --git a/README.md b/README.md index 409790a..25ff88e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SQLClient ========= -Native Microsoft SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library. +Native Microsoft SQL Server client for iOS. An Objective-C wrapper around the open-source [FreeTDS](https://github.com/FreeTDS/freetds/) library. ##Sample Usage @@ -9,26 +9,125 @@ Native Microsoft SQL Server client for iOS. An Objective-C wrapper around the op #import "SQLClient.h" SQLClient* client = [SQLClient sharedInstance]; -client.delegate = self; -[client connect:@"server:port" username:@"user" password:@"pass" database:@"db" completion:^(BOOL success) { - if (success) - { +[client connect:@"server\instance:port" username:@"user" password:@"pass" database:@"db" completion:^(BOOL success) { + if (success) { [client execute:@"SELECT * FROM Users" completion:^(NSArray* results) { - for (NSArray* table in results) - for (NSDictionary* row in table) - for (NSString* column in row) + for (NSArray* table in results) { + for (NSDictionary* row in table) { + for (NSString* column in row) { NSLog(@"%@=%@", column, row[column]); + } + } + } [client disconnect]; }]; } }]; + + +##Errors + +FreeTDS communicates both errors and messages. `SQLClient` rebroadcasts both via `NSNotificationCenter`: -//Required -- (void)error:(NSString*)error code:(int)code severity:(int)severity +``` +[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(error:) name:SQLClientErrorNotification object:nil]; +[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(message:) name:SQLClientMessageNotification object:nil]; + +- (void)error:(NSNotification*)notification { - NSLog(@"Error #%d: %@ (Severity %d)", code, error, severity); + NSNumber* code = notification.userInfo[SQLClientCodeKey]; + NSString* message = notification.userInfo[SQLClientMessageKey]; + NSNumber* severity = notification.userInfo[SQLClientSeverityKey]; + NSLog(@"Error #%@: %@ (Severity %@)", code, message, severity); } - + +- (void)message:(NSNotification*)notification +{ + NSString* message = notification.userInfo[SQLClientMessageKey]; + NSLog(@"Message: %@", message); +} +``` + +##Type Conversion +SQLClient maps SQL Server data types into the following native Objective-C types: + +* bigint → NSNumber +* binary(n) → NSData +* bit → NSNumber +* char(n) → NSString +* cursor → **not supported** +* date → **NSDate** or **NSString**† +* datetime → NSDate +* datetime2 → **NSDate** or **NSString**† +* datetimeoffset → **NSDate** or **NSString**† +* decimal(p,s) → NSNumber +* float(n) → NSNumber +* image → **NSData** +* int → NSNumber +* money → NSDecimalNumber **(last 2 digits are truncated)** +* nchar → NSString +* ntext → NSString +* null → NSNull +* numeric(p,s) → NSNumber +* nvarchar → NSString +* nvarchar(max) → NSString +* real → NSNumber +* smalldatetime → NSDate +* smallint → NSNumber +* smallmoney → NSDecimalNumber +* sql_variant → **not supported** +* table → **not supported** +* text → NSString* +* time → **NSDate** or **NSString**† +* timestamp → NSData +* tinyint → NSNumber +* uniqueidentifier → NSUUID +* varbinary → NSData +* varbinary(max) → NSData +* varchar(max) → NSString* +* varchar(n) → NSString +* xml → NSString + +\*The maximum length of a string in a query is configured on the server via the `SET TEXTSIZE` command. To find out your current setting, execute `SELECT @@TEXTSIZE`. SQLClient uses **4096** by default. To override this setting, update the `maxTextSize` property. + +†The following data types are only converted to **NSDate** on TDS version **7.3** and higher. By default FreeTDS uses version **7.1** of the TDS protocol, which converts them to **NSString**. To use a higher version of the TDS protocol, add an environment variable to Xcode named `TDSVER`. Possible values are +`4.2`, `5.0`, `7.0`, `7.1`, `7.2`, `7.3`, `7.4`, `auto`. +A value of `auto` tells FreeTDS to use an autodetection (trial-and-error) algorithm to choose the highest available protocol version. + +* date +* datetime2 +* datetimeoffset +* time + +##Testing + +The `SQLClientTests` target contains integration tests which require a connection to an instance of SQL Server. The integration tests have passed successfully on the following database servers: + +* SQL Server 7.0 (TDS 7.0) +* SQL Server 2000 (TDS 7.1) +* SQL Server 2005 (TDS 7.2) +* SQL Server 2008 (TDS 7.3) +* **TODO: add more!** + +To configure the connection for your server: + +* In Xcode, go to `Edit Scheme...` and select the `Test` scheme. +* On the `Arguments` tab, uncheck `Use the Run action's arguments and environment variables` +* Add the following environment variables for your server. The values should be the same as you pass in to the `connect:` method. + * `HOST` (`server\instance:port`) + * `DATABASE` (optional) + * `USERNAME` + * `PASSWORD` + +## Known Issues +PR's welcome! + +* **strings**: FreeTDS incorrectly returns an empty string "" for a single space " " +* **money**: FreeTDS will truncate the rightmost 2 digits. +* OSX support: [FreeTDS-iOS](https://github.com/martinrybak/FreeTDS-iOS) needs to be compiled to support OSX and Podspec updated +* No support for stored procedures with out parameters (yet) +* No support for returning number of rows changed (yet) +* Swift bindings: I welcome a PR to make the API more Swift-friendly ##Demo Project Open the Xcode project inside the **SQLClient** folder. @@ -43,7 +142,7 @@ Open the Xcode project inside the **SQLClient** folder. 1. Open a Terminal window. Update RubyGems by entering: `sudo gem update --system`. Enter your password when prompted. 2. Install CocoaPods by entering `sudo gem install cocoapods`. 3. Create a file at the root of your Xcode project folder called **Podfile**. -4. Enter the following text: `pod 'SQLClient', '~> 0.1.3'` +4. Enter the following text: `pod 'SQLClient', '~> 1.0.0'` 4. In Terminal navigate to this folder and enter `pod install`. 5. You will see a new **SQLClient.xcworkspace** file. Open this file in Xcode to work with this project from now on. @@ -70,3 +169,6 @@ https://github.com/patchhf/FreeTDS-iOS FreeTDS example code in C: http://freetds.schemamania.org/userguide/samplecode.htm + +SQL Server Logo +© Microsoft diff --git a/SQLClient.podspec b/SQLClient.podspec index 1a4a20e..b2c692d 100644 --- a/SQLClient.podspec +++ b/SQLClient.podspec @@ -1,17 +1,23 @@ Pod::Spec.new do |s| s.name = 'SQLClient' - s.version = '0.1.3' + s.version = '1.0.0' s.license = 'MIT' s.summary = 'An Objective-C wrapper around the open-source FreeTDS library' - s.homepage = 'http://htmlpreview.github.io/?https://github.com/martinrybak/SQLClient/blob/0.1.0/SQLClient/SQLClientDocs/html/index.html' + s.homepage = 'https://github.com/martinrybak/SQLClient' s.authors = { 'Martin Rybak' => 'martin.rybak@gmail.com' } s.source = { :git => 'https://github.com/martinrybak/SQLClient.git', :tag => s.version.to_s } s.source_files = 'SQLClient/SQLClient/SQLClient/*.{h,m}' - s.vendored_libraries = 'SQLClient/SQLClient/SQLClient/libfreetds.a' s.libraries = 'iconv' s.requires_arc = true s.ios.deployment_target = '7.0' + s.ios.vendored_libraries = 'SQLClient/SQLClient/SQLClient/libsybdb-ios.a' + + s.osx.deployment_target = '10.7' + s.osx.vendored_libraries = 'SQLClient/SQLClient/SQLClient/libsybdb-osx.a' + s.tvos.deployment_target = '9.0' + s.tvos.vendored_libraries = 'SQLClient/SQLClient/SQLClient/libsybdb-ios.a' + end diff --git a/SQLClient/SQLClient.xcodeproj/project.pbxproj b/SQLClient/SQLClient.xcodeproj/project.pbxproj index 2ba3a0f..95e1867 100644 --- a/SQLClient/SQLClient.xcodeproj/project.pbxproj +++ b/SQLClient/SQLClient.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ 5B0FC859180DCEB000DF4EFE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B0FC857180DCEB000DF4EFE /* InfoPlist.strings */; }; 5B0FC85B180DCEB000DF4EFE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0FC85A180DCEB000DF4EFE /* main.m */; }; 5B0FC85F180DCEB000DF4EFE /* SQLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0FC85E180DCEB000DF4EFE /* SQLAppDelegate.m */; }; - 5B0FC862180DCEB000DF4EFE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5B0FC860180DCEB000DF4EFE /* Main.storyboard */; }; 5B0FC865180DCEB000DF4EFE /* SQLViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0FC864180DCEB000DF4EFE /* SQLViewController.m */; }; 5B0FC867180DCEB000DF4EFE /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5B0FC866180DCEB000DF4EFE /* Images.xcassets */; }; 5B0FC86E180DCEB000DF4EFE /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B0FC86D180DCEB000DF4EFE /* XCTest.framework */; }; @@ -23,7 +22,7 @@ 5B0FC87A180DCEB000DF4EFE /* SQLClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0FC879180DCEB000DF4EFE /* SQLClientTests.m */; }; 5B0FC888180DCF3800DF4EFE /* libiconv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B0FC887180DCF3800DF4EFE /* libiconv.dylib */; }; 5B0FC8D9180DDF7E00DF4EFE /* SQLClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B0FC8D1180DDF7E00DF4EFE /* SQLClient.m */; }; - 5BC11BCC180EE4C9003471E4 /* libfreetds.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B0FC8CE180DDF7E00DF4EFE /* libfreetds.a */; }; + 5BC11BCC180EE4C9003471E4 /* libsybdb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B0FC8CE180DDF7E00DF4EFE /* libsybdb.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -47,7 +46,6 @@ 5B0FC85C180DCEB000DF4EFE /* SQLClient-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SQLClient-Prefix.pch"; sourceTree = ""; }; 5B0FC85D180DCEB000DF4EFE /* SQLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLAppDelegate.h; sourceTree = ""; }; 5B0FC85E180DCEB000DF4EFE /* SQLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQLAppDelegate.m; sourceTree = ""; }; - 5B0FC861180DCEB000DF4EFE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 5B0FC863180DCEB000DF4EFE /* SQLViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLViewController.h; sourceTree = ""; }; 5B0FC864180DCEB000DF4EFE /* SQLViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQLViewController.m; sourceTree = ""; }; 5B0FC866180DCEB000DF4EFE /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; @@ -62,7 +60,7 @@ 5B0FC8CB180DDF7E00DF4EFE /* cspublic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cspublic.h; sourceTree = ""; }; 5B0FC8CC180DDF7E00DF4EFE /* cstypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cstypes.h; sourceTree = ""; }; 5B0FC8CD180DDF7E00DF4EFE /* ctpublic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ctpublic.h; sourceTree = ""; }; - 5B0FC8CE180DDF7E00DF4EFE /* libfreetds.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libfreetds.a; sourceTree = ""; }; + 5B0FC8CE180DDF7E00DF4EFE /* libsybdb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libsybdb.a; sourceTree = ""; }; 5B0FC8CF180DDF7E00DF4EFE /* odbcss.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = odbcss.h; sourceTree = ""; }; 5B0FC8D0180DDF7E00DF4EFE /* SQLClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLClient.h; sourceTree = ""; }; 5B0FC8D1180DDF7E00DF4EFE /* SQLClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQLClient.m; sourceTree = ""; }; @@ -82,7 +80,7 @@ 5B0FC851180DCEB000DF4EFE /* CoreGraphics.framework in Frameworks */, 5B0FC853180DCEB000DF4EFE /* UIKit.framework in Frameworks */, 5B0FC84F180DCEB000DF4EFE /* Foundation.framework in Frameworks */, - 5BC11BCC180EE4C9003471E4 /* libfreetds.a in Frameworks */, + 5BC11BCC180EE4C9003471E4 /* libsybdb.a in Frameworks */, 5B0FC888180DCF3800DF4EFE /* libiconv.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -138,7 +136,6 @@ 5B0FC8C9180DDF7E00DF4EFE /* SQLClient */, 5B0FC85D180DCEB000DF4EFE /* SQLAppDelegate.h */, 5B0FC85E180DCEB000DF4EFE /* SQLAppDelegate.m */, - 5B0FC860180DCEB000DF4EFE /* Main.storyboard */, 5B0FC863180DCEB000DF4EFE /* SQLViewController.h */, 5B0FC864180DCEB000DF4EFE /* SQLViewController.m */, 5B0FC866180DCEB000DF4EFE /* Images.xcassets */, @@ -179,7 +176,7 @@ 5B0FC8C9180DDF7E00DF4EFE /* SQLClient */ = { isa = PBXGroup; children = ( - 5B0FC8CE180DDF7E00DF4EFE /* libfreetds.a */, + 5B0FC8CE180DDF7E00DF4EFE /* libsybdb.a */, 5B0FC8D0180DDF7E00DF4EFE /* SQLClient.h */, 5B0FC8D1180DDF7E00DF4EFE /* SQLClient.m */, 5B0FC8CA180DDF7E00DF4EFE /* bkpublic.h */, @@ -207,7 +204,6 @@ 5B0FC847180DCEB000DF4EFE /* Sources */, 5B0FC848180DCEB000DF4EFE /* Frameworks */, 5B0FC849180DCEB000DF4EFE /* Resources */, - 5B0FC8A1180DDBFE00DF4EFE /* ShellScript */, ); buildRules = ( ); @@ -243,7 +239,7 @@ isa = PBXProject; attributes = { CLASSPREFIX = SQL; - LastUpgradeCheck = 0600; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "Martin Rybak"; TargetAttributes = { 5B0FC86B180DCEB000DF4EFE = { @@ -277,7 +273,6 @@ files = ( 5B0FC867180DCEB000DF4EFE /* Images.xcassets in Resources */, 5B0FC859180DCEB000DF4EFE /* InfoPlist.strings in Resources */, - 5B0FC862180DCEB000DF4EFE /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -291,22 +286,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 5B0FC8A1180DDBFE00DF4EFE /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "#Update build number with number of git commits if in release mode\nif [ ${CONFIGURATION} == \"Release\" ]; then\nbuildNumber=$(git rev-list HEAD | wc -l | tr -d ' ')\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\nfi;\n\nif [ ${CONFIGURATION} == \"Release\" ]; then\nAPPLEDOC_PATH=`which appledoc`\nif [ $APPLEDOC_PATH ]; then\n$APPLEDOC_PATH \\\n--project-name ${PRODUCT_NAME} \\\n--project-company \"Martin Rybak\" \\\n--company-id \"com.martinrybak\" \\\n--output ${PRODUCT_NAME}Docs \\\n--keep-intermediate-files \\\n--ignore \"*.m\" \\\n--no-install-docset \\\n--no-repeat-first-par \\\n--no-warn-invalid-crossref \\\n--exit-threshold 2 \\\n${PROJECT_DIR}/${PRODUCT_NAME}\nfi;\nfi;"; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 5B0FC847180DCEB000DF4EFE /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -346,14 +325,6 @@ name = InfoPlist.strings; sourceTree = ""; }; - 5B0FC860180DCEB000DF4EFE /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 5B0FC861180DCEB000DF4EFE /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; 5B0FC876180DCEB000DF4EFE /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( @@ -378,13 +349,19 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -416,13 +393,18 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -452,6 +434,7 @@ "$(SRCROOT)/SQLClient/SQLClient", ); ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.martinrybak.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "armv7 armv7s arm64"; WRAPPER_EXTENSION = app; @@ -475,6 +458,7 @@ "$(SRCROOT)/SQLClient/SQLClient", ); ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.martinrybak.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; VALID_ARCHS = "armv7 armv7s arm64"; WRAPPER_EXTENSION = app; @@ -497,6 +481,7 @@ "$(inherited)", ); INFOPLIST_FILE = "SQLClientTests/SQLClientTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.martinrybak.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; @@ -515,6 +500,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SQLClient/SQLClient-Prefix.pch"; INFOPLIST_FILE = "SQLClientTests/SQLClientTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.martinrybak.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; diff --git a/SQLClient/SQLClient/Base.lproj/Main.storyboard b/SQLClient/SQLClient/Base.lproj/Main.storyboard deleted file mode 100644 index 6198c47..0000000 --- a/SQLClient/SQLClient/Base.lproj/Main.storyboard +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Contents.json b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Contents.json index a396706..dff5bdd 100644 --- a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,19 +1,58 @@ { "images" : [ { + "size" : "29x29", "idiom" : "iphone", + "filename" : "Icon-Small.png", + "scale" : "1x" + }, + { "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-Small@2x.png", "scale" : "2x" }, { + "size" : "29x29", "idiom" : "iphone", + "filename" : "Icon-Small@3x.png", + "scale" : "3x" + }, + { "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@2x.png", "scale" : "2x" }, { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-40@3x.png", + "scale" : "3x" + }, + { + "size" : "57x57", "idiom" : "iphone", + "filename" : "Icon.png", + "scale" : "1x" + }, + { + "size" : "57x57", + "idiom" : "iphone", + "filename" : "Icon@2x.png", + "scale" : "2x" + }, + { "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@2x.png", "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-60@3x.png", + "scale" : "3x" } ], "info" : { diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000..5435c8c Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 0000000..ee8d2ec Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png new file mode 100644 index 0000000..ee8d2ec Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png new file mode 100644 index 0000000..20ffeb2 Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small.png new file mode 100644 index 0000000..5ce9f9d Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png new file mode 100644 index 0000000..c89aee1 Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png new file mode 100644 index 0000000..e287f87 Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon.png new file mode 100644 index 0000000..7f37f0d Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon@2x.png b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon@2x.png new file mode 100644 index 0000000..ba58d7a Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/AppIcon.appiconset/Icon@2x.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/Contents.json b/SQLClient/SQLClient/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/SQLClient/SQLClient/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Contents.json b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Contents.json index c79ebd3..1274433 100644 --- a/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Contents.json +++ b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -1,19 +1,65 @@ { "images" : [ { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "736h", + "filename" : "Default-736h@3x~iphone.png", + "minimum-system-version" : "8.0", "orientation" : "portrait", + "scale" : "3x" + }, + { + "orientation" : "landscape", "idiom" : "iphone", "extent" : "full-screen", - "minimum-system-version" : "7.0", + "minimum-system-version" : "8.0", + "subtype" : "736h", + "scale" : "3x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "667h", + "filename" : "Default-667h@2x~iphone.png", + "minimum-system-version" : "8.0", + "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", - "subtype" : "retina4", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" + }, + { + "extent" : "full-screen", + "idiom" : "iphone", + "subtype" : "retina4", + "filename" : "Default-568h@2x~iphone.png", + "minimum-system-version" : "7.0", + "orientation" : "portrait", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "scale" : "1x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "extent" : "full-screen", + "subtype" : "retina4", + "scale" : "2x" } ], "info" : { diff --git a/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone.png b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone.png new file mode 100644 index 0000000..d23f51f Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-568h@2x~iphone.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-667h@2x~iphone.png b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-667h@2x~iphone.png new file mode 100644 index 0000000..8f46fa6 Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-667h@2x~iphone.png differ diff --git a/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-736h@3x~iphone.png b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-736h@3x~iphone.png new file mode 100644 index 0000000..7a49d42 Binary files /dev/null and b/SQLClient/SQLClient/Images.xcassets/LaunchImage.launchimage/Default-736h@3x~iphone.png differ diff --git a/SQLClient/SQLClient/SQLAppDelegate.h b/SQLClient/SQLClient/SQLAppDelegate.h index dc7a339..59497c0 100644 --- a/SQLClient/SQLClient/SQLAppDelegate.h +++ b/SQLClient/SQLClient/SQLAppDelegate.h @@ -10,6 +10,6 @@ @interface SQLAppDelegate : UIResponder -@property (strong, nonatomic) UIWindow *window; +@property (strong, nonatomic) UIWindow* window; @end diff --git a/SQLClient/SQLClient/SQLAppDelegate.m b/SQLClient/SQLClient/SQLAppDelegate.m index 3a4f65d..e6cc2b3 100644 --- a/SQLClient/SQLClient/SQLAppDelegate.m +++ b/SQLClient/SQLClient/SQLAppDelegate.m @@ -7,40 +7,23 @@ // #import "SQLAppDelegate.h" +#import "SQLViewController.h" @implementation SQLAppDelegate -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - // Override point for customization after application launch. + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + + //Only show SQLViewController if not testing + if (NSClassFromString(@"XCTest")) { + self.window.rootViewController = [[UIViewController alloc] init]; + } else { + self.window.rootViewController = [[SQLViewController alloc] init]; + } + + [self.window makeKeyAndVisible]; return YES; } - -- (void)applicationWillResignActive:(UIApplication *)application -{ - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. -} - -- (void)applicationDidEnterBackground:(UIApplication *)application -{ - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - -- (void)applicationWillEnterForeground:(UIApplication *)application -{ - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. -} - -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} @end diff --git a/SQLClient/SQLClient/SQLClient-Info.plist b/SQLClient/SQLClient/SQLClient-Info.plist index 493c572..cbea5ab 100644 --- a/SQLClient/SQLClient/SQLClient-Info.plist +++ b/SQLClient/SQLClient/SQLClient-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.martinrybak.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -24,8 +24,6 @@ 28 LSRequiresIPhoneOS - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7 diff --git a/SQLClient/SQLClient/SQLClient/SQLClient.h b/SQLClient/SQLClient/SQLClient/SQLClient.h index 094fdc6..4f3ff54 100644 --- a/SQLClient/SQLClient/SQLClient/SQLClient.h +++ b/SQLClient/SQLClient/SQLClient/SQLClient.h @@ -8,27 +8,11 @@ #import -@protocol SQLClientDelegate - -/** - * Required delegate method to receive error notifications - * - * @param error Error text - * @param code FreeTDS error code - * @param severity FreeTDS error severity - */ -- (void)error:(NSString*)error code:(int)code severity:(int)severity; - -@optional - -/** - * Optional delegate method to receive message notifications - * - * @param message Message text - */ -- (void)message:(NSString*)message; - -@end +extern NSString* _Nonnull const SQLClientMessageNotification; +extern NSString* _Nonnull const SQLClientErrorNotification; +extern NSString* _Nonnull const SQLClientMessageKey; +extern NSString* _Nonnull const SQLClientCodeKey; +extern NSString* _Nonnull const SQLClientSeverityKey; /** * Native SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library. @@ -36,86 +20,69 @@ @interface SQLClient : NSObject /** - * Connection timeout, in seconds. Default is 5. Override before calling connect: + * Connection timeout, in seconds. Default is 5. Set before calling connect. */ @property (nonatomic, assign) int timeout; /** - * The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) - */ -@property (nonatomic, copy, readonly) NSString* host; - -/** - * The database username - */ -@property (nonatomic, copy, readonly) NSString* username; - -/** - * The database name to use - */ -@property (nonatomic, copy, readonly) NSString* database; - -/** - * The delegate to receive error: and message: callbacks - */ -@property (nonatomic, weak) NSObject* delegate; - -/** - * The queue for database operations. By default, uses a new queue called 'com.martinrybak.sqlclient' created upon singleon intialization. Can be overridden. - */ -@property (nonatomic, strong) NSOperationQueue* workerQueue; - -/** - * The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden. + * The character set to use for converting the UCS-2 server results. Default is UTF-8. + * Set before calling connect. Can be set to any charset supported by the iconv library. + * To list all supported iconv character sets, open a Terminal window and enter: + $ iconv --list */ -@property (nonatomic, weak) NSOperationQueue* callbackQueue; +@property (nonatomic, copy, nonnull) NSString* charset; /** - * The character set to use for converting the UCS-2 server results. Default is UTF-8. - Can be overridden to any charset supported by the iconv library. - To list all supported iconv character sets, open a Terminal window and enter: - $ iconv --list + * The maximum length of a string in a query is configured on the server via the SET TEXTSIZE command. + * To find out your current setting, execute SELECT @@TEXTSIZE. SQLClient uses 4096 by default. + * To override this setting, update this property. */ -@property (nonatomic, copy) NSString* charset; +@property (atomic, assign) int maxTextSize; /** - * Returns an initialized SQLClient instance as a singleton + * Returns an initialized SQLClient instance as a singleton. * * @return Shared SQLClient object */ -+ (instancetype)sharedInstance; ++ (nullable instancetype)sharedInstance; /** - * Connects to a SQL database server + * Connects to a SQL database server. * * @param host Required. The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) * @param username Required. The database username * @param password Required. The database password - * @param database Required. The database name - * @param delegate Required. An NSObject that implements the SQLClientDelegate protocol for receiving error messages + * @param database The database name * @param completion Block to be executed upon method successful connection */ -- (void)connect:(NSString*)host - username:(NSString*)username - password:(NSString*)password - database:(NSString*)database - completion:(void (^)(BOOL success))completion; +- (void)connect:(nonnull NSString*)host + username:(nonnull NSString*)username + password:(nonnull NSString*)password + database:(nullable NSString*)database + completion:(nullable void(^)(BOOL success))completion; + +/** + * Indicates whether the database is currently connected. + */ +- (BOOL)isConnected; /** - * Indicates whether the database is currently connected + * Indicates whether the database is executing a command. */ -- (BOOL)connected; +- (BOOL)isExecuting; /** * Executes a SQL statement. Results of queries will be passed to the completion handler. Inserts, updates, and deletes do not return results. * * @param sql Required. A SQL statement - * @param completion Block to be executed upon method completion. Accepts an NSArray of tables. Each table is an NSArray of rows. Each row is an NSDictionary of columns where key = name and object = value as an NSString. + * @param completion Block to be executed upon method completion. Accepts an NSArray of tables. Each table is an NSArray of rows. + * Each row is an NSDictionary of columns where key = name and object = value as one of the following types: + * NSString, NSNumber, NSDecimalNumber, NSData, UIImage, NSDate, NSUUID */ -- (void)execute:(NSString*)sql completion:(void (^)(NSArray* results))completion; +- (void)execute:(nonnull NSString*)sql completion:(nullable void(^)(NSArray* _Nullable results))completion; /** - * Disconnects from database server + * Disconnects from database server. */ - (void)disconnect; diff --git a/SQLClient/SQLClient/SQLClient/SQLClient.m b/SQLClient/SQLClient/SQLClient/SQLClient.m index bc40805..71e0938 100644 --- a/SQLClient/SQLClient/SQLClient/SQLClient.m +++ b/SQLClient/SQLClient/SQLClient/SQLClient.m @@ -11,35 +11,54 @@ #import "sybdb.h" #import "syberror.h" +#define SYBUNIQUEIDENTIFIER 36 + int const SQLClientDefaultTimeout = 5; -int const SQLClientDefaultQueryTimeout = 5; +int const SQLClientDefaultMaxTextSize = 4096; NSString* const SQLClientDefaultCharset = @"UTF-8"; NSString* const SQLClientWorkerQueueName = @"com.martinrybak.sqlclient"; -NSString* const SQLClientDelegateError = @"Delegate must be set to an NSObject that implements the SQLClientDelegate protocol"; +NSString* const SQLClientPendingConnectionError = @"Attempting to connect while a connection is active."; +NSString* const SQLClientNoConnectionError = @"Attempting to execute while not connected."; +NSString* const SQLClientPendingExecutionError = @"Attempting to execute while a command is in progress."; NSString* const SQLClientRowIgnoreMessage = @"Ignoring unknown row type"; +NSString* const SQLClientMessageNotification = @"SQLClientMessageNotification"; +NSString* const SQLClientErrorNotification = @"SQLClientErrorNotification"; +NSString* const SQLClientMessageKey = @"SQLClientMessageKey"; +NSString* const SQLClientCodeKey = @"SQLClientCodeKey"; +NSString* const SQLClientSeverityKey = @"SQLClientSeverityKey"; + +struct GUID { + unsigned long data1; + unsigned short data2; + unsigned short data3; + unsigned char data4[8]; +}; -struct COL +struct COLUMN { char* name; - char* buffer; int type; int size; int status; + BYTE* data; }; @interface SQLClient () -@property (nonatomic, copy, readwrite) NSString* host; -@property (nonatomic, copy, readwrite) NSString* username; -@property (nonatomic, copy, readwrite) NSString* database; +@property (nonatomic, strong) NSOperationQueue* workerQueue; +@property (nonatomic, weak) NSOperationQueue* callbackQueue; +@property (atomic, assign, getter=isExecuting) BOOL executing; @end @implementation SQLClient { - LOGINREC* login; - DBPROCESS* connection; char* _password; + LOGINREC* _login; + DBPROCESS* _connection; + struct COLUMN* _columns; + int _numColumns; + RETCODE _returnCode; } #pragma mark - NSObject @@ -49,9 +68,10 @@ - (id)init { if (self = [super init]) { - //Initialize the FreeTDS library - if (dbinit() == FAIL) + //Initialize the FreeTDS library + if (dbinit() == FAIL) { return nil; + } //Initialize SQLClient self.timeout = SQLClientDefaultTimeout; @@ -59,6 +79,9 @@ - (id)init self.callbackQueue = [NSOperationQueue currentQueue]; self.workerQueue = [[NSOperationQueue alloc] init]; self.workerQueue.name = SQLClientWorkerQueueName; + self.workerQueue.maxConcurrentOperationCount = 1; + self.maxTextSize = SQLClientDefaultMaxTextSize; + self.executing = NO; //Set FreeTDS callback handlers dberrhandle(err_handler); @@ -75,7 +98,7 @@ - (void)dealloc #pragma mark - Public -+ (instancetype)sharedInstance ++ (nullable instancetype)sharedInstance { static SQLClient* sharedInstance = nil; static dispatch_once_t onceToken; @@ -85,224 +108,555 @@ + (instancetype)sharedInstance return sharedInstance; } -- (void)connect:(NSString*)host - username:(NSString*)username - password:(NSString*)password - database:(NSString*)database - completion:(void (^)(BOOL success))completion +- (void)connect:(nonnull NSString*)host + username:(nonnull NSString*)username + password:(nonnull NSString*)password + database:(nullable NSString*)database + completion:(nullable void(^)(BOOL success))completion { - //Save inputs - self.host = host; - self.username = username; - self.database = database; - - /* - Copy password into a global C string. This is because in connectionSuccess: and connectionFailure:, - dbloginfree() will attempt to overwrite the password in the login struct with zeroes for security. - So it must be a string that stays alive until then. Passing in [password UTF8String] does not work because: - - "The returned C string is a pointer to a structure inside the string object, which may have a lifetime - shorter than the string object and will certainly not have a longer lifetime. Therefore, you should - copy the C string if it needs to be stored outside of the memory context in which you called this method." - https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html#//apple_ref/occ/instm/NSString/UTF8String - */ - _password = strdup([password UTF8String]); - //Connect to database on worker queue [self.workerQueue addOperationWithBlock:^{ - //Set login timeout - dbsetlogintime(self.timeout); + if (self.isConnected) { + [self message:SQLClientPendingConnectionError]; + [self connectionFailure:completion]; + return; + } + + /* + Copy password into a global C string. This is because in cleanupAfterConnection, + dbloginfree() will attempt to overwrite the password in the login struct with zeroes for security. + So it must be a string that stays alive until then. + */ + _password = strdup([password UTF8String]); //Initialize login struct - if ((login = dblogin()) == FAIL) - return [self connectionFailure:completion]; + _login = dblogin(); + if (_login == FAIL) { + [self connectionFailure:completion]; + return; + } //Populate login struct - DBSETLUSER(login, [self.username UTF8String]); - DBSETLPWD(login, _password); - DBSETLHOST(login, [self.host UTF8String]); - DBSETLCHARSET(login, [self.charset UTF8String]); + DBSETLUSER(_login, [username UTF8String]); + DBSETLPWD(_login, _password); + DBSETLHOST(_login, [host UTF8String]); + DBSETLCHARSET(_login, [self.charset UTF8String]); + + //Set login timeout + dbsetlogintime(self.timeout); //Connect to database server - if ((connection = dbopen(login, [self.host UTF8String])) == NULL) - return [self connectionFailure:completion]; + _connection = dbopen(_login, [host UTF8String]); + if (!_connection) { + [self connectionFailure:completion]; + return; + } - //Switch to database - if (dbuse(connection, [self.database UTF8String]) == FAIL) - return [self connectionFailure:completion]; + //Switch to database, if provided + if (database) { + _returnCode = dbuse(_connection, [database UTF8String]); + if (_returnCode == FAIL) { + [self connectionFailure:completion]; + return; + } + } //Success! [self connectionSuccess:completion]; }]; } -- (BOOL)connected +- (BOOL)isConnected { - return !dbdead(connection); + return !dbdead(_connection); } -// TODO: how to get number of records changed during update or delete -// TODO: how to handle SQL stored procedure output parameters -- (void)execute:(NSString*)sql completion:(void (^)(NSArray* results))completion +// TODO: get number of records modified for insert/update/delete commands +// TODO: handle SQL stored procedure output parameters +- (void)execute:(nonnull NSString*)sql completion:(nullable void(^)(NSArray* _Nullable results))completion { //Execute query on worker queue [self.workerQueue addOperationWithBlock:^{ + if (!self.isConnected) { + [self message:SQLClientNoConnectionError]; + [self executionFailure:completion]; + return; + } + + if (self.isExecuting) { + [self message:SQLClientPendingExecutionError]; + [self executionFailure:completion]; + return; + } + + self.executing = YES; + //Set query timeout dbsettime(self.timeout); //Prepare SQL statement - dbcmd(connection, [sql UTF8String]); + _returnCode = dbcmd(_connection, [sql UTF8String]); + if (_returnCode == FAIL) { + [self executionFailure:completion]; + return; + } //Execute SQL statement - if (dbsqlexec(connection) == FAIL) - return [self executionFailure:completion]; + _returnCode = dbsqlexec(_connection); + if (_returnCode == FAIL) { + [self executionFailure:completion]; + return; + } //Create array to contain the tables - NSMutableArray* output = [[NSMutableArray alloc] init]; + NSMutableArray* output = [NSMutableArray array]; - struct COL* columns; - struct COL* pcol; - int erc; - - //Loop through each table - while ((erc = dbresults(connection)) != NO_MORE_RESULTS) + //Loop through each table metadata + //dbresults() returns SUCCEED, FAIL or, NO_MORE_RESULTS. + while ((_returnCode = dbresults(_connection)) != NO_MORE_RESULTS) { - int ncols; - int row_code; - - //Create array to contain the rows for this table - NSMutableArray* table = [[NSMutableArray alloc] init]; + if (_returnCode == FAIL) { + [self executionFailure:completion]; + return; + } //Get number of columns - ncols = dbnumcols(connection); + _numColumns = dbnumcols(_connection); + + //No columns, skip to next table + if (!_numColumns) { + continue; + } + + //Create array to contain the rows for this table + NSMutableArray* table = [NSMutableArray array]; //Allocate C-style array of COL structs - if ((columns = calloc(ncols, sizeof(struct COL))) == NULL) - return [self executionFailure:completion]; + _columns = calloc(_numColumns, sizeof(struct COLUMN)); + if (!_columns) { + [self executionFailure:completion]; + return; + } + + struct COLUMN* column; + STATUS rowCode; //Bind the column info - for (pcol = columns; pcol - columns < ncols; pcol++) + for (column = _columns; column - _columns < _numColumns; column++) { //Get column number - int c = pcol - columns + 1; + int c = column - _columns + 1; //Get column metadata - pcol->name = dbcolname(connection, c); - pcol->type = dbcoltype(connection, c); - - //For IMAGE data, we need to multiply by 2, because dbbind() will convert each byte to a hexadecimal pair. - //http://www.freetds.org/userguide/samplecode.htm#SAMPLECODE.RESULTS - if(pcol->type == SYBIMAGE){ - pcol->size = dbcollen(connection, c) * 2; - }else{ - pcol->size = dbcollen(connection, c); - } - - - //If the column is [VAR]CHAR or TEXT, we want the column's defined size, otherwise we want - //its maximum size when represented as a string, which FreeTDS's dbwillconvert() - //returns (for fixed-length datatypes). We also do not need to convert IMAGE data type - if (pcol->type != SYBCHAR && pcol->type != SYBTEXT && pcol->type != SYBIMAGE) - pcol->size = dbwillconvert(pcol->type, SYBCHAR); + column->name = dbcolname(_connection, c); + column->type = dbcoltype(_connection, c); + column->size = dbcollen(_connection, c); - //Allocate memory in the current pcol struct for a buffer - if ((pcol->buffer = calloc(1, pcol->size + 1)) == NULL) - return [self executionFailure:completion]; - - //Bind column name - erc = dbbind(connection, c, NTBSTRINGBIND, pcol->size + 1, (BYTE*)pcol->buffer); - if (erc == FAIL) - return [self executionFailure:completion]; + //Set bind type based on column type + int bindType = CHARBIND; //Default + switch (column->type) + { + case SYBBIT: + case SYBBITN: + { + bindType = BITBIND; + break; + } + case SYBINT1: + { + bindType = TINYBIND; + break; + } + case SYBINT2: + { + bindType = SMALLBIND; + break; + } + case SYBINT4: + case SYBINTN: + { + bindType = INTBIND; + break; + } + case SYBINT8: + { + bindType = BIGINTBIND; + break; + } + case SYBFLT8: + case SYBFLTN: + { + bindType = FLT8BIND; + break; + } + case SYBREAL: + { + bindType = REALBIND; + break; + } + case SYBMONEY4: + { + bindType = SMALLMONEYBIND; + break; + } + case SYBMONEY: + case SYBMONEYN: + { + bindType = MONEYBIND; + break; + } + case SYBDECIMAL: + case SYBNUMERIC: + { + //Workaround for incorrect size + bindType = CHARBIND; + column->size += 23; + break; + } + case SYBCHAR: + case SYBVARCHAR: + case SYBNVARCHAR: + case SYBTEXT: + case SYBNTEXT: + { + bindType = NTBSTRINGBIND; + column->size = MIN(column->size, self.maxTextSize); + break; + } + case SYBDATETIME: + case SYBDATETIME4: + case SYBDATETIMN: + case SYBBIGDATETIME: + case SYBBIGTIME: + { + bindType = DATETIMEBIND; + break; + } + case SYBDATE: + case SYBMSDATE: + { + bindType = DATEBIND; + break; + } + case SYBTIME: + case SYBMSTIME: + { + //Workaround for TIME data type. We have to increase the size and cast as string. + column->size += 14; + bindType = CHARBIND; + break; + } + case SYBMSDATETIMEOFFSET: + case SYBMSDATETIME2: + { + bindType = DATETIME2BIND; + break; + } + case SYBVOID: + case SYBIMAGE: + case SYBBINARY: + case SYBVARBINARY: + case SYBUNIQUEIDENTIFIER: + { + bindType = BINARYBIND; + break; + } + } - //Bind column status - erc = dbnullbind(connection, c, &pcol->status); - if (erc == FAIL) - return [self executionFailure:completion]; + //Create space for column data + column->data = calloc(1, column->size); + if (!column->data) { + [self executionFailure:completion]; + return; + } + + //Bind column data + _returnCode = dbbind(_connection, c, bindType, column->size, column->data); + if (_returnCode == FAIL) { + [self executionFailure:completion]; + return; + } - //printf("%s is type %d with value %s\n", pcol->name, pcol->type, pcol->buffer); + //Bind null value into column status + _returnCode = dbnullbind(_connection, c, &column->status); + if (_returnCode == FAIL) { + [self executionFailure:completion]; + return; + } } - //printf("\n"); - //Loop through each row - while ((row_code = dbnextrow(connection)) != NO_MORE_ROWS) + while ((rowCode = dbnextrow(_connection)) != NO_MORE_ROWS) { //Check row type - switch (row_code) + switch (rowCode) { //Regular row case REG_ROW: { //Create a new dictionary to contain the column names and vaues - NSMutableDictionary* row = [[NSMutableDictionary alloc] initWithCapacity:ncols]; + NSMutableDictionary* row = [[NSMutableDictionary alloc] initWithCapacity:_numColumns]; //Loop through each column and create an entry where dictionary[columnName] = columnValue - for (pcol = columns; pcol - columns < ncols; pcol++) + for (column = _columns; column - _columns < _numColumns; column++) { - NSString* column = [NSString stringWithUTF8String:pcol->name]; - id value; - if (pcol->status == -1) { //null value - value = [NSNull null]; - - //Converting hexadecimal buffer into UIImage - }else if (pcol ->type == SYBIMAGE){ - NSString *hexString = [[NSString stringWithUTF8String:pcol->buffer] stringByReplacingOccurrencesOfString:@" " withString:@""]; - NSMutableData *hexData = [[NSMutableData alloc] init]; - - //Converting hex string to NSData - unsigned char whole_byte; - char byte_chars[3] = {'\0','\0','\0'}; - int i; - for (i=0; i < [hexString length]/2; i++) { - byte_chars[0] = [hexString characterAtIndex:i*2]; - byte_chars[1] = [hexString characterAtIndex:i*2+1]; - whole_byte = strtol(byte_chars, NULL, 16); - [hexData appendBytes:&whole_byte length:1]; - } - value = [UIImage imageWithData:hexData]; - }else { - value = [NSString stringWithUTF8String:pcol->buffer]; + //Default to null + id value = [NSNull null]; + + //If not null, update value with column data + if (column->status != -1) + { + switch (column->type) + { + case SYBBIT: //0 or 1 + { + BOOL _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithBool:_value]; + break; + } + case SYBINT1: //Whole numbers from 0 to 255 + case SYBINT2: //Whole numbers between -32,768 and 32,767 + { + int16_t _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithShort:_value]; + break; + } + case SYBINT4: //Whole numbers between -2,147,483,648 and 2,147,483,647 + { + int32_t _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithInt:_value]; + break; + } + case SYBINT8: //Whole numbers between -9,223,372,036,854,775,808 and 9,223,372,036,854,775,807 + { + long long _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithLongLong:_value]; + break; + } + case SYBFLT8: //Floating precision number data from -1.79E+308 to 1.79E+308 + { + double _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithDouble:_value]; + break; + } + case SYBREAL: //Floating precision number data from -3.40E+38 to 3.40E+38 + { + float _value; + memcpy(&_value, column->data, sizeof _value); + value = [NSNumber numberWithFloat:_value]; + break; + } + case SYBDECIMAL: //Numbers from -10^38 +1 to 10^38 –1. + case SYBNUMERIC: + { + NSString* _value = [[NSString alloc] initWithUTF8String:(char*)column->data]; + value = [NSDecimalNumber decimalNumberWithString:_value]; + break; + } + case SYBMONEY4: //Monetary data from -214,748.3648 to 214,748.3647 + { + DBMONEY4 _value; + memcpy(&_value, column->data, sizeof _value); + NSNumber* number = @(_value.mny4); + NSDecimalNumber* decimalNumber = [NSDecimalNumber decimalNumberWithString:[number description]]; + value = [decimalNumber decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:@"10000"]]; + break; + } + case SYBMONEY: //Monetary data from -922,337,203,685,477.5808 to 922,337,203,685,477.5807 + { + BYTE* _value = calloc(20, sizeof(BYTE)); //Max string length is 20 + dbconvert(_connection, SYBMONEY, column->data, sizeof(SYBMONEY), SYBCHAR, _value, -1); + value = [NSDecimalNumber decimalNumberWithString:[NSString stringWithUTF8String:(char*)_value]]; + free(_value); + break; + } + case SYBDATETIME: //From 1/1/1753 00:00:00.000 to 12/31/9999 23:59:59.997 with an accuracy of 3.33 milliseconds + case SYBDATETIMN: + case SYBBIGDATETIME: + case SYBBIGTIME: + case SYBMSDATETIME2: //From 1/1/0001 00:00:00 to 12/31/9999 23:59:59.9999999 with an accuracy of 100 nanoseconds + case SYBMSDATETIMEOFFSET: //The same as SYBMSDATETIME2 with the addition of a time zone offset + { + DBDATEREC2 _value; + dbanydatecrack(_connection, &_value, column->type, column->data); + value = [self dateWithYear:_value.dateyear month:_value.datemonth + 1 day:_value.datedmonth hour:_value.datehour minute:_value.dateminute second:_value.datesecond nanosecond:_value.datensecond timezone:_value.datetzone]; + break; + } + case SYBDATETIME4: //From January 1, 1900 00:00 to June 6, 2079 23:59 with an accuracy of 1 minute + { + DBDATEREC _value; + dbdatecrack(_connection, &_value, (DBDATETIME*)column->data); + value = [self dateWithYear:_value.dateyear month:_value.datemonth + 1 day:_value.datedmonth hour:_value.datehour minute:_value.dateminute second:_value.datesecond]; + break; + } + case SYBMSDATE: //From January 1, 0001 to December 31, 9999 + { + DBDATEREC _value; + dbdatecrack(_connection, &_value, (DBDATETIME*)column->data); + value = [self dateWithYear:_value.dateyear month:_value.datemonth + 1 day:_value.datedmonth]; + break; + } + case SYBMSTIME: //00:00:00 to 23:59:59.9999999 with an accuracy of 100 nanoseconds + { + //Extract time components out of string since DBDATEREC conversion does not work + NSString* string = [NSString stringWithUTF8String:(char*)column->data]; + value = [self dateWithTimeString:string]; + break; + } + case SYBCHAR: + case SYBVARCHAR: + case SYBNVARCHAR: + case SYBTEXT: + case SYBNTEXT: + { + value = [NSString stringWithUTF8String:(char*)column->data]; + break; + } + case SYBUNIQUEIDENTIFIER: //https://en.wikipedia.org/wiki/Globally_unique_identifier#Binary_encoding + { + //Convert GUID to UUID + struct GUID _value; + memcpy(&_value, column->data, sizeof _value); + _value.data1 = NTOHL(_value.data1); + _value.data2 = NTOHS(_value.data2); + _value.data3 = NTOHS(_value.data3); + value = [[NSUUID alloc] initWithUUIDBytes:(const unsigned char*)&_value]; + break; + } + case SYBVOID: + case SYBIMAGE: + case SYBBINARY: + case SYBVARBINARY: + { + value = [NSData dataWithBytes:column->data length:column->size]; + break; + } + } } - //id value = [NSString stringWithUTF8String:pcol->buffer] ?: [NSNull null]; - row[column] = value; - //printf("%@=%@\n", column, value); + + NSString* columnName = [NSString stringWithUTF8String:column->name]; + row[columnName] = value; } - //Add an immutable copy to the table + //Add an immutable copy to the table [table addObject:[row copy]]; - //printf("\n"); break; } //Buffer full case BUF_FULL: - return [self executionFailure:completion]; + [self executionFailure:completion]; + return; //Error case FAIL: - return [self executionFailure:completion]; + [self executionFailure:completion]; + return; default: [self message:SQLClientRowIgnoreMessage]; + break; } } - - //Clean up - for (pcol = columns; pcol - columns < ncols; pcol++) - free(pcol->buffer); - free(columns); - + //Add immutable copy of table to output [output addObject:[table copy]]; + [self cleanupAfterTable]; } - //Success! Send an immutable copy of the results array + //Success! Send an immutable copy of the results array [self executionSuccess:completion results:[output copy]]; }]; } - (void)disconnect { - dbclose(connection); + [self.workerQueue addOperationWithBlock:^{ + [self cleanupAfterConnection]; + if (_connection) { + dbclose(_connection); + _connection = NULL; + } + }]; +} + +#pragma mark - FreeTDS Callbacks + +//Handles message callback from FreeTDS library. +int msg_handler(DBPROCESS* dbproc, DBINT msgno, int msgstate, int severity, char* msgtext, char* srvname, char* procname, int line) +{ + //Can't call self from a C function, so need to access singleton + SQLClient* self = [SQLClient sharedInstance]; + [self message:[NSString stringWithUTF8String:msgtext]]; + return INT_EXIT; +} + +//Handles error callback from FreeTDS library. +int err_handler(DBPROCESS* dbproc, int severity, int dberr, int oserr, char* dberrstr, char* oserrstr) +{ + //Can't call self from a C function, so need to access singleton + SQLClient* self = [SQLClient sharedInstance]; + [self error:[NSString stringWithUTF8String:dberrstr] code:dberr severity:severity]; + return INT_CANCEL; +} + +//Posts a SQLClientMessageNotification notification +- (void)message:(NSString*)message +{ + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:SQLClientMessageNotification object:nil userInfo:@{ SQLClientMessageKey:message }]; + }]; +} + +//Posts a SQLClientErrorNotification notification +- (void)error:(NSString*)error code:(int)code severity:(int)severity +{ + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ + [[NSNotificationCenter defaultCenter] postNotificationName:SQLClientErrorNotification object:nil userInfo:@{ + SQLClientMessageKey:error, + SQLClientCodeKey:@(code), + SQLClientSeverityKey:@(severity) + }]; + }]; +} + +#pragma mark - Cleanup + +- (void)cleanupAfterTable +{ + if (_columns) { + for (struct COLUMN* column = _columns; column - _columns < _numColumns; column++) { + if (column) { + free(column->data); + column->data = NULL; + } + } + free(_columns); + _columns = NULL; + } +} + +- (void)cleanupAfterExecution +{ + [self cleanupAfterTable]; + if (_connection) { + dbfreebuf(_connection); + } +} + +- (void)cleanupAfterConnection +{ + [self cleanupAfterExecution]; + if (_login) { + dbloginfree(_login); + _login = NULL; + } + free(_password); + _password = NULL; } #pragma mark - Private @@ -310,91 +664,110 @@ - (void)disconnect //Invokes connection completion handler on callback queue with success = NO - (void)connectionFailure:(void (^)(BOOL success))completion { - [self.callbackQueue addOperationWithBlock:^{ - if (completion) - completion(NO); - }]; - - //Cleanup - dbloginfree(login); - free(_password); + [self cleanupAfterConnection]; + [self.callbackQueue addOperationWithBlock:^{ + if (completion) { + completion(NO); + } + }]; } //Invokes connection completion handler on callback queue with success = [self connected] - (void)connectionSuccess:(void (^)(BOOL success))completion { - [self.callbackQueue addOperationWithBlock:^{ - if (completion) - completion([self connected]); - }]; - - //Cleanup - dbloginfree(login); - free(_password); + [self cleanupAfterConnection]; + [self.callbackQueue addOperationWithBlock:^{ + if (completion) { + completion([self isConnected]); + } + }]; } //Invokes execution completion handler on callback queue with results = nil - (void)executionFailure:(void (^)(NSArray* results))completion { - [self.callbackQueue addOperationWithBlock:^{ - if (completion) - completion(nil); - }]; - - //Clean up - dbfreebuf(connection); + self.executing = NO; + [self cleanupAfterExecution]; + [self.callbackQueue addOperationWithBlock:^{ + if (completion) { + completion(nil); + } + }]; } //Invokes execution completion handler on callback queue with results array - (void)executionSuccess:(void (^)(NSArray* results))completion results:(NSArray*)results { - [self.callbackQueue addOperationWithBlock:^{ - if (completion) - completion(results); - }]; - - //Clean up - dbfreebuf(connection); + self.executing = NO; + [self cleanupAfterExecution]; + [self.callbackQueue addOperationWithBlock:^{ + if (completion) { + completion(results); + } + }]; } -//Handles message callback from FreeTDS library. -int msg_handler(DBPROCESS* dbproc, DBINT msgno, int msgstate, int severity, char* msgtext, char* srvname, char* procname, int line) +#pragma mark - Date + +- (NSDate*)referenceDate { - //Can't call self from a C function, so need to access singleton - SQLClient* self = [SQLClient sharedInstance]; - [self message:[NSString stringWithUTF8String:msgtext]]; - return 0; + return [self dateWithYear:1900 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0]; } -//Handles error callback from FreeTDS library. -int err_handler(DBPROCESS* dbproc, int severity, int dberr, int oserr, char* dberrstr, char* oserrstr) +- (NSDate*)dateWithYear:(int)year month:(int)month day:(int)day { - //Can't call self from a C function, so need to access singleton - SQLClient* self = [SQLClient sharedInstance]; - [self error:[NSString stringWithUTF8String:dberrstr] code:dberr severity:severity]; - return INT_CANCEL; + return [self dateWithYear:year month:month day:day hour:0 minute:0 second:0]; } -//Forwards a message to the delegate on the callback queue if it implements -- (void)message:(NSString*)message +- (NSDate*)dateWithYear:(int)year month:(int)month day:(int)day hour:(int)hour minute:(int)minute second:(int)second { - //Invoke delegate on calling queue - [self.callbackQueue addOperationWithBlock:^{ - if ([self.delegate respondsToSelector:@selector(message:)]) - [self.delegate message:message]; - }]; + return [self dateWithYear:year month:month day:day hour:hour minute:minute second:second nanosecond:0]; } -//Forwards an error message to the delegate on the callback queue. -- (void)error:(NSString*)error code:(int)code severity:(int)severity +- (NSDate*)dateWithYear:(int)year month:(int)month day:(int)day hour:(int)hour minute:(int)minute second:(int)second nanosecond:(int)nanosecond +{ + return [self dateWithYear:year month:month day:day hour:hour minute:minute second:second nanosecond:0 timezone:0]; +} + +- (NSDate*)dateWithYear:(int)year month:(int)month day:(int)day hour:(int)hour minute:(int)minute second:(int)second nanosecond:(int)nanosecond timezone:(int)timezone +{ + NSCalendar* calendar = [NSCalendar currentCalendar]; + NSDateComponents* dateComponents = [[NSDateComponents alloc] init]; + dateComponents.year = year; + dateComponents.month = month; + dateComponents.day = day; + dateComponents.hour = hour; + dateComponents.minute = minute; + dateComponents.second = second; + dateComponents.nanosecond = nanosecond; + dateComponents.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:timezone * 60]; + return [calendar dateFromComponents:dateComponents]; +} + +- (NSDate*)dateWithTimeString:(NSString*)string { - if (!self.delegate || ![self.delegate conformsToProtocol:@protocol(SQLClientDelegate)]) - [NSException raise:SQLClientDelegateError format:nil]; + if (string.length < 30) { + return nil; + } - //Invoke delegate on callback queue - [self.callbackQueue addOperationWithBlock:^{ - [self.delegate error:error code:code severity:severity]; - }]; + NSString* time = [string substringFromIndex:string.length - 18]; + int hour = [[time substringWithRange:NSMakeRange(0, 2)] integerValue]; + int minute = [[time substringWithRange:NSMakeRange(3, 2)] integerValue]; + int second = [[time substringWithRange:NSMakeRange(6, 2)] integerValue]; + int nanosecond = [[time substringWithRange:NSMakeRange(9, 7)] integerValue]; + NSString* meridian = [time substringWithRange:NSMakeRange(16, 2)]; + + if ([meridian isEqualToString:@"AM"]) { + if (hour == 12) { + hour = 0; + } + } else { + if (hour < 12) { + hour += 12; + } + } + + return [self dateWithYear:1900 month:1 day:1 hour:hour minute:minute second:second nanosecond:nanosecond * 100 timezone:0]; } @end diff --git a/SQLClient/SQLClient/SQLClient/libfreetds.a b/SQLClient/SQLClient/SQLClient/libsybdb.a similarity index 100% rename from SQLClient/SQLClient/SQLClient/libfreetds.a rename to SQLClient/SQLClient/SQLClient/libsybdb.a diff --git a/SQLClient/SQLClient/SQLViewController.h b/SQLClient/SQLClient/SQLViewController.h index 9c3b079..0397bc4 100644 --- a/SQLClient/SQLClient/SQLViewController.h +++ b/SQLClient/SQLClient/SQLViewController.h @@ -9,6 +9,6 @@ #import #import "SQLClient.h" -@interface SQLViewController : UIViewController +@interface SQLViewController : UIViewController @end \ No newline at end of file diff --git a/SQLClient/SQLClient/SQLViewController.m b/SQLClient/SQLClient/SQLViewController.m index 3ed10b6..0d1e3e5 100644 --- a/SQLClient/SQLClient/SQLViewController.m +++ b/SQLClient/SQLClient/SQLViewController.m @@ -11,58 +11,115 @@ @interface SQLViewController () -@property (weak, nonatomic) IBOutlet UITextView* textView; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView* spinner; +@property (strong, nonatomic) UITextView* textView; +@property (strong, nonatomic) UIActivityIndicatorView* spinner; @end @implementation SQLViewController +#pragma mark - NSObject + +- (instancetype)init +{ + if (self = [super init]) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(error:) name:SQLClientErrorNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(message:) name:SQLClientMessageNotification object:nil]; + } + return self; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +#pragma mark - UIViewController + +- (void)loadView +{ + self.view = [[UIView alloc] init]; + + //Load textView + UITextView* textView = [[UITextView alloc] init]; + textView.editable = NO; + textView.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:textView]; + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[textView]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(textView)]]; + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[textView]|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(textView)]]; + self.textView = textView; + + //Load spinner + UIActivityIndicatorView* spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + spinner.hidesWhenStopped = YES; + spinner.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:spinner]; + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[spinner]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(spinner)]]; + [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[spinner]-|" options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil views:NSDictionaryOfVariableBindings(spinner)]]; + self.spinner = spinner; +} + - (void)viewDidLoad { [super viewDidLoad]; - - [self.spinner setHidesWhenStopped:YES]; - [self.spinner startAnimating]; - + [self connect]; +} + +#pragma mark - Private + +- (void)connect +{ SQLClient* client = [SQLClient sharedInstance]; - client.delegate = self; - [client connect:@"server:port" username:@"user" password:@"pass" database:@"db" completion:^(BOOL success) { - if (success) - { - [client execute:@"SELECT * FROM Users" completion:^(NSArray* results) { - [self.spinner stopAnimating]; - [self process:results]; - [client disconnect]; - }]; + [self.spinner startAnimating]; + [client connect:@"server\\instance:port" username:@"user" password:@"pass" database:@"db" completion:^(BOOL success) { + [self.spinner stopAnimating]; + if (success) { + [self execute]; } - else - [self.spinner stopAnimating]; }]; } -- (void)process:(NSArray*)data +- (void)execute +{ + SQLClient* client = [SQLClient sharedInstance]; + [self.spinner startAnimating]; + [client execute:@"SELECT * FROM Table" completion:^(NSArray* results) { + [self.spinner stopAnimating]; + [self process:results]; + [client disconnect]; + }]; +} + +- (void)process:(NSArray*)results { - NSMutableString* results = [[NSMutableString alloc] init]; - for (NSArray* table in data) - for (NSDictionary* row in table) - for (NSString* column in row) - [results appendFormat:@"\n%@=%@", column, row[column]]; - self.textView.text = results; + NSMutableString* output = [[NSMutableString alloc] init]; + for (NSArray* table in results) { + for (NSDictionary* row in table) { + for (NSString* column in row) { + [output appendFormat:@"\n%@=%@", column, row[column]]; + } + } + } + self.textView.text = output; } -#pragma mark - SQLClientDelegate +#pragma mark - SQLClientErrorNotification -//Required -- (void)error:(NSString*)error code:(int)code severity:(int)severity +- (void)error:(NSNotification*)notification { - NSLog(@"Error #%d: %@ (Severity %d)", code, error, severity); - [[[UIAlertView alloc] initWithTitle:@"Error" message:error delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; + NSNumber* code = notification.userInfo[SQLClientCodeKey]; + NSString* message = notification.userInfo[SQLClientMessageKey]; + NSNumber* severity = notification.userInfo[SQLClientSeverityKey]; + + NSLog(@"Error #%@: %@ (Severity %@)", code, message, severity); + [[[UIAlertView alloc] initWithTitle:@"Error" message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] show]; } -//Optional -- (void)message:(NSString*)message +#pragma mark - SQLClientMessageNotification + +- (void)message:(NSNotification*)notification { + NSString* message = notification.userInfo[SQLClientMessageKey]; NSLog(@"Message: %@", message); } diff --git a/SQLClient/SQLClientDocs/docset/Contents/Info.plist b/SQLClient/SQLClientDocs/docset/Contents/Info.plist deleted file mode 100644 index b99610e..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Info.plist +++ /dev/null @@ -1,34 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleIdentifier - com.martinrybak.SQLClient - CFBundleName - SQLClient Documentation - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1.0 - - - - - DocSetFeedName - SQLClient Documentation - - DocSetMinimumXcodeVersion - 3.0 - - DashDocSetFamily - appledoc - DocSetPublisherIdentifier - com.martinrybak.documentation - DocSetPublisherName - Martin Rybak - NSHumanReadableCopyright - Copyright © 2014 Martin Rybak. All rights reserved. - - diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Classes/SQLClient.html b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Classes/SQLClient.html deleted file mode 100644 index cfac23d..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Classes/SQLClient.html +++ /dev/null @@ -1,944 +0,0 @@ - - - - - SQLClient Class Reference - - - - - - -
- - - - -
- -
-
- - - -
- -
- - - - - - - -
Inherits fromNSObject
Declared inSQLClient.h
- - - - -
- -

Overview

-

Native SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library.

-
- - - - - -
- -

Tasks

- - - - - - - -
- - - - - -
- -

Properties

- -
- -

callbackQueue

- - - -
-

The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden.

-
- - - -
@property (nonatomic, weak) NSOperationQueue *callbackQueue
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

charset

- - - -
-
    -
  • The character set to use for converting the UCS-2 server results. Default is UTF-8. -Can be overridden to any charset supported by the iconv library. -To list all supported iconv character sets, open a Terminal window and enter: -$ iconv –list
  • -
- -
- - - -
@property (nonatomic, copy) NSString *charset
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

database

- - - -
-

The database name to use

-
- - - -
@property (nonatomic, copy, readonly) NSString *database
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

delegate

- - - -
-

The delegate to receive error: and message: callbacks

-
- - - -
@property (nonatomic, weak) NSObject<SQLClientDelegate> *delegate
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

host

- - - -
-

The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash)

-
- - - -
@property (nonatomic, copy, readonly) NSString *host
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

timeout

- - - -
-

Connection timeout, in seconds. Default is 5. Override before calling connect:

-
- - - -
@property (nonatomic, assign) int timeout
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

username

- - - -
-

The database username

-
- - - -
@property (nonatomic, copy, readonly) NSString *username
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

workerQueue

- - - -
-

The queue for database operations. By default, uses a new queue called ‘com.martinrybak.sqlclient’ created upon singleon intialization. Can be overridden.

-
- - - -
@property (nonatomic, strong) NSOperationQueue *workerQueue
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - -
- -

Class Methods

- -
- -

sharedInstance

- - - -
-

Returns an initialized SQLClient instance as a singleton

-
- - - -
+ (id)sharedInstance
- - - - - -
-

Return Value

-

Shared SQLClient object

-
- - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - -
- -

Instance Methods

- -
- -

connect:username:password:database:completion:

- - - -
-

Connects to a SQL database server

-
- - - -
- (void)connect:(NSString *)host username:(NSString *)username password:(NSString *)password database:(NSString *)database completion:(void ( ^ ) ( BOOL success ))completion
- - - -
-

Parameters

- -
-
host
-

Required. The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash)

-
- -
-
username
-

Required. The database username

-
- -
-
password
-

Required. The database password

-
- -
-
database
-

Required. The database name

-
- -
-
completion
-

Block to be executed upon method successful connection

-
- -
-
delegate
-

Required. An NSObject that implements the SQLClientDelegate protocol for receiving error messages

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

connected

- - - -
-

Indicates whether the database is currently connected

-
- - - -
- (BOOL)connected
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

disconnect

- - - -
-

Disconnects from database server

-
- - - -
- (void)disconnect
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

execute:completion:

- - - -
-

Executes a SQL statement. Results of queries will be passed to the completion handler. Inserts, updates, and deletes do not return results.

-
- - - -
- (void)execute:(NSString *)sql completion:(void ( ^ ) ( NSArray *results ))completion
- - - -
-

Parameters

- -
-
sql
-

Required. A SQL statement

-
- -
-
completion
-

Block to be executed upon method completion. Accepts an NSArray of tables. Each table is an NSArray of rows. Each row is an NSDictionary of columns where key = name and object = value as an NSString.

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - - -
- - -
-
- - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Protocols/SQLClientDelegate.html b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Protocols/SQLClientDelegate.html deleted file mode 100644 index 802ee87..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/Protocols/SQLClientDelegate.html +++ /dev/null @@ -1,369 +0,0 @@ - - - - - SQLClientDelegate Protocol Reference - - - - - - -
- - - - -
- -
-
- - - -
- -
- - - - - - - -
Conforms toNSObject
Declared inSQLClient.h
- - - - - - -
- -

Tasks

- - - - - - - -
- - - - - - - - - -
- -

Instance Methods

- -
- -

error:code:severity:

- - - -
-

Required delegate method to receive error notifications

-
- - - -
- (void)error:(NSString *)error code:(int)code severity:(int)severity
- - - -
-

Parameters

- -
-
error
-

Error text

-
- -
-
code
-

FreeTDS error code

-
- -
-
severity
-

FreeTDS error severity

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

message:

- - - -
-

Optional delegate method to receive message notifications

-
- - - -
- (void)message:(NSString *)message
- - - -
-

Parameters

- -
-
message
-

Message text

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - - -
- - -
-
- - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/styles.css b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/styles.css deleted file mode 100644 index 3308189..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/styles.css +++ /dev/null @@ -1,615 +0,0 @@ -body { - font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; - font-size: 13px; -} - -code { - font-family: Courier, Consolas, monospace; - font-size: 13px; - color: #666; -} - -pre { - font-family: Courier, Consolas, monospace; - font-size: 13px; - line-height: 18px; - tab-interval: 0.5em; - border: 1px solid #C7CFD5; - background-color: #F1F5F9; - color: #666; - padding: 0.3em 1em; -} - -ul { - list-style-type: square; -} - -li { - margin-bottom: 10px; -} - -a, a code { - text-decoration: none; - color: #36C; -} - -a:hover, a:hover code { - text-decoration: underline; - color: #36C; -} - -h2 { - border-bottom: 1px solid #8391A8; - color: #3C4C6C; - font-size: 187%; - font-weight: normal; - margin-top: 1.75em; - padding-bottom: 2px; -} - -table { - margin-bottom: 4em; - border-collapse:collapse; - vertical-align: middle; -} - -td { - border: 1px solid #9BB3CD; - padding: .667em; - font-size: 100%; -} - -th { - border: 1px solid #9BB3CD; - padding: .3em .667em .3em .667em; - background: #93A5BB; - font-size: 103%; - font-weight: bold; - color: white; - text-align: left; -} - -/* @group Common page elements */ - -#top_header { - height: 91px; - left: 0; - min-width: 598px; - position: absolute; - right: 0; - top: 0; - z-index: 900; -} - -#footer { - clear: both; - padding-top: 20px; - text-align: center; -} - -#contents, #overview_contents { - -webkit-overflow-scrolling: touch; - border-top: 1px solid #A9A9A9; - position: absolute; - top: 90px; - left: 0; - right: 0; - bottom: 0; - overflow-x: hidden; - overflow-y: auto; - padding-left: 2em; - padding-right: 2em; - padding-top: 1em; - min-width: 550px; -} - -#contents.isShowingTOC { - left: 230px; - min-width: 320px; -} - -.copyright { - font-size: 12px; -} - -.generator { - font-size: 11px; -} - -.main-navigation ul li { - display: inline; - margin-left: 15px; - list-style: none; -} - -.navigation-top { - clear: both; - float: right; -} - -.navigation-bottom { - clear: both; - float: right; - margin-top: 20px; - margin-bottom: -10px; -} - -.open > .disclosure { - background-image: url("../img/disclosure_open.png"); -} - -.disclosure { - background: url("../img/disclosure.png") no-repeat scroll 0 0; -} - -.disclosure, .nodisclosure { - display: inline-block; - height: 8px; - margin-right: 5px; - position: relative; - width: 9px; -} - -/* @end */ - -/* @group Header */ - -#top_header #library { - background: url("../img/library_background.png") repeat-x 0 0 #485E78; - background-color: #ccc; - height: 35px; - font-size: 115%; -} - -#top_header #library #libraryTitle { - color: #FFFFFF; - margin-left: 15px; - text-shadow: 0 -1px 0 #485E78; - top: 8px; - position: absolute; -} - -#libraryTitle { - left: 0; -} - -#top_header #library #developerHome { - color: #92979E; - right: 15px; - top: 8px; - position: absolute; -} - -#top_header #library a:hover { - text-decoration: none; -} - -#top_header #title { - background: url("../img/title_background.png") repeat-x 0 0 #8A98A9; - border-bottom: 1px solid #757575; - height: 25px; - overflow: hidden; -} - -#top_header h1 { - font-size: 105%; - font-weight: normal; - margin: 0; - padding: 3px 0 2px; - text-align: center; - /*text-shadow: 0 1px 0 #D5D5D5;*/ - white-space: nowrap; -} - -#headerButtons { - background-color: #D8D8D8; - background-image: url("../img/button_bar_background.png"); - border-bottom: 0px solid #EDEDED; - border-top: 0px solid #a8a8a8; - font-size: 8pt; - height: 28px; - left: 0; - list-style: none outside none; - margin: 0; - overflow: hidden; - padding: 0; - position: absolute; - right: 0; - top: 61px; -} - -#headerButtons li { - background-repeat: no-repeat; - display: inline; - margin-top: 0; - margin-bottom: 0; - padding: 0; -} - -#toc_button button { - background-color: #EBEEF1; - border-color: #ACACAC; - border-style: none solid none none; - border-width: 0 1px 0 0; - height: 28px; - margin: 0; - padding-left: 30px; - text-align: left; - width: 230px; -} - -li#jumpto_button { - left: 230px; - margin-left: 0; - position: absolute; -} - -li#jumpto_button select { - height: 22px; - margin: 5px 2px 0 10px; - max-width: 300px; -} - -/* @end */ - -/* @group Table of contents */ - -#tocContainer.isShowingTOC { - border-right: 1px solid #ACACAC; - display: block; - overflow-x: hidden; - overflow-y: auto; - padding: 0; -} - -#tocContainer { - background-color: #EBEEF1; - border-top: 1px solid #ACACAC; - bottom: 0; - display: none; - left: 0; - overflow: hidden; - position: absolute; - top: 90px; - width: 229px; -} - -#tocContainer > ul#toc { - font-size: 11px; - margin: 0; - padding: 12px 0 18px; - width: 209px; - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; -} - -#tocContainer > ul#toc > li { - margin: 0; - padding: 0 0 7px 30px; - text-indent: -15px; -} - -#tocContainer > ul#toc > li > .sectionName a { - color: #000000; - font-weight: bold; -} - -#tocContainer > ul#toc > li > .sectionName a:hover { - text-decoration: none; -} - -#tocContainer > ul#toc li.children > ul { - display: none; - height: 0; -} - -#tocContainer > ul#toc > li > ul { - margin: 0; - padding: 0; -} - -#tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { - margin-left: 0; - margin-bottom: 0; - padding-left: 15px; -} - -#tocContainer > ul#toc > li ul { - list-style: none; - margin-right: 0; - padding-right: 0; -} - -#tocContainer > ul#toc li.children.open > ul { - display: block; - height: auto; - margin-left: -15px; - padding-left: 0; -} - -#tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { - margin-left: 0; - padding-left: 15px; -} - -#tocContainer li ul li { - margin-top: 0.583em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#tocContainer li ul li span.sectionName { - white-space: normal; -} - -#tocContainer > ul#toc > li > ul > li > .sectionName a { - font-weight: bold; -} - -#tocContainer > ul#toc > li > ul a { - color: #4F4F4F; -} - -/* @end */ - -/* @group Index formatting */ - -.index-title { - font-size: 13px; - font-weight: normal; -} - -.index-column { - float: left; - width: 30%; - min-width: 200px; - font-size: 11px; -} - -.index-column ul { - margin: 8px 0 0 0; - padding: 0; - list-style: none; -} - -.index-column ul li { - margin: 0 0 3px 0; - padding: 0; -} - -.hierarchy-column { - min-width: 400px; -} - -.hierarchy-column ul { - margin: 3px 0 0 15px; -} - -.hierarchy-column ul li { - list-style-type: square; -} - -/* @end */ - -/* @group Common formatting elements */ - -.title { - font-weight: normal; - font-size: 215%; - margin-top:0; -} - -.subtitle { - font-weight: normal; - font-size: 180%; - color: #3C4C6C; - border-bottom: 1px solid #5088C5; -} - -.subsubtitle { - font-weight: normal; - font-size: 145%; - height: 0.7em; -} - -.note { - border: 1px solid #5088C5; - background-color: white; - margin: 1.667em 0 1.75em 0; - padding: 0 .667em .083em .750em; -} - -.warning { - border: 1px solid #5088C5; - background-color: #F0F3F7; - margin-bottom: 0.5em; - padding: 0.3em 0.8em; -} - -.bug { - border: 1px solid #000; - background-color: #ffffcc; - margin-bottom: 0.5em; - padding: 0.3em 0.8em; -} - -.deprecated { - color: #F60425; -} - -/* @end */ - -/* @group Common layout */ - -.section { - margin-top: 3em; -} - -/* @end */ - -/* @group Object specification section */ - -.section-specification { - margin-left: 2.5em; - margin-right: 2.5em; - font-size: 12px; -} - -.section-specification table { - margin-bottom: 0em; - border-top: 1px solid #d6e0e5; -} - -.section-specification td { - vertical-align: top; - border-bottom: 1px solid #d6e0e5; - border-left-width: 0px; - border-right-width: 0px; - border-top-width: 0px; - padding: .6em; -} - -.section-specification .specification-title { - font-weight: bold; -} - -/* @end */ - -/* @group Tasks section */ - -.task-list { - list-style-type: none; - padding-left: 0px; -} - -.task-list li { - margin-bottom: 3px; -} - -.task-item-suffix { - color: #996; - font-size: 12px; - font-style: italic; - margin-left: 0.5em; -} - -span.tooltip span.tooltip { - font-size: 1.0em; - display: none; - padding: 0.3em; - border: 1px solid #aaa; - background-color: #fdfec8; - color: #000; - text-align: left; -} - -span.tooltip:hover span.tooltip { - display: block; - position: absolute; - margin-left: 2em; -} - -/* @end */ - -/* @group Method section */ - -.section-method { - margin-top: 2.3em; -} - -.method-title { - margin-bottom: 1.5em; -} - -.method-subtitle { - margin-top: 0.7em; - margin-bottom: 0.2em; -} - -.method-subsection p { - margin-top: 0.4em; - margin-bottom: 0.8em; -} - -.method-declaration { - margin-top:1.182em; - margin-bottom:.909em; -} - -.method-declaration code { - font:14px Courier, Consolas, monospace; - color:#000; -} - -.declaration { - color: #000; -} - -.termdef { - margin-bottom: 10px; - margin-left: 0px; - margin-right: 0px; - margin-top: 0px; -} - -.termdef dt { - margin: 0; - padding: 0; -} - -.termdef dd { - margin-bottom: 6px; - margin-left: 16px; - margin-right: 0px; - margin-top: 1px; -} - -.termdef dd p { - margin-bottom: 6px; - margin-left: 0px; - margin-right: 0px; - margin-top: -1px; -} - -.argument-def { - margin-top: 0.3em; - margin-bottom: 0.3em; -} - -.argument-def dd { - margin-left: 1.25em; -} - -.see-also-section ul { - list-style-type: none; - padding-left: 0px; - margin-top: 0; -} - -.see-also-section li { - margin-bottom: 3px; -} - -.declared-in-ref { - color: #666; -} - -#tocContainer.hideInXcode { - display: none; - border: 0px solid black; -} - -#top_header.hideInXcode { - display: none; -} - -#contents.hideInXcode { - border: 0px solid black; - top: 0px; - left: 0px; -} - -/* @end */ - diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/stylesPrint.css b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/stylesPrint.css deleted file mode 100644 index dc54cd2..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/css/stylesPrint.css +++ /dev/null @@ -1,22 +0,0 @@ - -header { - display: none; -} - -div.main-navigation, div.navigation-top { - display: none; -} - -div#overview_contents, div#contents.isShowingTOC, div#contents { - overflow: visible; - position: relative; - top: 0px; - border: none; - left: 0; -} -#tocContainer.isShowingTOC { - display: none; -} -nav { - display: none; -} \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/hierarchy.html b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/hierarchy.html deleted file mode 100644 index 5b6a6d3..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/hierarchy.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - SQLClient Hierarchy - - - - - -
- - - - -
-
-
- - -
- -
-

Class Hierarchy

- - - -
- - - -
- -

Protocol References

- - - - -
- -
- - -
-
- - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/button_bar_background.png b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/button_bar_background.png deleted file mode 100644 index 71d1019..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/button_bar_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure.png b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure.png deleted file mode 100644 index 4c5cbf4..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure_open.png b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure_open.png deleted file mode 100644 index 82396fe..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/disclosure_open.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/library_background.png b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/library_background.png deleted file mode 100644 index 3006248..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/library_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/title_background.png b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/title_background.png deleted file mode 100644 index 846e496..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/img/title_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/index.html b/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/index.html deleted file mode 100644 index 9006da1..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Documents/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - SQLClient Reference - - - - - -
- - - - -
-
-
- - -
- - - - - -
-

Class References

- -
- - - -
- -

Protocol References

- - - - -
- -
- - -
-
- - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Nodes.xml b/SQLClient/SQLClientDocs/docset/Contents/Resources/Nodes.xml deleted file mode 100644 index 96c6fd6..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Nodes.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - SQLClient - index.html - - - - - Classes - index.html - - - - - - - - - - Protocols - index.html - - - - - - - - - - - - - - SQLClient - Classes/SQLClient.html - - - - Classes/SQLClient.html - Overview - overview - - - Classes/SQLClient.html - Tasks - tasks - - - - Classes/SQLClient.html - Properties - properties - - - - - Classes/SQLClient.html - Class Methods - class_methods - - - - - Classes/SQLClient.html - Instance Methods - instance_methods - - - - - - - - - SQLClientDelegate - Protocols/SQLClientDelegate.html - - - - Protocols/SQLClientDelegate.html - Overview - overview - - - Protocols/SQLClientDelegate.html - Tasks - tasks - - - - - - Protocols/SQLClientDelegate.html - Instance Methods - instance_methods - - - - - - - - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens1.xml b/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens1.xml deleted file mode 100644 index 5e2f022..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens1.xml +++ /dev/null @@ -1,402 +0,0 @@ - - - - - - //apple_ref/occ/cl/SQLClient - Native SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library. - SQLClient.h - - - - - - - - //apple_ref/occ/instm/SQLClient/setTimeout: - Connection timeout, in seconds. Default is 5. Override before calling connect: - SQLClient.h - - @property (nonatomic, assign) int timeout - - - //api/name/timeout - - - - - //apple_ref/occ/instm/SQLClient/timeout - Connection timeout, in seconds. Default is 5. Override before calling connect: - SQLClient.h - - @property (nonatomic, assign) int timeout - - - //api/name/timeout - - - - - //apple_ref/occ/instp/SQLClient/timeout - Connection timeout, in seconds. Default is 5. Override before calling connect: - SQLClient.h - - @property (nonatomic, assign) int timeout - - - //api/name/timeout - - - - - //apple_ref/occ/instm/SQLClient/setHost: - The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *host - - - //api/name/host - - - - - //apple_ref/occ/instm/SQLClient/host - The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *host - - - //api/name/host - - - - - //apple_ref/occ/instp/SQLClient/host - The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *host - - - //api/name/host - - - - - //apple_ref/occ/instm/SQLClient/setUsername: - The database username - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *username - - - //api/name/username - - - - - //apple_ref/occ/instm/SQLClient/username - The database username - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *username - - - //api/name/username - - - - - //apple_ref/occ/instp/SQLClient/username - The database username - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *username - - - //api/name/username - - - - - //apple_ref/occ/instm/SQLClient/setDatabase: - The database name to use - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *database - - - //api/name/database - - - - - //apple_ref/occ/instm/SQLClient/database - The database name to use - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *database - - - //api/name/database - - - - - //apple_ref/occ/instp/SQLClient/database - The database name to use - SQLClient.h - - @property (nonatomic, copy, readonly) NSString *database - - - //api/name/database - - - - - //apple_ref/occ/instm/SQLClient/setDelegate: - The delegate to receive error: and message: callbacks - SQLClient.h - - @property (nonatomic, weak) NSObject<SQLClientDelegate> *delegate - - - //api/name/delegate - - - - - //apple_ref/occ/instm/SQLClient/delegate - The delegate to receive error: and message: callbacks - SQLClient.h - - @property (nonatomic, weak) NSObject<SQLClientDelegate> *delegate - - - //api/name/delegate - - - - - //apple_ref/occ/instp/SQLClient/delegate - The delegate to receive error: and message: callbacks - SQLClient.h - - @property (nonatomic, weak) NSObject<SQLClientDelegate> *delegate - - - //api/name/delegate - - - - - //apple_ref/occ/instm/SQLClient/setWorkerQueue: - The queue for database operations. By default, uses a new queue called 'com.martinrybak.sqlclient' created upon singleon intialization. Can be overridden. - SQLClient.h - - @property (nonatomic, strong) NSOperationQueue *workerQueue - - - //api/name/workerQueue - - - - - //apple_ref/occ/instm/SQLClient/workerQueue - The queue for database operations. By default, uses a new queue called 'com.martinrybak.sqlclient' created upon singleon intialization. Can be overridden. - SQLClient.h - - @property (nonatomic, strong) NSOperationQueue *workerQueue - - - //api/name/workerQueue - - - - - //apple_ref/occ/instp/SQLClient/workerQueue - The queue for database operations. By default, uses a new queue called 'com.martinrybak.sqlclient' created upon singleon intialization. Can be overridden. - SQLClient.h - - @property (nonatomic, strong) NSOperationQueue *workerQueue - - - //api/name/workerQueue - - - - - //apple_ref/occ/instm/SQLClient/setCallbackQueue: - The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden. - SQLClient.h - - @property (nonatomic, weak) NSOperationQueue *callbackQueue - - - //api/name/callbackQueue - - - - - //apple_ref/occ/instm/SQLClient/callbackQueue - The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden. - SQLClient.h - - @property (nonatomic, weak) NSOperationQueue *callbackQueue - - - //api/name/callbackQueue - - - - - //apple_ref/occ/instp/SQLClient/callbackQueue - The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden. - SQLClient.h - - @property (nonatomic, weak) NSOperationQueue *callbackQueue - - - //api/name/callbackQueue - - - - - //apple_ref/occ/instm/SQLClient/setCharset: - * The character set to use for converting the UCS-2 server results. Default is UTF-8. -Can be overridden to any charset supported by the iconv library. -To list all supported iconv character sets, open a Terminal window and enter: -$ iconv --list - SQLClient.h - - @property (nonatomic, copy) NSString *charset - - - //api/name/charset - - - - - //apple_ref/occ/instm/SQLClient/charset - * The character set to use for converting the UCS-2 server results. Default is UTF-8. -Can be overridden to any charset supported by the iconv library. -To list all supported iconv character sets, open a Terminal window and enter: -$ iconv --list - SQLClient.h - - @property (nonatomic, copy) NSString *charset - - - //api/name/charset - - - - - //apple_ref/occ/instp/SQLClient/charset - * The character set to use for converting the UCS-2 server results. Default is UTF-8. -Can be overridden to any charset supported by the iconv library. -To list all supported iconv character sets, open a Terminal window and enter: -$ iconv --list - SQLClient.h - - @property (nonatomic, copy) NSString *charset - - - //api/name/charset - - - - - //apple_ref/occ/clm/SQLClient/sharedInstance - Returns an initialized SQLClient instance as a singleton - SQLClient.h - - + (id)sharedInstance - - Shared SQLClient object - //api/name/sharedInstance - - - - - //apple_ref/occ/instm/SQLClient/connect:username:password:database:completion: - Connects to a SQL database server - SQLClient.h - - - (void)connect:(NSString *)host username:(NSString *)username password:(NSString *)password database:(NSString *)database completion:(void ( ^ ) ( BOOL success ))completion - - - host - Required. The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash) - - username - Required. The database username - - password - Required. The database password - - database - Required. The database name - - completion - Block to be executed upon method successful connection - - delegate - Required. An NSObject that implements the SQLClientDelegate protocol for receiving error messages - - - - //api/name/connect:username:password:database:completion: - - - - - //apple_ref/occ/instm/SQLClient/connected - Indicates whether the database is currently connected - SQLClient.h - - - (BOOL)connected - - - //api/name/connected - - - - - //apple_ref/occ/instm/SQLClient/execute:completion: - Executes a SQL statement. Results of queries will be passed to the completion handler. Inserts, updates, and deletes do not return results. - SQLClient.h - - - (void)execute:(NSString *)sql completion:(void ( ^ ) ( NSArray *results ))completion - - - sql - Required. A SQL statement - - completion - Block to be executed upon method completion. Accepts an NSArray of tables. Each table is an NSArray of rows. Each row is an NSDictionary of columns where key = name and object = value as an NSString. - - - - //api/name/execute:completion: - - - - - //apple_ref/occ/instm/SQLClient/disconnect - Disconnects from database server - SQLClient.h - - - (void)disconnect - - - //api/name/disconnect - - - - - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens2.xml b/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens2.xml deleted file mode 100644 index 4854111..0000000 --- a/SQLClient/SQLClientDocs/docset/Contents/Resources/Tokens2.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - //apple_ref/occ/intf/SQLClientDelegate - - SQLClient.h - - - - - - - - //apple_ref/occ/intfm/SQLClientDelegate/error:code:severity: - Required delegate method to receive error notifications - SQLClient.h - - - (void)error:(NSString *)error code:(int)code severity:(int)severity - - - error - Error text - - code - FreeTDS error code - - severity - FreeTDS error severity - - - - //api/name/error:code:severity: - - - - - //apple_ref/occ/intfm/SQLClientDelegate/message: - Optional delegate method to receive message notifications - SQLClient.h - - - (void)message:(NSString *)message - - - message - Message text - - - - //api/name/message: - - - - - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.dsidx b/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.dsidx deleted file mode 100644 index 67dc0eb..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.dsidx and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.mom b/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.mom deleted file mode 100644 index ae97502..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.mom and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.skidx b/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.skidx deleted file mode 100644 index 962de0f..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.skidx and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.toc b/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.toc deleted file mode 100644 index 0cd9010..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.toc and /dev/null differ diff --git a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.tokencache b/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.tokencache deleted file mode 100644 index b463d90..0000000 Binary files a/SQLClient/SQLClientDocs/docset/Contents/Resources/docSet.tokencache and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/Classes/SQLClient.html b/SQLClient/SQLClientDocs/html/Classes/SQLClient.html deleted file mode 100644 index cfac23d..0000000 --- a/SQLClient/SQLClientDocs/html/Classes/SQLClient.html +++ /dev/null @@ -1,944 +0,0 @@ - - - - - SQLClient Class Reference - - - - - - -
- - - - -
- -
-
- - - -
- -
- - - - - - - -
Inherits fromNSObject
Declared inSQLClient.h
- - - - -
- -

Overview

-

Native SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library.

-
- - - - - -
- -

Tasks

- - - - - - - -
- - - - - -
- -

Properties

- -
- -

callbackQueue

- - - -
-

The queue for block callbacks. By default, uses the current queue upon singleton initialization. Can be overridden.

-
- - - -
@property (nonatomic, weak) NSOperationQueue *callbackQueue
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

charset

- - - -
-
    -
  • The character set to use for converting the UCS-2 server results. Default is UTF-8. -Can be overridden to any charset supported by the iconv library. -To list all supported iconv character sets, open a Terminal window and enter: -$ iconv –list
  • -
- -
- - - -
@property (nonatomic, copy) NSString *charset
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

database

- - - -
-

The database name to use

-
- - - -
@property (nonatomic, copy, readonly) NSString *database
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

delegate

- - - -
-

The delegate to receive error: and message: callbacks

-
- - - -
@property (nonatomic, weak) NSObject<SQLClientDelegate> *delegate
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

host

- - - -
-

The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash)

-
- - - -
@property (nonatomic, copy, readonly) NSString *host
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

timeout

- - - -
-

Connection timeout, in seconds. Default is 5. Override before calling connect:

-
- - - -
@property (nonatomic, assign) int timeout
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

username

- - - -
-

The database username

-
- - - -
@property (nonatomic, copy, readonly) NSString *username
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

workerQueue

- - - -
-

The queue for database operations. By default, uses a new queue called ‘com.martinrybak.sqlclient’ created upon singleon intialization. Can be overridden.

-
- - - -
@property (nonatomic, strong) NSOperationQueue *workerQueue
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - -
- -

Class Methods

- -
- -

sharedInstance

- - - -
-

Returns an initialized SQLClient instance as a singleton

-
- - - -
+ (id)sharedInstance
- - - - - -
-

Return Value

-

Shared SQLClient object

-
- - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - -
- -

Instance Methods

- -
- -

connect:username:password:database:completion:

- - - -
-

Connects to a SQL database server

-
- - - -
- (void)connect:(NSString *)host username:(NSString *)username password:(NSString *)password database:(NSString *)database completion:(void ( ^ ) ( BOOL success ))completion
- - - -
-

Parameters

- -
-
host
-

Required. The database server, i.e. server, server:port, or server\instance (be sure to escape the backslash)

-
- -
-
username
-

Required. The database username

-
- -
-
password
-

Required. The database password

-
- -
-
database
-

Required. The database name

-
- -
-
completion
-

Block to be executed upon method successful connection

-
- -
-
delegate
-

Required. An NSObject that implements the SQLClientDelegate protocol for receiving error messages

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

connected

- - - -
-

Indicates whether the database is currently connected

-
- - - -
- (BOOL)connected
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

disconnect

- - - -
-

Disconnects from database server

-
- - - -
- (void)disconnect
- - - - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

execute:completion:

- - - -
-

Executes a SQL statement. Results of queries will be passed to the completion handler. Inserts, updates, and deletes do not return results.

-
- - - -
- (void)execute:(NSString *)sql completion:(void ( ^ ) ( NSArray *results ))completion
- - - -
-

Parameters

- -
-
sql
-

Required. A SQL statement

-
- -
-
completion
-

Block to be executed upon method completion. Accepts an NSArray of tables. Each table is an NSArray of rows. Each row is an NSDictionary of columns where key = name and object = value as an NSString.

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - - -
- - -
-
- - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/html/Protocols/SQLClientDelegate.html b/SQLClient/SQLClientDocs/html/Protocols/SQLClientDelegate.html deleted file mode 100644 index 802ee87..0000000 --- a/SQLClient/SQLClientDocs/html/Protocols/SQLClientDelegate.html +++ /dev/null @@ -1,369 +0,0 @@ - - - - - SQLClientDelegate Protocol Reference - - - - - - -
- - - - -
- -
-
- - - -
- -
- - - - - - - -
Conforms toNSObject
Declared inSQLClient.h
- - - - - - -
- -

Tasks

- - - - - - - -
- - - - - - - - - -
- -

Instance Methods

- -
- -

error:code:severity:

- - - -
-

Required delegate method to receive error notifications

-
- - - -
- (void)error:(NSString *)error code:(int)code severity:(int)severity
- - - -
-

Parameters

- -
-
error
-

Error text

-
- -
-
code
-

FreeTDS error code

-
- -
-
severity
-

FreeTDS error severity

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- -

message:

- - - -
-

Optional delegate method to receive message notifications

-
- - - -
- (void)message:(NSString *)message
- - - -
-

Parameters

- -
-
message
-

Message text

-
- -
- - - - - - - - - - - - - -
-

Declared In

- SQLClient.h
-
- - -
- -
- - - - -
- - -
-
- - - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/html/css/styles.css b/SQLClient/SQLClientDocs/html/css/styles.css deleted file mode 100644 index 3308189..0000000 --- a/SQLClient/SQLClientDocs/html/css/styles.css +++ /dev/null @@ -1,615 +0,0 @@ -body { - font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; - font-size: 13px; -} - -code { - font-family: Courier, Consolas, monospace; - font-size: 13px; - color: #666; -} - -pre { - font-family: Courier, Consolas, monospace; - font-size: 13px; - line-height: 18px; - tab-interval: 0.5em; - border: 1px solid #C7CFD5; - background-color: #F1F5F9; - color: #666; - padding: 0.3em 1em; -} - -ul { - list-style-type: square; -} - -li { - margin-bottom: 10px; -} - -a, a code { - text-decoration: none; - color: #36C; -} - -a:hover, a:hover code { - text-decoration: underline; - color: #36C; -} - -h2 { - border-bottom: 1px solid #8391A8; - color: #3C4C6C; - font-size: 187%; - font-weight: normal; - margin-top: 1.75em; - padding-bottom: 2px; -} - -table { - margin-bottom: 4em; - border-collapse:collapse; - vertical-align: middle; -} - -td { - border: 1px solid #9BB3CD; - padding: .667em; - font-size: 100%; -} - -th { - border: 1px solid #9BB3CD; - padding: .3em .667em .3em .667em; - background: #93A5BB; - font-size: 103%; - font-weight: bold; - color: white; - text-align: left; -} - -/* @group Common page elements */ - -#top_header { - height: 91px; - left: 0; - min-width: 598px; - position: absolute; - right: 0; - top: 0; - z-index: 900; -} - -#footer { - clear: both; - padding-top: 20px; - text-align: center; -} - -#contents, #overview_contents { - -webkit-overflow-scrolling: touch; - border-top: 1px solid #A9A9A9; - position: absolute; - top: 90px; - left: 0; - right: 0; - bottom: 0; - overflow-x: hidden; - overflow-y: auto; - padding-left: 2em; - padding-right: 2em; - padding-top: 1em; - min-width: 550px; -} - -#contents.isShowingTOC { - left: 230px; - min-width: 320px; -} - -.copyright { - font-size: 12px; -} - -.generator { - font-size: 11px; -} - -.main-navigation ul li { - display: inline; - margin-left: 15px; - list-style: none; -} - -.navigation-top { - clear: both; - float: right; -} - -.navigation-bottom { - clear: both; - float: right; - margin-top: 20px; - margin-bottom: -10px; -} - -.open > .disclosure { - background-image: url("../img/disclosure_open.png"); -} - -.disclosure { - background: url("../img/disclosure.png") no-repeat scroll 0 0; -} - -.disclosure, .nodisclosure { - display: inline-block; - height: 8px; - margin-right: 5px; - position: relative; - width: 9px; -} - -/* @end */ - -/* @group Header */ - -#top_header #library { - background: url("../img/library_background.png") repeat-x 0 0 #485E78; - background-color: #ccc; - height: 35px; - font-size: 115%; -} - -#top_header #library #libraryTitle { - color: #FFFFFF; - margin-left: 15px; - text-shadow: 0 -1px 0 #485E78; - top: 8px; - position: absolute; -} - -#libraryTitle { - left: 0; -} - -#top_header #library #developerHome { - color: #92979E; - right: 15px; - top: 8px; - position: absolute; -} - -#top_header #library a:hover { - text-decoration: none; -} - -#top_header #title { - background: url("../img/title_background.png") repeat-x 0 0 #8A98A9; - border-bottom: 1px solid #757575; - height: 25px; - overflow: hidden; -} - -#top_header h1 { - font-size: 105%; - font-weight: normal; - margin: 0; - padding: 3px 0 2px; - text-align: center; - /*text-shadow: 0 1px 0 #D5D5D5;*/ - white-space: nowrap; -} - -#headerButtons { - background-color: #D8D8D8; - background-image: url("../img/button_bar_background.png"); - border-bottom: 0px solid #EDEDED; - border-top: 0px solid #a8a8a8; - font-size: 8pt; - height: 28px; - left: 0; - list-style: none outside none; - margin: 0; - overflow: hidden; - padding: 0; - position: absolute; - right: 0; - top: 61px; -} - -#headerButtons li { - background-repeat: no-repeat; - display: inline; - margin-top: 0; - margin-bottom: 0; - padding: 0; -} - -#toc_button button { - background-color: #EBEEF1; - border-color: #ACACAC; - border-style: none solid none none; - border-width: 0 1px 0 0; - height: 28px; - margin: 0; - padding-left: 30px; - text-align: left; - width: 230px; -} - -li#jumpto_button { - left: 230px; - margin-left: 0; - position: absolute; -} - -li#jumpto_button select { - height: 22px; - margin: 5px 2px 0 10px; - max-width: 300px; -} - -/* @end */ - -/* @group Table of contents */ - -#tocContainer.isShowingTOC { - border-right: 1px solid #ACACAC; - display: block; - overflow-x: hidden; - overflow-y: auto; - padding: 0; -} - -#tocContainer { - background-color: #EBEEF1; - border-top: 1px solid #ACACAC; - bottom: 0; - display: none; - left: 0; - overflow: hidden; - position: absolute; - top: 90px; - width: 229px; -} - -#tocContainer > ul#toc { - font-size: 11px; - margin: 0; - padding: 12px 0 18px; - width: 209px; - -moz-user-select: none; - -webkit-user-select: none; - user-select: none; -} - -#tocContainer > ul#toc > li { - margin: 0; - padding: 0 0 7px 30px; - text-indent: -15px; -} - -#tocContainer > ul#toc > li > .sectionName a { - color: #000000; - font-weight: bold; -} - -#tocContainer > ul#toc > li > .sectionName a:hover { - text-decoration: none; -} - -#tocContainer > ul#toc li.children > ul { - display: none; - height: 0; -} - -#tocContainer > ul#toc > li > ul { - margin: 0; - padding: 0; -} - -#tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { - margin-left: 0; - margin-bottom: 0; - padding-left: 15px; -} - -#tocContainer > ul#toc > li ul { - list-style: none; - margin-right: 0; - padding-right: 0; -} - -#tocContainer > ul#toc li.children.open > ul { - display: block; - height: auto; - margin-left: -15px; - padding-left: 0; -} - -#tocContainer > ul#toc > li > ul, ul#toc > li > ul > li { - margin-left: 0; - padding-left: 15px; -} - -#tocContainer li ul li { - margin-top: 0.583em; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#tocContainer li ul li span.sectionName { - white-space: normal; -} - -#tocContainer > ul#toc > li > ul > li > .sectionName a { - font-weight: bold; -} - -#tocContainer > ul#toc > li > ul a { - color: #4F4F4F; -} - -/* @end */ - -/* @group Index formatting */ - -.index-title { - font-size: 13px; - font-weight: normal; -} - -.index-column { - float: left; - width: 30%; - min-width: 200px; - font-size: 11px; -} - -.index-column ul { - margin: 8px 0 0 0; - padding: 0; - list-style: none; -} - -.index-column ul li { - margin: 0 0 3px 0; - padding: 0; -} - -.hierarchy-column { - min-width: 400px; -} - -.hierarchy-column ul { - margin: 3px 0 0 15px; -} - -.hierarchy-column ul li { - list-style-type: square; -} - -/* @end */ - -/* @group Common formatting elements */ - -.title { - font-weight: normal; - font-size: 215%; - margin-top:0; -} - -.subtitle { - font-weight: normal; - font-size: 180%; - color: #3C4C6C; - border-bottom: 1px solid #5088C5; -} - -.subsubtitle { - font-weight: normal; - font-size: 145%; - height: 0.7em; -} - -.note { - border: 1px solid #5088C5; - background-color: white; - margin: 1.667em 0 1.75em 0; - padding: 0 .667em .083em .750em; -} - -.warning { - border: 1px solid #5088C5; - background-color: #F0F3F7; - margin-bottom: 0.5em; - padding: 0.3em 0.8em; -} - -.bug { - border: 1px solid #000; - background-color: #ffffcc; - margin-bottom: 0.5em; - padding: 0.3em 0.8em; -} - -.deprecated { - color: #F60425; -} - -/* @end */ - -/* @group Common layout */ - -.section { - margin-top: 3em; -} - -/* @end */ - -/* @group Object specification section */ - -.section-specification { - margin-left: 2.5em; - margin-right: 2.5em; - font-size: 12px; -} - -.section-specification table { - margin-bottom: 0em; - border-top: 1px solid #d6e0e5; -} - -.section-specification td { - vertical-align: top; - border-bottom: 1px solid #d6e0e5; - border-left-width: 0px; - border-right-width: 0px; - border-top-width: 0px; - padding: .6em; -} - -.section-specification .specification-title { - font-weight: bold; -} - -/* @end */ - -/* @group Tasks section */ - -.task-list { - list-style-type: none; - padding-left: 0px; -} - -.task-list li { - margin-bottom: 3px; -} - -.task-item-suffix { - color: #996; - font-size: 12px; - font-style: italic; - margin-left: 0.5em; -} - -span.tooltip span.tooltip { - font-size: 1.0em; - display: none; - padding: 0.3em; - border: 1px solid #aaa; - background-color: #fdfec8; - color: #000; - text-align: left; -} - -span.tooltip:hover span.tooltip { - display: block; - position: absolute; - margin-left: 2em; -} - -/* @end */ - -/* @group Method section */ - -.section-method { - margin-top: 2.3em; -} - -.method-title { - margin-bottom: 1.5em; -} - -.method-subtitle { - margin-top: 0.7em; - margin-bottom: 0.2em; -} - -.method-subsection p { - margin-top: 0.4em; - margin-bottom: 0.8em; -} - -.method-declaration { - margin-top:1.182em; - margin-bottom:.909em; -} - -.method-declaration code { - font:14px Courier, Consolas, monospace; - color:#000; -} - -.declaration { - color: #000; -} - -.termdef { - margin-bottom: 10px; - margin-left: 0px; - margin-right: 0px; - margin-top: 0px; -} - -.termdef dt { - margin: 0; - padding: 0; -} - -.termdef dd { - margin-bottom: 6px; - margin-left: 16px; - margin-right: 0px; - margin-top: 1px; -} - -.termdef dd p { - margin-bottom: 6px; - margin-left: 0px; - margin-right: 0px; - margin-top: -1px; -} - -.argument-def { - margin-top: 0.3em; - margin-bottom: 0.3em; -} - -.argument-def dd { - margin-left: 1.25em; -} - -.see-also-section ul { - list-style-type: none; - padding-left: 0px; - margin-top: 0; -} - -.see-also-section li { - margin-bottom: 3px; -} - -.declared-in-ref { - color: #666; -} - -#tocContainer.hideInXcode { - display: none; - border: 0px solid black; -} - -#top_header.hideInXcode { - display: none; -} - -#contents.hideInXcode { - border: 0px solid black; - top: 0px; - left: 0px; -} - -/* @end */ - diff --git a/SQLClient/SQLClientDocs/html/css/stylesPrint.css b/SQLClient/SQLClientDocs/html/css/stylesPrint.css deleted file mode 100644 index dc54cd2..0000000 --- a/SQLClient/SQLClientDocs/html/css/stylesPrint.css +++ /dev/null @@ -1,22 +0,0 @@ - -header { - display: none; -} - -div.main-navigation, div.navigation-top { - display: none; -} - -div#overview_contents, div#contents.isShowingTOC, div#contents { - overflow: visible; - position: relative; - top: 0px; - border: none; - left: 0; -} -#tocContainer.isShowingTOC { - display: none; -} -nav { - display: none; -} \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/html/hierarchy.html b/SQLClient/SQLClientDocs/html/hierarchy.html deleted file mode 100644 index 5b6a6d3..0000000 --- a/SQLClient/SQLClientDocs/html/hierarchy.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - SQLClient Hierarchy - - - - - -
- - - - -
-
-
- - -
- -
-

Class Hierarchy

- - - -
- - - -
- -

Protocol References

- - - - -
- -
- - -
-
- - \ No newline at end of file diff --git a/SQLClient/SQLClientDocs/html/img/button_bar_background.png b/SQLClient/SQLClientDocs/html/img/button_bar_background.png deleted file mode 100644 index 71d1019..0000000 Binary files a/SQLClient/SQLClientDocs/html/img/button_bar_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/img/disclosure.png b/SQLClient/SQLClientDocs/html/img/disclosure.png deleted file mode 100644 index 4c5cbf4..0000000 Binary files a/SQLClient/SQLClientDocs/html/img/disclosure.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/img/disclosure_open.png b/SQLClient/SQLClientDocs/html/img/disclosure_open.png deleted file mode 100644 index 82396fe..0000000 Binary files a/SQLClient/SQLClientDocs/html/img/disclosure_open.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/img/library_background.png b/SQLClient/SQLClientDocs/html/img/library_background.png deleted file mode 100644 index 3006248..0000000 Binary files a/SQLClient/SQLClientDocs/html/img/library_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/img/title_background.png b/SQLClient/SQLClientDocs/html/img/title_background.png deleted file mode 100644 index 846e496..0000000 Binary files a/SQLClient/SQLClientDocs/html/img/title_background.png and /dev/null differ diff --git a/SQLClient/SQLClientDocs/html/index.html b/SQLClient/SQLClientDocs/html/index.html deleted file mode 100644 index 9006da1..0000000 --- a/SQLClient/SQLClientDocs/html/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - SQLClient Reference - - - - - -
- - - - -
-
-
- - -
- - - - - -
-

Class References

- -
- - - -
- -

Protocol References

- - - - -
- -
- - -
-
- - \ No newline at end of file diff --git a/SQLClient/SQLClientTests/SQLClientTests-Info.plist b/SQLClient/SQLClientTests/SQLClientTests-Info.plist index 8ccbca8..169b6f7 100644 --- a/SQLClient/SQLClientTests/SQLClientTests-Info.plist +++ b/SQLClient/SQLClientTests/SQLClientTests-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.martinrybak.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/SQLClient/SQLClientTests/SQLClientTests.m b/SQLClient/SQLClientTests/SQLClientTests.m index 6f12f28..11884cd 100644 --- a/SQLClient/SQLClientTests/SQLClientTests.m +++ b/SQLClient/SQLClientTests/SQLClientTests.m @@ -7,6 +7,7 @@ // #import +#import "SQLClient.h" @interface SQLClientTests : XCTestCase @@ -14,21 +15,743 @@ @interface SQLClientTests : XCTestCase @implementation SQLClientTests -- (void)setUp +#pragma mark - CRUD + +- (void)testSelectWithError +{ + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:@"SELECT" completion:^(NSArray* results) { + XCTAssertNil(results); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testSelectWithOneTable +{ + NSString* sql = @"SELECT 'Foo' AS Bar"; + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 1); + XCTAssertEqual([results[0] count], 1); + XCTAssertEqualObjects(results[0][0][@"Bar"], @"Foo"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testSelectWithTwoTables +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"SELECT 'Foo' AS Bar;"]; + [sql appendString:@"SELECT * FROM (VALUES (1), (2), (3)) AS Table1(a)"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 2); + XCTAssertEqual([results[0] count], 1); + XCTAssertEqual([results[1] count], 3); + XCTAssertEqualObjects(results[0][0][@"Bar"], @"Foo"); + XCTAssertEqualObjects(results[1][0][@"a"], @(1)); + XCTAssertEqualObjects(results[1][1][@"a"], @(2)); + XCTAssertEqualObjects(results[1][2][@"a"], @(3)); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testInsert +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 0); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testInsertWithSelect +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"SELECT @@IDENTITY AS Id;"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 1); + XCTAssertEqual([results[0] count], 1); + XCTAssertEqualObjects(results[0][0][@"Id"], @(1)); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testUpdate +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"UPDATE #Temp SET Name = 'Bar';"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 0); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testUpdateWithSelect +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"UPDATE #Temp SET Name = 'Bar';"]; + [sql appendString:@"SELECT * FROM #Temp;"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 1); + XCTAssertEqual([results[0] count], 1); + XCTAssertEqualObjects(results[0][0][@"Id"], @(1)); + XCTAssertEqualObjects(results[0][0][@"Name"], @"Bar"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testDelete +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"DELETE FROM #Temp;"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 0); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testDeleteWithSelect +{ + NSMutableString* sql = [NSMutableString string]; + [sql appendString:@"CREATE TABLE #Temp(Id INT IDENTITY, Name CHAR(20));"]; + [sql appendString:@"INSERT INTO #Temp (Name) VALUES ('Foo');"]; + [sql appendString:@"DELETE FROM #Temp;"]; + [sql appendString:@"SELECT * FROM #Temp;"]; + [sql appendString:@"DROP TABLE #Temp;"]; + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertNotNil(results); + XCTAssertEqual(results.count, 1); + XCTAssertEqual([results[0] count], 0); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +#pragma mark - Bit + +- (void)testBitWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"BIT" convertsTo:value]; +} + +- (void)testBitWithTrue +{ + id value = @(YES); + [self testValue:value ofType:@"BIT" convertsTo:value]; +} + +- (void)testBitWithFalse +{ + id value = @(YES); + [self testValue:value ofType:@"BIT" convertsTo:value]; +} + +#pragma mark - Tiny Int + +- (void)testTinyIntWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"TINYINT" convertsTo:value]; +} + +- (void)testTinyIntWithMinimum +{ + id value = @(0); + [self testValue:value ofType:@"TINYINT" convertsTo:value]; +} + +- (void)testTinyIntWithMaximum +{ + id value = @(UCHAR_MAX); + [self testValue:value ofType:@"TINYINT" convertsTo:value]; +} + +#pragma mark - Small Int + +- (void)testSmallIntWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"SMALLINT" convertsTo:value]; +} + +- (void)testSmallIntWithMinimum +{ + id value = @(SHRT_MIN); + [self testValue:value ofType:@"SMALLINT" convertsTo:value]; +} + +- (void)testSmallIntWithMaximum +{ + id value = @(SHRT_MAX); + [self testValue:value ofType:@"SMALLINT" convertsTo:value]; +} + +#pragma mark - Int + +- (void)testIntWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"INT" convertsTo:value]; +} + +- (void)testIntWithMinimum +{ + id value = @(SHRT_MIN); + [self testValue:value ofType:@"INT" convertsTo:value]; +} + +- (void)testIntWithMaximum +{ + id value = @(SHRT_MAX); + [self testValue:value ofType:@"INT" convertsTo:value]; +} + +#pragma mark - Big Int + +- (void)testBigIntWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"BIGINT" convertsTo:value]; +} + +- (void)testBigIntWithMinimum +{ + id value = @(LLONG_MIN); + [self testValue:value ofType:@"BIGINT" convertsTo:value]; +} + +- (void)testBigIntWithMaximum +{ + id value = @(LLONG_MAX); + [self testValue:value ofType:@"BIGINT" convertsTo:value]; +} + +#pragma mark - Float + +- (void)testFloatWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"FLOAT" convertsTo:value]; +} + +- (void)testFloatWithMinimum +{ + id value = @(-1.79e+308); + [self testValue:value ofType:@"FLOAT" convertsTo:value]; +} + +- (void)testFloatWithMaximum +{ + id value = @(1.79e+308); + [self testValue:value ofType:@"FLOAT" convertsTo:value]; +} + +#pragma mark - Real + +- (void)testRealWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"REAL" convertsTo:value]; +} + +- (void)testRealWithMinimum +{ + id value = [NSNumber numberWithFloat:-3.4e+38]; + [self testValue:value ofType:@"REAL" convertsTo:value]; +} + +- (void)testRealWithMaximum +{ + id value = [NSNumber numberWithFloat:3.4e+38]; + [self testValue:value ofType:@"REAL" convertsTo:value]; +} + +#pragma mark - Decimal + +- (void)testDecimalWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"DECIMAL" convertsTo:value]; +} + +- (void)testDecimalWithMinimum +{ + id value = [[NSDecimalNumber decimalNumberWithMantissa:1 exponent:38 isNegative:YES] decimalNumberByAdding:[NSDecimalNumber one]]; + [self testValue:value ofType:@"DECIMAL(38,0)" convertsTo:value]; +} + +- (void)testDecimalWithMaximum +{ + id value = [[NSDecimalNumber decimalNumberWithMantissa:1 exponent:38 isNegative:NO] decimalNumberBySubtracting:[NSDecimalNumber one]]; + [self testValue:value ofType:@"DECIMAL(38,0)" convertsTo:value]; +} + +#pragma mark - Numeric + +- (void)testNumericWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"NUMERIC" convertsTo:value]; +} + +- (void)testNumericWithMinimum +{ + id value = [[NSDecimalNumber decimalNumberWithMantissa:1 exponent:38 isNegative:YES] decimalNumberByAdding:[NSDecimalNumber one]]; + [self testValue:value ofType:@"NUMERIC(38,0)" convertsTo:value]; +} + +- (void)testNumericWithMaximum +{ + id value = [[NSDecimalNumber decimalNumberWithMantissa:1 exponent:38 isNegative:NO] decimalNumberBySubtracting:[NSDecimalNumber one]]; + [self testValue:value ofType:@"NUMERIC(38,0)" convertsTo:value]; +} + +#pragma mark - Small Money + +- (void)testSmallMoneyWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"SMALLMONEY" convertsTo:value]; +} + +- (void)testSmallMoneyWithMinimum +{ + id value = [NSDecimalNumber decimalNumberWithString:@"-214748.3648"]; + [self testValue:value ofType:@"SMALLMONEY" convertsTo:value]; +} + +- (void)testSmallMoneyWithMaximum +{ + id value = [NSDecimalNumber decimalNumberWithString:@"214748.3647"]; + [self testValue:value ofType:@"SMALLMONEY" convertsTo:value]; +} + +#pragma mark - Money + +- (void)testMoneyWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"MONEY" convertsTo:value]; +} + +- (void)testMoneyWithMinimum +{ + //TODO: fix last 2 digits, i.e. -922337203685477.5808 returns -922337203685477.58 + id value = [NSDecimalNumber decimalNumberWithString:@"-922337203685477.58"]; + [self testValue:value ofType:@"MONEY" convertsTo:value]; +} + +- (void)testMoneyWithMaximum +{ + //TODO: fix last 2 digits, i.e. 922337203685477.5807 returns 922337203685477.58 + id value = [NSDecimalNumber decimalNumberWithString:@"922337203685477.58"]; + [self testValue:value ofType:@"MONEY" convertsTo:value]; +} + +#pragma mark - Small DateTime + +- (void)testSmallDateTimeWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"SMALLDATETIME" convertsTo:value]; +} + +- (void)testSmallDateTimeWithMinimum +{ + id input = @"01-01-1900 00:00:00"; + id output = [self dateWithYear:1900 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"SMALLDATETIME" convertsTo:output]; +} + +- (void)testSmallDateTimeWithMaximum +{ + id input = @"06-06-2079 23:59:00"; + id output = [self dateWithYear:2079 month:6 day:6 hour:23 minute:59 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"SMALLDATETIME" convertsTo:output]; +} + +#pragma mark - DateTime + +- (void)testDateTimeWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"DATETIME" convertsTo:value]; +} + +- (void)testDateTimeWithMinimum +{ + id input = @"01-01-1753 00:00:00:000"; + id output = [self dateWithYear:1753 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"DATETIME" convertsTo:output]; +} + +- (void)testDateTimeWithMaximum +{ + id input = @"12-31-9999 23:59:59:997"; + id output = [self dateWithYear:9999 month:12 day:31 hour:23 minute:59 second:59 nanosecond:997000000 timezone:0]; + [self testValue:input ofType:@"DATETIME" convertsTo:output]; +} + +#pragma mark - DateTime2 + +//If these tests fail, you must tell FreeTDS to use the TDS protocol >= 7.3. +//Add an environment variable to the test scheme with name TDSVER and value auto + +- (void)testDateTime2WithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"DATETIME2" convertsTo:value]; +} + +- (void)testDateTime2WithMinimum +{ + id input = @"01-01-0001 00:00:00.0000000"; + id output = [self dateWithYear:1 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"DATETIME2" convertsTo:output]; +} + +- (void)testDateTime2WithMaximum +{ + id input = @"12-31-9999 23:59:59.9999999"; + id output = [self dateWithYear:9999 month:12 day:31 hour:23 minute:59 second:59 nanosecond:999999900 timezone:0]; + [self testValue:input ofType:@"DATETIME2" convertsTo:output]; +} + +#pragma mark - DateTimeOffset + +//If these tests fail, you must tell FreeTDS to use the TDS protocol >= 7.3. +//Add an environment variable to the test scheme with name TDSVER and value auto + +- (void)testDateTimeOffsetWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"DATETIMEOFFSET" convertsTo:value]; +} + +- (void)testDateTimeOffsetWithMinimum +{ + id input = @"01-01-0001 00:00:00.0000000 -14:00"; + id output = [self dateWithYear:1 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:-840]; + [self testValue:input ofType:@"DATETIMEOFFSET" convertsTo:output]; +} + +- (void)testDateTimeOffsetWithMaximum +{ + id input = @"12-31-9999 23:59:59.9999999 +14:00"; + id output = [self dateWithYear:9999 month:12 day:31 hour:23 minute:59 second:59 nanosecond:999999900 timezone:840]; + [self testValue:input ofType:@"DATETIMEOFFSET" convertsTo:output]; +} + +#pragma mark - Date + +//If these tests fail, you must tell FreeTDS to use the TDS protocol >= 7.3. +//Add an environment variable to the test scheme with name TDSVER and value auto + +- (void)testDateWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"DATE" convertsTo:value]; +} + +- (void)testDateWithMinimum +{ + id input = @"01-01-0001"; + id output = [self dateWithYear:1 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"DATE" convertsTo:output]; +} + +- (void)testDateWithMaximum +{ + id input = @"12-31-9999"; + id output = [self dateWithYear:9999 month:12 day:31 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"DATE" convertsTo:output]; +} + +#pragma mark - Time + +//If these tests fail, you must tell FreeTDS to use the TDS protocol >= 7.3. +//Add an environment variable to the test scheme with name TDSVER and value auto + +- (void)testTimeWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"TIME" convertsTo:value]; +} + +- (void)testTimeWithMinimum +{ + id input = @"00:00:00.0000000"; + id output = [self dateWithYear:1900 month:1 day:1 hour:0 minute:0 second:0 nanosecond:0 timezone:0]; + [self testValue:input ofType:@"TIME" convertsTo:output]; +} + +- (void)testTimeWithMaximum +{ + id input = @"23:59:59.9999999"; + id output = [self dateWithYear:1900 month:1 day:1 hour:23 minute:59 second:59 nanosecond:999999900 timezone:0]; + [self testValue:input ofType:@"TIME" convertsTo:output]; +} + +#pragma mark - Char + +- (void)testCharWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"CHAR(1)" convertsTo:value]; +} + +- (void)testCharWithMinimum +{ + //TODO: Fix (32 doesn't work) + //33 = minimum ASCII value + id value = [NSString stringWithFormat:@"%c", 33]; + [self testValue:value ofType:@"CHAR(1)" convertsTo:value]; +} + +- (void)testCharWithMaximum +{ + //127 = maximum printable ASCII value + id value = [NSString stringWithFormat:@"%c", 127]; + [self testValue:value ofType:@"CHAR(1)" convertsTo:value]; +} + +#pragma mark - VarChar(Max) + +- (void)testVarCharMaxWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"VARCHAR(MAX)" convertsTo:value]; +} + +- (void)testVarCharMaxWithMinimum +{ + //TODO: Fix (32 doesn't work) + //33 = minimum ASCII value + id value = [NSString stringWithFormat:@"%c", 33]; + [self testValue:value ofType:@"VARCHAR(MAX)" convertsTo:value]; +} + +- (void)testVarCharMaxWithMaximum +{ + id input = [self stringWithLength:[SQLClient sharedInstance].maxTextSize + 1]; + id output = [input substringToIndex:[SQLClient sharedInstance].maxTextSize - 1]; + [self testValue:input ofType:@"VARCHAR(MAX)" convertsTo:output]; +} + +#pragma mark - Text + +- (void)testTextWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"TEXT" convertsTo:value]; +} + +- (void)testTextWithMinimum +{ + //TODO: Fix (32 doesn't work) + //33 = minimum ASCII value + id value = [NSString stringWithFormat:@"%c", 33]; + [self testValue:value ofType:@"TEXT" convertsTo:value]; +} + +- (void)testTextWithMaximum +{ + id input = [self stringWithLength:[SQLClient sharedInstance].maxTextSize + 1]; + id output = [input substringToIndex:[SQLClient sharedInstance].maxTextSize - 1]; + [self testValue:input ofType:@"TEXT" convertsTo:output]; +} + +#pragma mark - UniqueIdentifier + +- (void)testUniqueIdentifierWithNull +{ + id value = [NSNull null]; + [self testValue:nil ofType:@"UNIQUEIDENTIFIER" convertsTo:value]; +} + +- (void)testUniqueIdentifierWithValue +{ + id output = [NSUUID UUID]; + id input = [output UUIDString]; + [self testValue:input ofType:@"UNIQUEIDENTIFIER" convertsTo:output]; +} + +#pragma mark - Binary + +- (void)testBinaryWithNull +{ + id value = [NSNull null]; + [self testBinaryValue:nil ofType:@"BINARY" convertsTo:value withStyle:1]; +} + +- (void)testBinaryWithValue +{ + NSString* string = [self stringWithLength:30]; + NSData* output = [string dataUsingEncoding:NSASCIIStringEncoding]; + NSString* input = [self hexStringWithData:output]; + [self testBinaryValue:input ofType:@"BINARY" convertsTo:output withStyle:1]; +} + +#pragma mark - VarBinary + +- (void)testVarBinaryWithNull +{ + id value = [NSNull null]; + [self testBinaryValue:nil ofType:@"VARBINARY" convertsTo:value withStyle:1]; +} + +- (void)testVarBinaryWithValue +{ + NSString* string = [self stringWithLength:30]; + NSData* output = [string dataUsingEncoding:NSASCIIStringEncoding]; + NSString* input = [self hexStringWithData:output]; + [self testBinaryValue:input ofType:@"VARBINARY" convertsTo:output withStyle:1]; +} + +#pragma mark - Private + +- (void)testValue:(id)input ofType:(NSString*)type convertsTo:(id)output +{ + NSString* sql; + if (input) { + sql = [NSString stringWithFormat:@"SELECT CAST('%@' AS %@) AS Value", input, type]; + } else { + sql = [NSString stringWithFormat:@"SELECT CAST(NULL AS %@) AS Value", type]; + } + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertEqualObjects(results[0][0][@"Value"], output); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)testBinaryValue:(id)input ofType:(NSString*)type convertsTo:(id)output withStyle:(int)style +{ + NSString* sql; + if (input) { + sql = [NSString stringWithFormat:@"SELECT CONVERT(%@, '%@', %d) AS Value", type, input, style]; + } else { + sql = [NSString stringWithFormat:@"SELECT CONVERT(%@, NULL, %d) AS Value", type, style]; + } + + XCTestExpectation* expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)]; + [self execute:sql completion:^(NSArray* results) { + XCTAssertEqualObjects(results[0][0][@"Value"], output); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:[SQLClient sharedInstance].timeout handler:nil]; +} + +- (void)execute:(NSString*)sql completion:(void (^)(NSArray* results))completion +{ + //Environment variables from the Test Debug Scheme + NSDictionary* environment = [[NSProcessInfo processInfo] environment]; + NSString* host = environment[@"HOST"]; + NSString* username = environment[@"USERNAME"]; + NSString* password = environment[@"PASSWORD"]; + NSString* database = environment[@"DATABASE"]; + + NSParameterAssert(host); + NSParameterAssert(username); + NSParameterAssert(password); + + SQLClient* client = [SQLClient sharedInstance]; + [client connect:host username:username password:password database:database completion:^(BOOL success) { + [client execute:sql completion:^(NSArray* results) { + [client disconnect]; + if (completion) { + completion(results); + } + }]; + }]; +} + +- (NSDate*)dateWithYear:(int)year month:(int)month day:(int)day hour:(int)hour minute:(int)minute second:(int)second nanosecond:(int)nanosecond timezone:(int)timezone { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. + NSCalendar* calendar = [NSCalendar currentCalendar]; + NSDateComponents* dateComponents = [[NSDateComponents alloc] init]; + dateComponents.year = year; + dateComponents.month = month; + dateComponents.day = day; + dateComponents.hour = hour; + dateComponents.minute = minute; + dateComponents.second = second; + dateComponents.nanosecond = nanosecond; + dateComponents.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:timezone * 60]; + return [calendar dateFromComponents:dateComponents]; } -- (void)tearDown +- (NSString*)stringWithLength:(NSUInteger)length { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; + NSMutableString* output = [NSMutableString string]; + for (NSUInteger i = 0; i < length; i++) { + //65-122 == alphanumeric ASCII values + char character = arc4random_uniform(65) + 57; + [output appendString:[NSString stringWithFormat:@"%c", character]]; + } + //Sanitize + return [output stringByReplacingOccurrencesOfString:@"'" withString:@"''"]; } -- (void)testExample +- (NSString*)hexStringWithData:(NSData*)data { - XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); + const unsigned char* dataBuffer = (const unsigned char*)[data bytes]; + if (!dataBuffer) { + return [NSString string]; + } + + NSMutableString* output = [NSMutableString stringWithCapacity:(data.length * 2)]; + for (int i = 0; i < data.length; ++i) { + [output appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]]; + } + return [NSString stringWithFormat:@"0x%@", output]; } @end