/*
* 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->_conn,
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];
}
- (bool)step
{
int code = sqlite3_step(_stmt);
if (code != SQLITE_DONE && code != SQLITE_ROW)
@throw [SL3ExecuteStatementFailedException
exceptionWithStatement: self
errorCode: code];
return (code == SQLITE_ROW);
}
- (id)objectForColumn: (size_t)column
{
if (column > INT_MAX)
@throw [OFOutOfRangeException exception];
switch (sqlite3_column_type(_stmt, column)) {
case SQLITE_INTEGER:
return [OFNumber numberWithLongLong:
sqlite3_column_int64(_stmt, column)];
case SQLITE_FLOAT:
return [OFNumber numberWithDouble:
sqlite3_column_double(_stmt, column)];
case SQLITE_TEXT:
return [OFString stringWithUTF8String:
(const char *)sqlite3_column_text(_stmt, column)];
case SQLITE_BLOB:
return [OFData
dataWithItems: sqlite3_column_blob(_stmt, column)
count: sqlite3_column_bytes(_stmt, column)];
case SQLITE_NULL:
return [OFNull null];
default:
OFEnsure(0);
}
}
- (size_t)columnCount
{
return sqlite3_column_count(_stmt);
}
- (OFString *)nameForColumn: (size_t)column
{
const char *name;
if (column > [self columnCount])
@throw [OFOutOfRangeException exception];
if ((name = sqlite3_column_name(_stmt, column)) == NULL)
@throw [OFOutOfMemoryException exception];
return [OFString stringWithUTF8String: name];
}
- (OFArray *)rowArray
{
size_t count = [self columnCount];
OFMutableArray *array = [OFMutableArray arrayWithCapacity: count];
for (size_t i = 0; i < count; i++)
[array addObject: [self objectForColumn: i]];
[array makeImmutable];
return array;
}
- (OFDictionary OF_GENERIC(OFString *, id) *)rowDictionary
{
size_t count = [self columnCount];
OFMutableDictionary *dictionary =
[OFMutableDictionary dictionaryWithCapacity: count];
for (size_t i = 0; i < count; i++)
[dictionary setObject: [self objectForColumn: i]
forKey: [self nameForColumn: i]];
[dictionary makeImmutable];
return dictionary;
}
- (void)reset
{
int code = sqlite3_reset(_stmt);
if (code != SQLITE_OK)
@throw [SL3ResetStatementFailedException
exceptionWithStatement: self
errorCode: code];
}
@end