/ Analytics / SQLite / SFSQLiteStatement.m
SFSQLiteStatement.m
  1  /*
  2   * Copyright (c) 2017 Apple Inc. All Rights Reserved.
  3   *
  4   * @APPLE_LICENSE_HEADER_START@
  5   *
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   *
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   *
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  #if __OBJC2__
 25  
 26  #import <Foundation/NSKeyedArchiver_Private.h>
 27  #import "SFSQLite.h"
 28  #import "SFSQLiteStatement.h"
 29  #import "SFObjCType.h"
 30  #import "utilities/debugging.h"
 31  
 32  @interface SFSQLiteStatement ()
 33  @property (nonatomic, strong) NSMutableArray *temporaryBoundObjects;
 34  @end
 35  @implementation SFSQLiteStatement
 36  
 37  @synthesize SQLite = _SQLite;
 38  @synthesize SQL = _SQL;
 39  @synthesize handle = _handle;
 40  @synthesize reset = _reset;
 41  @synthesize temporaryBoundObjects = _temporaryBoundObjects;
 42  
 43  - (id)initWithSQLite:(SFSQLite *)SQLite SQL:(NSString *)SQL handle:(sqlite3_stmt *)handle {
 44      if ((self = [super init])) {
 45          _SQLite = SQLite;
 46          _SQL = SQL;
 47          _handle = handle;
 48          _reset = YES;
 49      }
 50      return self;
 51  }
 52  
 53  - (void)finalizeStatement {
 54      if (!_reset) {
 55          secerror("sfsqlite: Statement not reset after last use: \"%@\"", _SQL);
 56          return;
 57      }
 58      if (sqlite3_finalize(_handle)) {
 59          secerror("sfsqlite: Error finalizing prepared statement: \"%@\"", _SQL);
 60          return;
 61      }
 62  }
 63  
 64  - (void)resetAfterStepError
 65  {
 66      if (!_reset) {
 67          (void)sqlite3_reset(_handle); // we expect this to return an error
 68          (void)sqlite3_clear_bindings(_handle);
 69          [_temporaryBoundObjects removeAllObjects];
 70          _reset = YES;
 71      }
 72  }
 73  
 74  - (BOOL)step {
 75      if (_reset) {
 76          _reset = NO;
 77      }
 78      
 79      int rc = sqlite3_step(_handle);
 80      if ((rc & 0x00FF) == SQLITE_ROW) {
 81          return YES;
 82      } else if ((rc & 0x00FF) == SQLITE_DONE) {
 83          return NO;
 84      } else {
 85          [self resetAfterStepError];
 86          secerror("sfsqlite: Failed to step (%d): \"%@\"", rc, _SQL);
 87          return NO;
 88      }
 89  }
 90  
 91  - (void)reset {
 92      if (!_reset) {
 93          if (sqlite3_reset(_handle)) {
 94              secerror("sfsqlite: Error resetting prepared statement: \"%@\"", _SQL);
 95              return;
 96          }
 97          
 98          if (sqlite3_clear_bindings(_handle)) {
 99              secerror("sfsqlite: Error clearing prepared statement bindings: \"%@\"", _SQL);
100              return;
101          }
102          [_temporaryBoundObjects removeAllObjects];
103          _reset = YES;
104      }
105  }
106  
107  - (void)bindInt:(SInt32)value atIndex:(NSUInteger)index {
108      if (!_reset) {
109          secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL);
110          return;
111      }
112      
113      if (sqlite3_bind_int(_handle, (int)index+1, value)) {
114          secerror("sfsqlite: Error binding int at %ld: \"%@\"", (unsigned long)index, _SQL);
115          return;
116      }
117  }
118  
119  - (void)bindInt64:(SInt64)value atIndex:(NSUInteger)index {
120      if (!_reset) {
121          secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL);
122          return;
123      }
124      
125      if (sqlite3_bind_int64(_handle, (int)index+1, value)) {
126          secerror("sfsqlite: Error binding int64 at %ld: \"%@\"", (unsigned long)index, _SQL);
127          return;
128      }
129  }
130  
131  - (void)bindDouble:(double)value atIndex:(NSUInteger)index {
132      if (!_reset) {
133          secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL);
134          return;
135      }
136      
137      if (sqlite3_bind_double(_handle, (int)index+1, value)) {
138          secerror("sfsqlite: Error binding double at %ld: \"%@\"", (unsigned long)index, _SQL);
139          return;
140      }
141  }
142  
143  - (void)bindBlob:(NSData *)value atIndex:(NSUInteger)index {
144      if (!_reset) {
145          secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL);
146          return;
147      }
148      
149      if (value) {
150          NS_VALID_UNTIL_END_OF_SCOPE NSData *arcSafeValue = value;
151          if (sqlite3_bind_blob(_handle, (int)index+1, [arcSafeValue bytes], (int)[arcSafeValue length], NULL)) {
152              secerror("sfsqlite: Error binding blob at %ld: \"%@\"", (unsigned long)index, _SQL);
153              return;
154          }
155      } else {
156          [self bindNullAtIndex:index];
157      }
158  }
159  
160  - (void)bindText:(NSString *)value atIndex:(NSUInteger)index {
161      if (!_reset) {
162          secerror("sfsqlite: Statement is not reset: \"%@\"", _SQL);
163          return;
164      }
165  
166      if (value) {
167          NS_VALID_UNTIL_END_OF_SCOPE NSString *arcSafeValue = value;
168          if (sqlite3_bind_text(_handle, (int)index+1, [arcSafeValue UTF8String], -1, NULL)) {
169              secerror("sfsqlite: Error binding text at %ld: \"%@\"", (unsigned long)index, _SQL);
170              return;
171          }
172      } else {
173          [self bindNullAtIndex:index];
174      }
175  }
176  
177  - (void)bindNullAtIndex:(NSUInteger)index {
178      int rc = sqlite3_bind_null(_handle, (int)index+1);
179      if ((rc & 0x00FF) != SQLITE_OK) {
180          secerror("sfsqlite: sqlite3_bind_null error");
181          return;
182      }
183  }
184  
185  - (id)retainedTemporaryBoundObject:(id)object
186  {
187      if (!_temporaryBoundObjects) {
188          _temporaryBoundObjects = [NSMutableArray new];
189      }
190      [_temporaryBoundObjects addObject:object];
191      return object;
192  }
193  
194  - (void)bindValue:(id)value atIndex:(NSUInteger)index {
195      if ([value isKindOfClass:[NSNumber class]]) {
196          SFObjCType *type = [SFObjCType typeForValue:value];
197          if (type.isIntegerNumber) {
198              if (type.size <= 4) {
199                  [self bindInt:[value intValue] atIndex:index];
200              } else {
201                  [self bindInt64:[value longLongValue] atIndex:index];
202              }
203          } else {
204              NSAssert(type.isFloatingPointNumber, @"Expected number type to be either integer or floating point");
205              NSAssert(type.code == SFObjCTypeDouble || type.code == SFObjCTypeFloat, @"Unexpected floating point number type: %@", type);
206              [self bindDouble:[value doubleValue] atIndex:index];
207          }
208      } else if ([value isKindOfClass:[NSData class]]) {
209          [self bindBlob:value atIndex:index];
210      } else if ([value isKindOfClass:[NSUUID class]]) {
211          uuid_t uuid;
212          [(NSUUID *)value getUUIDBytes:uuid];
213          [self bindBlob:[self retainedTemporaryBoundObject:[NSData dataWithBytes:uuid length:sizeof(uuid_t)]] atIndex:index];
214      } else if ([value isKindOfClass:[NSString class]]) {
215          [self bindText:value atIndex:index];
216      } else if ([value isKindOfClass:[NSNull class]]) {
217          [self bindNullAtIndex:index];
218      } else if ([value isKindOfClass:[NSDate class]]) {
219          [self bindDouble:[(NSDate *)value timeIntervalSinceReferenceDate] atIndex:index];
220      } else if ([value isKindOfClass:[NSError class]]) {
221          [self bindBlob:[self retainedTemporaryBoundObject:[NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:nil]] atIndex:index];
222      } else if ([value isKindOfClass:[NSURL class]]) {
223          [self bindText:[self retainedTemporaryBoundObject:[value absoluteString]] atIndex:index];
224      } else {
225          secerror("sfsqlite: Can't bind object of type %@", [value class]);
226          return;
227      }
228  }
229  
230  - (void)bindValues:(NSArray *)values {
231      for (NSUInteger i = 0; i < values.count; i++) {
232          [self bindValue:values[i] atIndex:i];
233      }
234  }
235  
236  - (NSUInteger)columnCount {
237      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
238      
239      return sqlite3_column_count(_handle);
240  }
241  
242  - (int)columnTypeAtIndex:(NSUInteger)index {
243      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
244      
245      return sqlite3_column_type(_handle, (int)index);
246  }
247  
248  - (NSString *)columnNameAtIndex:(NSUInteger)index {
249      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
250      
251      return @(sqlite3_column_name(_handle, (int)index));
252  }
253  
254  - (SInt32)intAtIndex:(NSUInteger)index {
255      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
256      
257      return sqlite3_column_int(_handle, (int)index);
258  }
259  
260  - (SInt64)int64AtIndex:(NSUInteger)index {
261      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
262      
263      return sqlite3_column_int64(_handle, (int)index);
264  }
265  
266  - (double)doubleAtIndex:(NSUInteger)index {
267      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
268      
269      return sqlite3_column_double(_handle, (int)index);
270  }
271  
272  - (NSData *)blobAtIndex:(NSUInteger)index {
273      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
274      
275      const void *bytes = sqlite3_column_blob(_handle, (int)index);
276      if (bytes) {
277          int length = sqlite3_column_bytes(_handle, (int)index);
278          return [NSData dataWithBytes:bytes length:length];
279      } else {
280          return nil;
281      }
282  }
283  
284  - (NSString *)textAtIndex:(NSUInteger)index {
285      NSAssert(!_reset, @"Statement is reset: \"%@\"", _SQL);
286      
287      const char *text = (const char *)sqlite3_column_text(_handle, (int)index);
288      if (text) {
289          return @(text);
290      } else {
291          return nil;
292      }
293  }
294  
295  - (id)objectAtIndex:(NSUInteger)index {
296      int type = [self columnTypeAtIndex:index];
297      switch (type) {
298          case SQLITE_INTEGER:
299              return @([self int64AtIndex:index]);
300              
301          case SQLITE_FLOAT:
302              return @([self doubleAtIndex:index]);
303              
304          case SQLITE_TEXT:
305              return [self textAtIndex:index];
306              
307          case SQLITE_BLOB:
308              return [self blobAtIndex:index];
309              
310          case SQLITE_NULL:
311              return nil;
312              
313          default:
314              secerror("sfsqlite: Unexpected column type: %d", type);
315              return nil;
316      }
317  }
318  
319  - (NSArray *)allObjects {
320      NSUInteger columnCount = [self columnCount];
321      NSMutableArray *objects = [NSMutableArray arrayWithCapacity:columnCount];
322      for (NSUInteger i = 0; i < columnCount; i++) {
323          objects[i] = [self objectAtIndex:i] ?: [NSNull null];
324      }
325      return objects;
326  }
327  
328  - (NSDictionary *)allObjectsByColumnName {
329      NSUInteger columnCount = [self columnCount];
330      NSMutableDictionary *objectsByColumnName = [NSMutableDictionary dictionaryWithCapacity:columnCount];
331      for (NSUInteger i = 0; i < columnCount; i++) {
332          NSString *columnName = [self columnNameAtIndex:i];
333          id object = [self objectAtIndex:i];
334          if (object) {
335              objectsByColumnName[columnName] = object;
336          }
337      }
338      return objectsByColumnName;
339  }
340  
341  @end
342  
343  #endif