FossilOrigin-Name: 4fc7a99ac98531e4bc6252a53c4a08277e32c62c01fa7593c1d0f24001d974fe
183 lines
4.9 KiB
Objective-C
183 lines
4.9 KiB
Objective-C
/*
|
|
* Copyright (c) 2020, Jonathan Schleifer <js@nil.im>
|
|
*
|
|
* https://fossil.nil.im/objsqlite3
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice is present in all copies.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#import "SL3PreparedStatement.h"
|
|
#import "SL3PreparedStatement+Private.h"
|
|
|
|
#import "SL3BindObjectFailedException.h"
|
|
#import "SL3ClearBindingsFailedException.h"
|
|
#import "SL3ExecuteStatementFailedException.h"
|
|
#import "SL3PrepareStatementFailedException.h"
|
|
#import "SL3ResetStatementFailedException.h"
|
|
|
|
static void
|
|
releaseObject(void *object)
|
|
{
|
|
[(id)object release];
|
|
}
|
|
|
|
@implementation SL3PreparedStatement
|
|
- (instancetype)sl3_initWithConnection: (SL3Connection *)connection
|
|
SQLStatement: (OFConstantString *)SQLStatement
|
|
{
|
|
self = [super init];
|
|
|
|
@try {
|
|
int code = sqlite3_prepare_v2(connection->_db,
|
|
SQLStatement.UTF8String, SQLStatement.UTF8StringLength,
|
|
&_stmt, NULL);
|
|
|
|
if (code != SQLITE_OK)
|
|
@throw [SL3PrepareStatementFailedException
|
|
exceptionWithConnection: connection
|
|
SQLStatement: SQLStatement
|
|
errorCode: code];
|
|
|
|
_connection = [connection retain];
|
|
} @catch (id e) {
|
|
[self release];
|
|
@throw e;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
sqlite3_finalize(_stmt);
|
|
[_connection release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
static void
|
|
bindObject(SL3PreparedStatement *statement, int column, id object)
|
|
{
|
|
int code;
|
|
|
|
if ([object isKindOfClass: [OFNumber class]]) {
|
|
switch (*[object objCType]) {
|
|
case 'f':
|
|
case 'd':
|
|
code = sqlite3_bind_double(statement->_stmt, column,
|
|
[object doubleValue]);
|
|
break;
|
|
/* TODO: Check for range when converting to signed. */
|
|
default:
|
|
code = sqlite3_bind_int64(statement->_stmt, column,
|
|
[object longLongValue]);
|
|
break;
|
|
}
|
|
} else if ([object isKindOfClass: [OFString class]]) {
|
|
OFString *copy = [object copy];
|
|
|
|
code = sqlite3_bind_text64(statement->_stmt, column,
|
|
copy.UTF8String, copy.UTF8StringLength, releaseObject,
|
|
SQLITE_UTF8);
|
|
} else if ([object isKindOfClass: [OFData class]]) {
|
|
OFData *copy = [object copy];
|
|
|
|
code = sqlite3_bind_blob64(statement->_stmt, column, copy.items,
|
|
copy.count * copy.itemSize, releaseObject);
|
|
} else if ([object isEqual: [OFNull null]])
|
|
code = sqlite3_bind_null(statement->_stmt, column);
|
|
else
|
|
@throw [OFInvalidArgumentException exception];
|
|
|
|
if (code != SQLITE_OK)
|
|
@throw [SL3BindObjectFailedException
|
|
exceptionWithObject: object
|
|
column: column
|
|
statement: statement
|
|
errorCode: code];
|
|
}
|
|
|
|
- (void)bindWithArray: (OFArray *)array
|
|
{
|
|
void *pool = objc_autoreleasePoolPush();
|
|
int column = 0;
|
|
|
|
if (array.count > sqlite3_bind_parameter_count(_stmt))
|
|
@throw [OFOutOfRangeException exception];
|
|
|
|
for (id object in array)
|
|
bindObject(self, ++column, object);
|
|
|
|
objc_autoreleasePoolPop(pool);
|
|
}
|
|
|
|
- (void)bindWithDictionary:
|
|
(OFDictionary OF_GENERIC(OFString *, id) *)dictionary
|
|
{
|
|
void *pool = objc_autoreleasePoolPush();
|
|
OFEnumerator OF_GENERIC(OFString *) *keyEnumerator =
|
|
[dictionary keyEnumerator];
|
|
OFEnumerator *objectEnumerator = [dictionary objectEnumerator];
|
|
OFString *key;
|
|
id object;
|
|
|
|
while ((key = [keyEnumerator nextObject]) != nil &&
|
|
(object = [objectEnumerator nextObject]) != nil) {
|
|
int column = sqlite3_bind_parameter_index(
|
|
_stmt, key.UTF8String);
|
|
|
|
if (column == 0)
|
|
@throw [OFUndefinedKeyException
|
|
exceptionWithObject: self
|
|
key: key];
|
|
|
|
bindObject(self, column, object);
|
|
}
|
|
|
|
objc_autoreleasePoolPop(pool);
|
|
}
|
|
|
|
- (void)clearBindings
|
|
{
|
|
int code = sqlite3_clear_bindings(_stmt);
|
|
|
|
if (code != SQLITE_OK)
|
|
@throw [SL3ClearBindingsFailedException
|
|
exceptionWithStatement: self
|
|
errorCode: code];
|
|
}
|
|
|
|
- (void)step
|
|
{
|
|
int code = sqlite3_step(_stmt);
|
|
|
|
if (code != SQLITE_DONE && code != SQLITE_ROW)
|
|
@throw [SL3ExecuteStatementFailedException
|
|
exceptionWithStatement: self
|
|
errorCode: code];
|
|
}
|
|
|
|
- (void)reset
|
|
{
|
|
int code = sqlite3_reset(_stmt);
|
|
|
|
if (code != SQLITE_OK)
|
|
@throw [SL3ResetStatementFailedException
|
|
exceptionWithStatement: self
|
|
errorCode: code];
|
|
}
|
|
@end
|