/* * 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