Skip to content

Instantly share code, notes, and snippets.

@ramsince88
Last active February 27, 2017 05:59
Show Gist options
  • Save ramsince88/8535996 to your computer and use it in GitHub Desktop.
Save ramsince88/8535996 to your computer and use it in GitHub Desktop.
//
// SQLClient.h
// SQLClient
//
// Created by Martin Rybak on 10/4/13.
// Copyright (c) 2013 Martin Rybak. All rights reserved.
//
#import <Foundation/Foundation.h>
@protocol SQLClientDelegate <NSObject>
/**
* 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
/**
* Native SQL Server client for iOS. An Objective-C wrapper around the open-source FreeTDS library.
*/
@interface SQLClient : NSObject
/**
* Connection timeout, in seconds. Default is 5. Override 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<SQLClientDelegate>* 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.
*/
@property (nonatomic, weak) NSOperationQueue* callbackQueue;
/**
* 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;
/**
* Returns an initialized SQLClient instance as a singleton
*
* @return Shared SQLClient object
*/
+ (id)sharedInstance;
/**
* 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 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;
/**
* Indicates whether the database is currently connected
*/
- (BOOL)connected;
/**
* 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.
*/
- (void)execute:(NSString*)sql completion:(void (^)(NSArray* results))completion;
/**
* Disconnects from database server
*/
- (void)disconnect;
@end
//
// SQLClient.m
// SQLClient
//
// Created by Martin Rybak on 10/4/13.
// Copyright (c) 2013 Martin Rybak. All rights reserved.
//
#import "SQLClient.h"
#import "sybfront.h"
#import "sybdb.h"
#import "syberror.h"
int const SQLClientDefaultTimeout = 5;
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 SQLClientRowIgnoreMessage = @"Ignoring unknown row type";
struct COL
{
char* name;
char* buffer;
int type;
int size;
int status;
};
@interface SQLClient ()
@property (nonatomic, copy, readwrite) NSString* host;
@property (nonatomic, copy, readwrite) NSString* username;
@property (nonatomic, copy, readwrite) NSString* database;
@property (nonatomic, copy, readwrite) NSString* password;
@end
@implementation SQLClient
{
LOGINREC* login;
DBPROCESS* connection;
}
#pragma mark - NSObject
//Initializes the FreeTDS library and sets callback handlers
- (id)init
{
if (self = [super init])
{
//Initialize the FreeTDS library
if (dbinit() == FAIL)
return nil;
//Initialize SQLClient
self.timeout = SQLClientDefaultTimeout;
self.charset = SQLClientDefaultCharset;
self.callbackQueue = [NSOperationQueue currentQueue];
self.workerQueue = [[NSOperationQueue alloc] init];
self.workerQueue.name = SQLClientWorkerQueueName;
//Set FreeTDS callback handlers
dberrhandle(err_handler);
dbmsghandle(msg_handler);
}
return self;
}
//Exits the FreeTDS library
- (void)dealloc
{
dbexit();
}
#pragma mark - Public
+ (id)sharedInstance
{
static SQLClient* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)connect:(NSString*)host
username:(NSString*)username
password:(NSString*)password
database:(NSString*)database
completion:(void (^)(BOOL success))completion
{
//Save inputs
self.host = host;
self.username = username;
self.password = password;
self.database = database;
//Connect to database on worker queue
[self.workerQueue addOperationWithBlock:^{
//Set login timeout
dbsetlogintime(self.timeout);
//Initialize login struct
if ((login = dblogin()) == FAIL)
return [self connectionFailure:completion];
//Populate login struct
DBSETLUSER(login, [self.username UTF8String]);
DBSETLPWD(login, [self.password UTF8String]);
DBSETLHOST(login, [self.host UTF8String]);
DBSETLCHARSET(login, [self.charset UTF8String]);
//Connect to database server
if ((connection = dbopen(login, [self.host UTF8String])) == NULL)
return [self connectionFailure:completion];
NSLog(@"%s", [self.database UTF8String]);
//Switch to database
if (dbuse(connection, [self.database UTF8String]) == FAIL)
return [self connectionFailure:completion];
//Success!
[self connectionSuccess:completion];
}];
}
- (BOOL)connected
{
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
{
//Execute query on worker queue
[self.workerQueue addOperationWithBlock:^{
//Prepare SQL statement
dbcmd(connection, [sql UTF8String]);
//Execute SQL statement
if (dbsqlexec(connection) == FAIL)
return [self executionFailure:completion];
//Create array to contain the tables
NSMutableArray* output = [[NSMutableArray alloc] init];
struct COL* columns;
struct COL* pcol;
int erc;
//Loop through each table
while ((erc = dbresults(connection)) != NO_MORE_RESULTS)
{
int ncols;
int row_code;
//Create array to contain the rows for this table
NSMutableArray* table = [[NSMutableArray alloc] init];
//Get number of columns
ncols = dbnumcols(connection);
//Allocate C-style array of COL structs
if ((columns = calloc(ncols, sizeof(struct COL))) == NULL)
return [self executionFailure:completion];
//Bind the column info
for (pcol = columns; pcol - columns < ncols; pcol++)
{
//Get column number
int c = pcol - columns + 1;
//Get column metadata
pcol->name = dbcolname(connection, c);
pcol->type = dbcoltype(connection, c);
pcol->size = dbcollen(connection, c);
//If the column is [VAR]CHAR, 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).
if (pcol->type != SYBCHAR)
pcol->size = dbwillconvert(pcol->type, SYBCHAR);
//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];
//Bind column status
erc = dbnullbind(connection, c, &pcol->status);
if (erc == FAIL)
return [self executionFailure:completion];
//printf("%s is type %d with value %s\n", pcol->name, pcol->type, pcol->buffer);
}
//printf("\n");
//Loop through each row
while ((row_code = dbnextrow(connection)) != NO_MORE_ROWS)
{
//Check row type
switch (row_code)
{
//Regular row
case REG_ROW:
{
//Create a new dictionary to contain the column names and vaues
NSMutableDictionary* row = [[NSMutableDictionary alloc] initWithCapacity:ncols];
//Loop through each column and create an entry where dictionary[columnName] = columnValue
for (pcol = columns; pcol - columns < ncols; pcol++)
{
NSString* column = [NSString stringWithUTF8String:pcol->name];
id value = [NSString stringWithUTF8String:pcol->buffer] ?: [NSNull null];
row[column] = value;
//printf("%@=%@\n", column, value);
}
//Add an immutable copy to the table
[table addObject:[row copy]];
//printf("\n");
break;
}
//Buffer full
case BUF_FULL:
return [self executionFailure:completion];
//Error
case FAIL:
return [self executionFailure:completion];
default:
[self message:SQLClientRowIgnoreMessage];
}
}
//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]];
}
//Success! Send an immutable copy of the results array
[self executionSuccess:completion results:[output copy]];
}];
}
- (void)disconnect
{
dbclose(connection);
}
#pragma mark - Private
//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);
}
//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);
}
//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);
}
//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);
}
//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 0;
}
//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;
}
//Forwards a message to the delegate on the callback queue if it implements
- (void)message:(NSString*)message
{
//Invoke delegate on calling queue
[self.callbackQueue addOperationWithBlock:^{
if ([self.delegate respondsToSelector:@selector(message:)])
[self.delegate message:message];
}];
}
//Forwards an error message to the delegate on the callback queue.
- (void)error:(NSString*)error code:(int)code severity:(int)severity
{
if (!self.delegate || ![self.delegate conformsToProtocol:@protocol(SQLClientDelegate)])
[NSException raise:SQLClientDelegateError format:nil];
//Invoke delegate on callback queue
[self.callbackQueue addOperationWithBlock:^{
[self.delegate error:error code:code severity:severity];
}];
}
@end
@zhangzheng999
Copy link

您好!我现在用这个库进行数据库访问!在进行上架的时候遇到了IPV6被拒问题,就是在审核的时候测试账号一直无法与数据库进行连接!按照苹果提供的在本地搭建IPV6测试环境进行测试的时候可以正常访问,但是每次上架审核的时候就会因为IPV6问题被拒!可以帮助解决一下吗,十分感谢!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment