PageRenderTime 64ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Persistencia/SQLitePersistentObjects/SQLitePersistentObject.m

https://github.com/wookay/touching
Objective C | 1087 lines | 911 code | 117 blank | 59 comment | 268 complexity | e89df997b5ba1f421d0733be91a53c21 MD5 | raw file
  1. // ----------------------------------------------------------------------
  2. // Part of the SQLite Persistent Objects for Cocoa and Cocoa Touch
  3. //
  4. // Original Version: (c) 2008 Jeff LaMarche (jeff_Lamarche@mac.com)
  5. // ----------------------------------------------------------------------
  6. // This code may be used without restriction in any software, commercial,
  7. // free, or otherwise. There are no attribution requirements, and no
  8. // requirement that you distribute your changes, although bugfixes and
  9. // enhancements are welcome.
  10. //
  11. // If you do choose to re-distribute the source code, you must retain the
  12. // copyright notice and this license information. I also request that you
  13. // place comments in to identify your changes.
  14. //
  15. // For information on how to use these classes, take a look at the
  16. // included eadme.txt file
  17. // ----------------------------------------------------------------------
  18. #import "SQLitePersistentObject.h"
  19. #import "SQLiteInstanceManager.h"
  20. #import "NSString-SQLiteColumnName.h"
  21. #import "NSObject-SQLitePersistence.h"
  22. #import "NSString-UppercaseFirst.h"
  23. #import "NSString-NumberStuff.h"
  24. id findByMethodImp(id self, SEL _cmd, id value)
  25. {
  26. NSString *methodBeingCalled = [NSString stringWithUTF8String:sel_getName(_cmd)];
  27. NSRange theRange = NSMakeRange(6, [methodBeingCalled length] - 7);
  28. NSString *property = [[methodBeingCalled substringWithRange:theRange] stringByLowercasingFirstLetter];
  29. NSMutableString *queryCondition = [NSMutableString stringWithFormat:@"WHERE %@ = ", [property stringAsSQLColumnName]];
  30. if (![value isKindOfClass:[NSNumber class]])
  31. [queryCondition appendString:@"'"];
  32. if ([value conformsToProtocol:@protocol(SQLitePersistence)])
  33. {
  34. if ([[value class] shouldBeStoredInBlob])
  35. {
  36. NSLog(@"*** Can't search on BLOB fields");
  37. return nil;
  38. }
  39. else
  40. [queryCondition appendString:[value sqlColumnRepresentationOfSelf]];
  41. }
  42. else
  43. {
  44. [queryCondition appendString:[value stringValue]];
  45. }
  46. if (![value isKindOfClass:[NSNumber class]])
  47. [queryCondition appendString:@"'"];
  48. return [self findByCriteria:queryCondition];
  49. }
  50. @interface SQLitePersistentObject (private)
  51. + (void)tableCheck;
  52. - (void)setPk:(int)newPk;
  53. + (NSString *)classNameForTableName:(NSString *)theTable;
  54. + (void)setUpDynamicMethods;
  55. @end
  56. @interface SQLitePersistentObject (private_memory)
  57. + (void)registerObjectInMemory:(SQLitePersistentObject *)theObject;
  58. + (void)unregisterObject:(SQLitePersistentObject *)theObject;
  59. - (NSString *)memoryMapKey;
  60. @end
  61. NSMutableDictionary *objectMap;
  62. @implementation SQLitePersistentObject
  63. #if (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR)
  64. - (NSString *)className
  65. {
  66. return [NSString stringWithUTF8String:class_getName([self class])];
  67. }
  68. + (NSString *)className
  69. {
  70. return [NSString stringWithUTF8String:class_getName(self)];
  71. }
  72. #endif
  73. #pragma mark -
  74. #pragma mark Public Class Methods
  75. +(NSArray *)indices
  76. {
  77. return nil;
  78. }
  79. +(SQLitePersistentObject *)findFirstByCriteria:(NSString *)criteriaString;
  80. {
  81. NSArray *array = [self findByCriteria:criteriaString];
  82. if (array != nil)
  83. if ([array count] > 0)
  84. return [array objectAtIndex:0];
  85. return nil;
  86. }
  87. + (NSInteger)count
  88. {
  89. return [self countByCriteria:@""];
  90. }
  91. + (NSInteger)countByCriteria:(NSString *)criteriaString
  92. {
  93. [self tableCheck];
  94. NSInteger countOfRecords;
  95. countOfRecords = 0;
  96. NSString *countQuery;
  97. countQuery = [NSString stringWithFormat:@"SELECT COUNT(*) FROM %@ %@", [self tableName], criteriaString];
  98. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  99. sqlite3_stmt *statement;
  100. if (sqlite3_prepare_v2(database, [countQuery UTF8String], -1, &statement, nil) == SQLITE_OK)
  101. {
  102. if (sqlite3_step(statement) == SQLITE_ROW)
  103. countOfRecords = sqlite3_column_int(statement, 0);
  104. }
  105. else NSLog(@"Error determining count of rows in table %@", [self tableName]);
  106. sqlite3_finalize(statement);
  107. return countOfRecords;
  108. }
  109. +(NSArray *)allObjects
  110. {
  111. return [[self class] findByCriteria:@""];
  112. }
  113. +(SQLitePersistentObject *)findByPK:(int)inPk
  114. {
  115. return [self findFirstByCriteria:[NSString stringWithFormat:@"WHERE pk = %d", inPk]];
  116. }
  117. +(NSArray *)findByCriteria:(NSString *)criteriaString
  118. {
  119. [[self class] tableCheck];
  120. NSMutableArray *ret = [NSMutableArray array];
  121. NSDictionary *theProps = [self propertiesWithEncodedTypes];
  122. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  123. NSString *query = [NSString stringWithFormat:@"SELECT * FROM %@ %@", [[self class] tableName], criteriaString];
  124. sqlite3_stmt *statement;
  125. if (sqlite3_prepare_v2( database, [query UTF8String], -1, &statement, NULL) == SQLITE_OK)
  126. {
  127. while (sqlite3_step(statement) == SQLITE_ROW)
  128. {
  129. BOOL foundInMemory = NO;
  130. id oneItem = [[[self class] alloc] init];
  131. int i;
  132. for (i=0; i < sqlite3_column_count(statement); i++)
  133. {
  134. NSString *colName = [NSString stringWithUTF8String:sqlite3_column_name(statement, i)];
  135. if ([colName isEqualToString:@"pk"])
  136. {
  137. [oneItem setPk:sqlite3_column_int(statement, i)];
  138. NSString *mapKey = [oneItem memoryMapKey];
  139. if ([[objectMap allKeys] containsObject:mapKey])
  140. {
  141. SQLitePersistentObject *testObject = [objectMap objectForKey:mapKey];
  142. if (testObject != nil)
  143. {
  144. // Object is already loaded, release object, and use the one in memory
  145. [oneItem release];
  146. // Retain it so that the object count matches what we had before
  147. oneItem = [testObject retain];
  148. // end the loop so we don't bother reading any more data
  149. i = sqlite3_column_count(statement) + 1;
  150. // Mark Found in memory so we don't try and load xref tables
  151. foundInMemory = YES;
  152. }
  153. }
  154. }
  155. else
  156. {
  157. NSString *propName = [colName stringAsPropertyString];
  158. NSString *colType = [theProps valueForKey:propName];
  159. if (colType == nil)
  160. break;
  161. if ([colType isEqualToString:@"i"] || // int
  162. [colType isEqualToString:@"l"] || // long
  163. [colType isEqualToString:@"q"] || // long long
  164. [colType isEqualToString:@"s"] || // short
  165. [colType isEqualToString:@"B"] ) // bool or _Bool
  166. {
  167. long long value = sqlite3_column_int64(statement, i);
  168. NSNumber *colValue = [NSNumber numberWithLongLong:value];
  169. [oneItem setValue:colValue forKey:propName];
  170. }
  171. else if ([colType isEqualToString:@"I"] || // unsigned int
  172. [colType isEqualToString:@"L"] || // usigned long
  173. [colType isEqualToString:@"Q"] || // unsigned long long
  174. [colType isEqualToString:@"S"]) // unsigned short
  175. {
  176. unsigned long long value = sqlite3_column_int64(statement, i);
  177. NSNumber *colValue = [NSNumber numberWithUnsignedLongLong:value];
  178. [oneItem setValue:colValue forKey:propName];
  179. }
  180. else if ([colType isEqualToString:@"f"] || // float
  181. [colType isEqualToString:@"d"] ) // double
  182. {
  183. NSNumber *colVal = [NSNumber numberWithFloat:sqlite3_column_double(statement, i)];
  184. [oneItem setValue:colVal forKey:propName];
  185. }
  186. else if ([colType isEqualToString:@"c"] || // char
  187. [colType isEqualToString:@"C"] ) // unsigned char
  188. {
  189. const char *colVal = (const char *)sqlite3_column_text(statement, i);
  190. NSString *colValString = [NSString stringWithUTF8String:colVal];
  191. if (colVal != nil)
  192. {
  193. if ([colValString holdsFloatingPointValue])
  194. {
  195. NSNumber *number = [NSNumber numberWithDouble:[colValString doubleValue]];
  196. [oneItem setValue:number forKey:propName];
  197. }
  198. else if ([colValString holdsIntegerValue])
  199. {
  200. NSNumber *number = [NSNumber numberWithInt:[colValString intValue]];
  201. [oneItem setValue:number forKey:propName];
  202. }
  203. else
  204. {
  205. [oneItem setValue:colValString forKey:propName];
  206. }
  207. }
  208. }
  209. else if ([colType hasPrefix:@"@"])
  210. {
  211. NSString *className = [colType substringWithRange:NSMakeRange(2, [colType length]-3)];
  212. Class propClass = objc_lookUpClass([className UTF8String]);
  213. if ([propClass isSubclassOfClass:[SQLitePersistentObject class]])
  214. {
  215. NSString *objMemoryMapKey = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement, i)];
  216. NSArray *parts = [objMemoryMapKey componentsSeparatedByString:@"-"];
  217. NSString *classString = [parts objectAtIndex:0];
  218. int fk = [[parts objectAtIndex:1] intValue];
  219. Class propClass = objc_lookUpClass([classString UTF8String]);
  220. id fkObj = [propClass findByCriteria:[NSString stringWithFormat:@"WHERE pk = %d", fk]];
  221. [oneItem setValue:fkObj forKey:propName];
  222. }
  223. else if ([propClass shouldBeStoredInBlob])
  224. {
  225. const char * rawData = sqlite3_column_blob(statement, i);
  226. int rawDataLength = sqlite3_column_bytes(statement, i);
  227. NSData *data = [NSData dataWithBytes:rawData length:rawDataLength];
  228. id colData = [propClass objectWithSQLBlobRepresentation:data];
  229. [oneItem setValue:colData forKey:propName];
  230. }
  231. else
  232. {
  233. id colData = nil;
  234. const char *columnText = (const char *)sqlite3_column_text(statement, i);
  235. if (NULL != columnText)
  236. colData = [propClass objectWithSqlColumnRepresentation:[NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement, i)]];
  237. [oneItem setValue:colData forKey:propName];
  238. }
  239. }
  240. }
  241. }
  242. if (!foundInMemory)
  243. {
  244. // Loop through properties and look for collections classes
  245. for (NSString *propName in theProps)
  246. {
  247. NSString *propType = [theProps objectForKey:propName];
  248. if ([propType hasPrefix:@"@"])
  249. {
  250. NSString *className = [propType substringWithRange:NSMakeRange(2, [propType length]-3)];
  251. if (isNSSetType(className) || isNSArrayType(className) || isNSDictionaryType(className))
  252. {
  253. if (isNSSetType(className))
  254. {
  255. NSMutableSet *set = [NSMutableSet set];
  256. [oneItem setValue:set forKey:propName];
  257. /*
  258. parent_pk INTEGER, fk INTEGER, fk_table_name TEXT, object_data TEXT
  259. */
  260. NSString *setQuery = [NSString stringWithFormat:@"SELECT fk, fk_table_name, object_data, object_class FROM %@_%@ WHERE parent_pk = %d", [[self class] tableName], [propName stringAsSQLColumnName], [oneItem pk]];
  261. sqlite3_stmt *setStmt;
  262. if (sqlite3_prepare_v2(database, [setQuery UTF8String], -1, &setStmt, NULL) == SQLITE_OK)
  263. {
  264. while (sqlite3_step(setStmt) == SQLITE_ROW)
  265. {
  266. int fk = sqlite3_column_int(setStmt, 0);
  267. if (fk > 0)
  268. {
  269. const char *fkTableNameRaw = (const char *)sqlite3_column_text(setStmt, 1);
  270. NSString *fkTableName = (fkTableNameRaw == nil) ? nil : [NSString stringWithUTF8String:fkTableNameRaw];
  271. NSString *propClassName = [[self class] classNameForTableName:fkTableName];
  272. Class propClass = objc_lookUpClass([propClassName UTF8String]);
  273. id oneObject = [propClass findFirstByCriteria:[NSString stringWithFormat:@"where pk = %d", fk]];
  274. if (oneObject != nil)
  275. [set addObject:oneObject];
  276. }
  277. else
  278. {
  279. const char *objectClassRaw = (const char *)sqlite3_column_text(setStmt, 3);
  280. NSString *objectClassName = (objectClassRaw == nil) ? nil : [NSString stringWithUTF8String:objectClassRaw];
  281. Class objectClass = objc_lookUpClass([objectClassName UTF8String]);
  282. if ([objectClass shouldBeStoredInBlob])
  283. {
  284. NSData *data = [NSData dataWithBytes:sqlite3_column_blob(setStmt, 3) length:sqlite3_column_bytes(setStmt, 3)];
  285. id theObject = [objectClass objectWithSQLBlobRepresentation:data];
  286. [set addObject:theObject];
  287. }
  288. else
  289. {
  290. const char *objectDataRaw = (const char *)sqlite3_column_text(setStmt, 2);
  291. NSString *objectData = (objectDataRaw == nil) ? nil : [NSString stringWithUTF8String:objectDataRaw];
  292. id theObject = [objectClass objectWithSqlColumnRepresentation:objectData];
  293. [set addObject:theObject];
  294. }
  295. }
  296. }
  297. }
  298. sqlite3_finalize(setStmt);
  299. }
  300. else if (isNSArrayType(className))
  301. {
  302. NSMutableArray *array = [NSMutableArray array];
  303. [oneItem setValue:array forKey:propName];
  304. NSString *arrayQuery = [NSString stringWithFormat:@"SELECT fk, fk_table_name, object_data, object_class FROM %@_%@ WHERE parent_pk = %d order by array_index", [[self class] tableName], [propName stringAsSQLColumnName], [oneItem pk]];
  305. sqlite3_stmt *arrayStmt;
  306. if (sqlite3_prepare_v2(database, [arrayQuery UTF8String], -1, &arrayStmt, NULL) == SQLITE_OK)
  307. {
  308. while (sqlite3_step(arrayStmt) == SQLITE_ROW)
  309. {
  310. int fk = sqlite3_column_int(arrayStmt, 0);
  311. if (fk > 0)
  312. {
  313. const char *fkTableNameRaw = (const char *)sqlite3_column_text(arrayStmt, 1);
  314. NSString *fkTableName = (fkTableNameRaw == nil) ? nil : [NSString stringWithUTF8String:fkTableNameRaw];
  315. NSString *propClassName = [[self class] classNameForTableName:fkTableName];
  316. Class propClass = objc_lookUpClass([propClassName UTF8String]);
  317. id oneObject = [propClass findFirstByCriteria:[NSString stringWithFormat:@"where pk = %d", fk]];
  318. if (oneObject != nil)
  319. [array addObject:oneObject];
  320. }
  321. else
  322. {
  323. const char *objectClassRaw = (const char *)sqlite3_column_text(arrayStmt, 3);
  324. NSString *objectClassName = (objectClassRaw == nil) ? nil : [NSString stringWithUTF8String:objectClassRaw];
  325. Class objectClass = objc_lookUpClass([objectClassName UTF8String]);
  326. if ([objectClass shouldBeStoredInBlob])
  327. {
  328. NSData *data = [NSData dataWithBytes:sqlite3_column_blob(arrayStmt, 3) length:sqlite3_column_bytes(arrayStmt, 3)];
  329. id theObject = [objectClass objectWithSQLBlobRepresentation:data];
  330. [array addObject:theObject];
  331. }
  332. else
  333. {
  334. const char *objectDataRaw = (const char *)sqlite3_column_text(arrayStmt, 2);
  335. NSString *objectData = (objectDataRaw == nil) ? nil : [NSString stringWithUTF8String:objectDataRaw];
  336. id theObject = [objectClass objectWithSqlColumnRepresentation:objectData];
  337. if (theObject)
  338. [array addObject:theObject];
  339. }
  340. }
  341. }
  342. }
  343. sqlite3_finalize(arrayStmt);
  344. }
  345. else if (isNSDictionaryType(className))
  346. {
  347. NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
  348. [oneItem setValue:dictionary forKey:propName];
  349. /* parent_pk integer, dictionary_key TEXT, fk INTEGER, fk_table_name TEXT, object_data BLOB, object_class */
  350. NSString *dictionaryQuery = [NSString stringWithFormat:@"SELECT dictionary_key, fk, fk_table_name, object_data, object_class FROM %@_%@ WHERE parent_pk = %d", [[self class] tableName], [propName stringAsSQLColumnName], [oneItem pk]];
  351. sqlite3_stmt *dictionaryStmt;
  352. if (sqlite3_prepare_v2(database, [dictionaryQuery UTF8String], -1, &dictionaryStmt, NULL) == SQLITE_OK)
  353. {
  354. while (sqlite3_step(dictionaryStmt) == SQLITE_ROW)
  355. {
  356. NSString *key = [NSString stringWithUTF8String:(char *)sqlite3_column_text(dictionaryStmt, 0)];
  357. int fk = sqlite3_column_int(dictionaryStmt, 1);
  358. if (fk > 0)
  359. {
  360. const char *fkTableNameRaw = (const char *)sqlite3_column_text(dictionaryStmt, 2);
  361. NSString *fkTableName = (fkTableNameRaw == nil) ? nil : [NSString stringWithUTF8String:fkTableNameRaw];
  362. NSString *propClassName = [[self class] classNameForTableName:fkTableName];
  363. Class propClass = objc_lookUpClass([propClassName UTF8String]);
  364. id oneObject = [propClass findFirstByCriteria:[NSString stringWithFormat:@"where pk = %d", fk]];
  365. if (oneObject != nil)
  366. [dictionary setObject:oneObject forKey:key];
  367. }
  368. else
  369. {
  370. const char *objectClassRaw = (const char *)sqlite3_column_text(dictionaryStmt, 4);
  371. NSString *objectClassName = (objectClassRaw == nil) ? nil : [NSString stringWithUTF8String:objectClassRaw];
  372. Class objectClass = objc_lookUpClass([objectClassName UTF8String]);
  373. if ([objectClass shouldBeStoredInBlob])
  374. {
  375. NSData *data = [NSData dataWithBytes:sqlite3_column_blob(dictionaryStmt, 3) length:sqlite3_column_bytes(dictionaryStmt, 3)];
  376. id theObject = [objectClass objectWithSQLBlobRepresentation:data];
  377. if (theObject)
  378. [dictionary setObject:theObject forKey:key];
  379. }
  380. else
  381. {
  382. const char *objectDataRaw = (const char *)sqlite3_column_text(dictionaryStmt, 3);
  383. NSString *objectData = (objectDataRaw == nil) ? nil : [NSString stringWithUTF8String:objectDataRaw];
  384. id theObject = [objectClass objectWithSqlColumnRepresentation:objectData];
  385. if (theObject != nil)
  386. [dictionary setObject:theObject forKey:key];
  387. }
  388. }
  389. }
  390. }
  391. sqlite3_finalize(dictionaryStmt);
  392. }
  393. }
  394. }
  395. }
  396. }
  397. [ret addObject:oneItem];
  398. [oneItem release];
  399. }
  400. sqlite3_finalize(statement);
  401. }
  402. return ret;
  403. }
  404. +(NSMutableDictionary *)sortedFieldValuesWithKeysForProperty:(NSString *)theProp
  405. {
  406. NSMutableDictionary *ret = [NSMutableDictionary dictionary];
  407. [[self class] tableCheck];
  408. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  409. NSString *query = [NSString stringWithFormat:@"SELECT pk, %@ FROM %@ ORDER BY %@", [theProp stringAsSQLColumnName], [[self class] tableName], [theProp stringAsSQLColumnName]];
  410. sqlite3_stmt *statement;
  411. if (sqlite3_prepare_v2( database, [query UTF8String], -1, &statement, NULL) == SQLITE_OK)
  412. {
  413. while (sqlite3_step(statement) == SQLITE_ROW)
  414. {
  415. NSNumber *thePK = [NSNumber numberWithInt:sqlite3_column_int(statement, 0)];
  416. NSString *theName = [NSString stringWithUTF8String:(const char *)sqlite3_column_text(statement, 1)];
  417. [ret setObject:thePK forKey:theName];
  418. }
  419. }
  420. sqlite3_finalize(statement);
  421. return ret;
  422. }
  423. +(NSDictionary *)propertiesWithEncodedTypes
  424. {
  425. // Recurse up the classes, but stop at NSObject. Each class only reports its own properties, not those inherited from its superclass
  426. NSMutableDictionary *theProps;
  427. if ([self superclass] != [NSObject class])
  428. theProps = (NSMutableDictionary *)[[self superclass] propertiesWithEncodedTypes];
  429. else
  430. theProps = [NSMutableDictionary dictionary];
  431. unsigned int outCount;
  432. objc_property_t *propList = class_copyPropertyList([self class], &outCount);
  433. int i;
  434. // Loop through properties and add declarations for the create
  435. for (i=0; i < outCount; i++)
  436. {
  437. objc_property_t * oneProp = propList + i;
  438. NSString *propName = [NSString stringWithUTF8String:property_getName(*oneProp)];
  439. NSString *attrs = [NSString stringWithUTF8String: property_getAttributes(*oneProp)];
  440. NSArray *attrParts = [attrs componentsSeparatedByString:@","];
  441. if (attrParts != nil)
  442. {
  443. if ([attrParts count] > 0)
  444. {
  445. NSString *propType = [[attrParts objectAtIndex:0] substringFromIndex:1];
  446. [theProps setObject:propType forKey:propName];
  447. }
  448. }
  449. }
  450. // [encodedTypesByClass setValue:theProps forKey:[self className]];
  451. return theProps;
  452. }
  453. #pragma mark -
  454. #pragma mark Public Instance Methods
  455. -(int)pk
  456. {
  457. return pk;
  458. }
  459. -(void)save
  460. {
  461. [[self class] tableCheck];
  462. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  463. // If this object is new, we need to figure out the correct primary key value,
  464. // which will be one higher than the current highest pk value in the table.
  465. if (pk < 0)
  466. {
  467. NSString *pkQuery = [NSString stringWithFormat:@"SELECT MAX(PK) FROM %@", [[self class] tableName]];
  468. sqlite3_stmt *statement;
  469. if (sqlite3_prepare_v2(database, [pkQuery UTF8String], -1, &statement, nil) == SQLITE_OK)
  470. {
  471. if (sqlite3_step(statement) == SQLITE_ROW)
  472. pk = sqlite3_column_int(statement, 0)+1;
  473. }
  474. else NSLog(@"Error determining next PK value in table %@", [[self class] tableName]);
  475. sqlite3_finalize(statement);
  476. }
  477. NSMutableString *updateSQL = [NSMutableString stringWithFormat:@"INSERT OR REPLACE INTO %@ (pk", [[self class] tableName]];
  478. NSMutableString *bindSQL = [NSMutableString string];
  479. NSDictionary *props = [[self class] propertiesWithEncodedTypes];
  480. for (NSString *propName in props)
  481. {
  482. NSString *propType = [[[self class] propertiesWithEncodedTypes] objectForKey:propName];
  483. NSString *className = @"";
  484. if ([propType hasPrefix:@"@"])
  485. className = [propType substringWithRange:NSMakeRange(2, [propType length]-3)];
  486. if (! (isNSSetType(className) || isNSArrayType(className) || isNSDictionaryType(className)))
  487. {
  488. [updateSQL appendFormat:@", %@", [propName stringAsSQLColumnName]];
  489. [bindSQL appendString:@", ?"];
  490. }
  491. }
  492. [updateSQL appendFormat:@") VALUES (?%@)", bindSQL];
  493. sqlite3_stmt *stmt;
  494. if (sqlite3_prepare_v2( database, [updateSQL UTF8String], -1, &stmt, nil) == SQLITE_OK)
  495. {
  496. int colIndex = 1;
  497. sqlite3_bind_int(stmt, colIndex++, pk);
  498. props = [[self class] propertiesWithEncodedTypes];
  499. for (NSString *propName in props)
  500. {
  501. NSString *propType = [[[self class] propertiesWithEncodedTypes] objectForKey:propName];
  502. //int colIndex = sqlite3_bind_parameter_index(stmt, [[propName stringAsSQLColumnName] UTF8String]);
  503. id theProperty = [self valueForKey:propName];
  504. if (theProperty == nil)
  505. {
  506. sqlite3_bind_null(stmt, colIndex++);
  507. }
  508. else if ([propType isEqualToString:@"i"] || // int
  509. [propType isEqualToString:@"I"] || // unsigned int
  510. [propType isEqualToString:@"l"] || // long
  511. [propType isEqualToString:@"L"] || // usigned long
  512. [propType isEqualToString:@"q"] || // long long
  513. [propType isEqualToString:@"Q"] || // unsigned long long
  514. [propType isEqualToString:@"s"] || // short
  515. [propType isEqualToString:@"S"] || // unsigned short
  516. [propType isEqualToString:@"B"] || // bool or _Bool
  517. [propType isEqualToString:@"f"] || // float
  518. [propType isEqualToString:@"d"] ) // double
  519. {
  520. sqlite3_bind_text(stmt, colIndex++, [[theProperty stringValue] UTF8String], -1, NULL);
  521. }
  522. else if ([propType isEqualToString:@"c"] || // char
  523. [propType isEqualToString:@"C"] ) // unsigned char
  524. {
  525. NSString *theString = [theProperty stringValue];
  526. sqlite3_bind_text(stmt, colIndex, [theString UTF8String], -1, NULL);
  527. }
  528. else if ([propType hasPrefix:@"@"] ) // Object
  529. {
  530. NSString *className = [propType substringWithRange:NSMakeRange(2, [propType length]-3)];
  531. if (! (isNSSetType(className) || isNSArrayType(className) || isNSDictionaryType(className)))
  532. {
  533. if ([[theProperty class] isSubclassOfClass:[SQLitePersistentObject class]])
  534. {
  535. [theProperty save];
  536. sqlite3_bind_text(stmt, colIndex++, [[theProperty memoryMapKey] UTF8String], -1, NULL);
  537. }
  538. else if ([[theProperty class] shouldBeStoredInBlob])
  539. {
  540. NSData *data = [theProperty sqlBlobRepresentationOfSelf];
  541. sqlite3_bind_blob(stmt, colIndex++, [data bytes], [data length], NULL);
  542. }
  543. else
  544. {
  545. sqlite3_bind_text(stmt, colIndex++, [[theProperty sqlColumnRepresentationOfSelf] UTF8String], -1, NULL);
  546. }
  547. }
  548. else
  549. {
  550. // Too difficult to try and figure out what's changed, just wipe rows and re-insert the current data.
  551. NSString *xrefDelete = [NSString stringWithFormat:@"delete from %@_%@ where parent_pk = %d", [[self class] tableName], [propName stringAsSQLColumnName], pk];
  552. char *errmsg = NULL;
  553. if (sqlite3_exec (database, [xrefDelete UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  554. NSLog(@"Error deleting child rows in xref table for array: %s", errmsg);
  555. sqlite3_free(errmsg);
  556. if (isNSArrayType(className))
  557. {
  558. int arrayIndex = 0;
  559. for (id oneObject in (NSArray *)theProperty)
  560. {
  561. if ([oneObject isKindOfClass:[SQLitePersistentObject class]])
  562. {
  563. [oneObject save];
  564. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, array_index, fk, fk_table_name) values (%d, %d, %d, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, arrayIndex++, [oneObject pk], [[oneObject class] tableName]];
  565. if (sqlite3_exec (database, [xrefInsert UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  566. NSLog(@"Error inserting child rows in xref table for array: %s", errmsg);
  567. sqlite3_free(errmsg);
  568. }
  569. else
  570. {
  571. if ([[oneObject class] canBeStoredInSQLite])
  572. {
  573. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, array_index, object_data, object_class) values (%d, %d, ?, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, arrayIndex++, [oneObject className]];
  574. sqlite3_stmt *xStmt;
  575. if (sqlite3_prepare_v2( database, [xrefInsert UTF8String], -1, &xStmt, nil) == SQLITE_OK)
  576. {
  577. if ([[oneObject class] shouldBeStoredInBlob])
  578. {
  579. NSData *data = [oneObject sqlBlobRepresentationOfSelf];
  580. sqlite3_bind_blob(stmt, colIndex++, [data bytes], [data length], NULL);
  581. }
  582. else
  583. {
  584. if ([[oneObject class] shouldBeStoredInBlob])
  585. {
  586. NSData *data = [oneObject sqlBlobRepresentationOfSelf];
  587. sqlite3_bind_blob(stmt, colIndex++, [data bytes], [data length], NULL);
  588. }
  589. else
  590. sqlite3_bind_text(xStmt, 1, [[oneObject sqlColumnRepresentationOfSelf] UTF8String], -1, NULL);
  591. }
  592. if (sqlite3_step(xStmt) != SQLITE_DONE)
  593. NSLog(@"Error inserting or updating cross-reference row");
  594. sqlite3_finalize(xStmt);
  595. }
  596. }
  597. else
  598. NSLog(@"Could not save object at array index: %d", arrayIndex++);
  599. }
  600. }
  601. }
  602. else if (isNSDictionaryType(className))
  603. {
  604. for (NSString *oneKey in (NSDictionary *)theProperty)
  605. {
  606. id oneObject = [(NSDictionary *)theProperty objectForKey:oneKey];
  607. if ([(NSObject *)oneObject isKindOfClass:[SQLitePersistentObject class]])
  608. {
  609. [(SQLitePersistentObject *)oneObject save];
  610. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, dictionary_key, fk, fk_table_name) values (%d, '%@', %d, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, oneKey, [(SQLitePersistentObject *)oneObject pk], [[oneObject class] tableName]];
  611. if (sqlite3_exec (database, [xrefInsert UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  612. NSLog(@"Error inserting child rows in xref table for array: %s", errmsg);
  613. sqlite3_free(errmsg);
  614. }
  615. else
  616. {
  617. if ([[oneObject class] canBeStoredInSQLite])
  618. {
  619. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, dictionary_key, object_data, object_class) values (%d, '%@', ?, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, oneKey, [oneObject className]];
  620. sqlite3_stmt *xStmt;
  621. if (sqlite3_prepare_v2( database, [xrefInsert UTF8String], -1, &xStmt, nil) == SQLITE_OK)
  622. {
  623. if ([[oneObject class] shouldBeStoredInBlob])
  624. {
  625. NSData *data = [oneObject sqlBlobRepresentationOfSelf];
  626. sqlite3_bind_blob(stmt, colIndex++, [data bytes], [data length], NULL);
  627. }
  628. else
  629. sqlite3_bind_text(xStmt, 1, [[oneObject sqlColumnRepresentationOfSelf] UTF8String], -1, NULL);
  630. if (sqlite3_step(xStmt) != SQLITE_DONE)
  631. NSLog(@"Error inserting or updating cross-reference row");
  632. sqlite3_finalize(xStmt);
  633. }
  634. }
  635. }
  636. }
  637. }
  638. else // NSSet
  639. {
  640. for (id oneObject in (NSSet *)theProperty)
  641. {
  642. if ([oneObject isKindOfClass:[SQLitePersistentObject class]])
  643. {
  644. [oneObject save];
  645. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, fk, fk_table_name) values (%d, %d, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, [oneObject pk], [[oneObject class] tableName]];
  646. if (sqlite3_exec (database, [xrefInsert UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  647. NSLog(@"Error inserting child rows in xref table for array: %s", errmsg);
  648. sqlite3_free(errmsg);
  649. }
  650. else
  651. {
  652. if ([[oneObject class] canBeStoredInSQLite])
  653. {
  654. NSString *xrefInsert = [NSString stringWithFormat:@"insert into %@_%@ (parent_pk, object_data, object_class) values (%d, ?, '%@')", [[self class] tableName], [propName stringAsSQLColumnName], pk, [oneObject className]];
  655. sqlite3_stmt *xStmt;
  656. if (sqlite3_prepare_v2( database, [xrefInsert UTF8String], -1, &xStmt, nil) == SQLITE_OK)
  657. {
  658. if ([[oneObject class] shouldBeStoredInBlob])
  659. {
  660. NSData *data = [oneObject sqlBlobRepresentationOfSelf];
  661. sqlite3_bind_blob(stmt, colIndex++, [data bytes], [data length], NULL);
  662. }
  663. else
  664. sqlite3_bind_text(xStmt, 1, [[oneObject sqlColumnRepresentationOfSelf] UTF8String], -1, NULL);
  665. if (sqlite3_step(xStmt) != SQLITE_DONE)
  666. NSLog(@"Error inserting or updating cross-reference row");
  667. sqlite3_finalize(xStmt);
  668. }
  669. }
  670. else
  671. NSLog(@"Could not save object from set");
  672. }
  673. }
  674. }
  675. }
  676. }
  677. }
  678. if (sqlite3_step(stmt) != SQLITE_DONE)
  679. NSLog(@"Error inserting or updating row");
  680. sqlite3_finalize(stmt);
  681. }
  682. // Can't register in memory map until we have PK, so do that now.
  683. if (![[objectMap allKeys] containsObject:[self memoryMapKey]])
  684. [[self class] registerObjectInMemory:self];
  685. }
  686. -(BOOL) existsInDB
  687. {
  688. return pk >= 0;
  689. }
  690. -(void)deleteObject
  691. {
  692. [self deleteObjectCascade:NO];
  693. }
  694. -(void)deleteObjectCascade:(BOOL)cascade
  695. {
  696. BOOL tableChecked = NO;
  697. if (!tableChecked)
  698. {
  699. tableChecked = YES;
  700. [[self class] tableCheck];
  701. NSString *deleteQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE pk = %d", [[self class] tableName], pk];
  702. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  703. char *errmsg = NULL;
  704. if (sqlite3_exec (database, [deleteQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  705. NSLog(@"Error deleting row in table: %s", errmsg);
  706. sqlite3_free(errmsg);
  707. NSDictionary *theProps = [[self class] propertiesWithEncodedTypes];
  708. for (NSString *prop in [theProps allKeys])
  709. {
  710. NSString *colType = [theProps valueForKey:prop];
  711. if ([colType hasPrefix:@"@"])
  712. {
  713. NSString *className = [prop substringWithRange:NSMakeRange(2, [prop length]-3)];
  714. if (isNSDictionaryType(className) || isNSArrayType(className) || isNSSetType(className))
  715. {
  716. if (cascade)
  717. {
  718. Class fkClass = objc_lookUpClass([prop UTF8String]);
  719. NSString *fkDeleteQuery = [NSString stringWithFormat:@"DELETE FROM %@ WHERE PK IN (SELECT FK FROM %@_%@_XREF WHERE pk = %d)", [fkClass tableName], [[self class] tableName], [prop stringAsSQLColumnName], pk];
  720. // Suppress the error if there was one: it's faster than checking to see if the table exists.
  721. // It may not if the property was used to store strings or another storage class but never
  722. // a subclass of SQLitePersistentObject
  723. sqlite3_exec (database, [fkDeleteQuery UTF8String], NULL, NULL, NULL);
  724. }
  725. NSString *xRefDeleteQuery = [NSString stringWithFormat:@"DELETE FROM %@_%@ WHERE parent_pk = %d", [[self class] tableName], [prop stringAsSQLColumnName], pk];
  726. if (sqlite3_exec (database, [xRefDeleteQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  727. NSLog(@"Error deleting from foreign key table: %s", errmsg);
  728. sqlite3_free(errmsg);
  729. }
  730. }
  731. }
  732. }
  733. }
  734. #pragma mark -
  735. #pragma mark NSObject Overrides
  736. + (BOOL)resolveClassMethod:(SEL)theMethod
  737. {
  738. NSString *methodBeingCalled = [NSString stringWithUTF8String: sel_getName(theMethod)];
  739. if ([methodBeingCalled hasPrefix:@"findBy"])
  740. {
  741. NSRange theRange = NSMakeRange(6, [methodBeingCalled length] - 7);
  742. NSString *property = [[methodBeingCalled substringWithRange:theRange] stringByLowercasingFirstLetter];
  743. NSDictionary *properties = [self propertiesWithEncodedTypes];
  744. if ([[properties allKeys] containsObject:property])
  745. {
  746. SEL newMethodSelector = sel_registerName([methodBeingCalled UTF8String]);
  747. // Hardcore juju here, this is not documented anywhere in the runtime (at least no
  748. // anywhere easy to find for a dope like me), but if you want to add a class method
  749. // to a class, you have to get the metaclass object and add the clas to that. If you
  750. // add the method
  751. Class selfMetaClass = objc_getMetaClass([[self className] UTF8String]);
  752. return (class_addMethod(selfMetaClass, newMethodSelector, (IMP) findByMethodImp, "@@:@")) ? YES : [super resolveClassMethod:theMethod];
  753. }
  754. else
  755. return [super resolveClassMethod:theMethod];
  756. }
  757. return [super resolveClassMethod:theMethod];
  758. }
  759. -(id)init
  760. {
  761. if (self=[super init])
  762. {
  763. pk = -1;
  764. }
  765. return self;
  766. }
  767. - (void)dealloc
  768. {
  769. [[self class] unregisterObject:self];
  770. [super dealloc];
  771. }
  772. #pragma mark -
  773. #pragma mark Private Methods
  774. + (NSString *)classNameForTableName:(NSString *)theTable
  775. {
  776. static NSMutableDictionary *classNamesForTables = nil;
  777. if (classNamesForTables == nil)
  778. classNamesForTables = [[NSMutableDictionary alloc] init];
  779. if ([[classNamesForTables allKeys] containsObject:theTable])
  780. return [classNamesForTables objectForKey:theTable];
  781. NSMutableString *ret = [NSMutableString string];
  782. BOOL lastCharacterWasUnderscore = NO;
  783. for (int i = 0; i < theTable.length; i++)
  784. {
  785. NSRange range = NSMakeRange(i, 1);
  786. NSString *oneChar = [theTable substringWithRange:range];
  787. if ([oneChar isEqualToString:@"_"])
  788. lastCharacterWasUnderscore = YES;
  789. else
  790. {
  791. if (lastCharacterWasUnderscore || i == 0)
  792. [ret appendString:[oneChar uppercaseString]];
  793. else
  794. [ret appendString:oneChar];
  795. lastCharacterWasUnderscore = NO;
  796. }
  797. }
  798. [classNamesForTables setObject:ret forKey:theTable];
  799. return ret;
  800. }
  801. + (NSString *)tableName
  802. {
  803. static NSMutableDictionary *tableNamesByClass = nil;
  804. if (tableNamesByClass == nil)
  805. tableNamesByClass = [[NSMutableDictionary alloc] init];
  806. if ([[tableNamesByClass allKeys] containsObject:[self className]])
  807. return [tableNamesByClass objectForKey:[self className]];
  808. // Note: Using a static variable to store the table name
  809. // will cause problems because the static variable will
  810. // be shared by instances of classes and their subclasses
  811. // Cache in the instances, not here...
  812. NSMutableString *ret = [NSMutableString string];
  813. NSString *className = [self className];
  814. for (int i = 0; i < className.length; i++)
  815. {
  816. NSRange range = NSMakeRange(i, 1);
  817. NSString *oneChar = [className substringWithRange:range];
  818. if ([oneChar isEqualToString:[oneChar uppercaseString]] && i > 0)
  819. [ret appendFormat:@"_%@", [oneChar lowercaseString]];
  820. else
  821. [ret appendString:[oneChar lowercaseString]];
  822. }
  823. [tableNamesByClass setObject:ret forKey:[self className]];
  824. return ret;
  825. }
  826. +(void)tableCheck
  827. {
  828. static NSMutableArray *checked = nil;
  829. if (checked == nil)
  830. checked = [[NSMutableArray alloc] init];
  831. if (![checked containsObject:[self className]])
  832. {
  833. [checked addObject:[self className]];
  834. // Do not use static variables to cache information in this method, as it will be
  835. // shared across subclasses. Do caching in instance methods.
  836. sqlite3 *database = [[SQLiteInstanceManager sharedManager] database];
  837. NSMutableString *createSQL = [NSMutableString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (pk INTEGER PRIMARY KEY",[self tableName]];
  838. for (NSString *oneProp in [[self class] propertiesWithEncodedTypes])
  839. {
  840. NSString *propName = [oneProp stringAsSQLColumnName];
  841. NSString *propType = [[[self class] propertiesWithEncodedTypes] objectForKey:oneProp];
  842. // Integer Types
  843. if ([propType isEqualToString:@"i"] || // int
  844. [propType isEqualToString:@"I"] || // unsigned int
  845. [propType isEqualToString:@"l"] || // long
  846. [propType isEqualToString:@"L"] || // usigned long
  847. [propType isEqualToString:@"q"] || // long long
  848. [propType isEqualToString:@"Q"] || // unsigned long long
  849. [propType isEqualToString:@"s"] || // short
  850. [propType isEqualToString:@"S"] || // unsigned short
  851. [propType isEqualToString:@"B"] ) // bool or _Bool
  852. {
  853. [createSQL appendFormat:@", %@ INTEGER", propName];
  854. }
  855. // Character Types
  856. else if ([propType isEqualToString:@"c"] || // char
  857. [propType isEqualToString:@"C"] ) // unsigned char
  858. {
  859. [createSQL appendFormat:@", %@ TEXT", propName];
  860. }
  861. else if ([propType isEqualToString:@"f"] || // float
  862. [propType isEqualToString:@"d"] ) // double
  863. {
  864. [createSQL appendFormat:@", %@ REAL", propName];
  865. }
  866. else if ([propType hasPrefix:@"@"] ) // Object
  867. {
  868. NSString *className = [propType substringWithRange:NSMakeRange(2, [propType length]-3)];
  869. // Collection classes have to be handled differently. Instead of adding a column, we add a child table.
  870. // Child tables will have a field for holding data and also a non-required foreign key field. If the
  871. // object stored in the collection is a subclass of SQLitePersistentObject, then it is stored as
  872. // a reference to the row in the table that holds the object. If it's not, then it is stored
  873. // in the field using the SQLitePersistence protocol methods. If it's not a subclass of
  874. // SQLitePersistentObject and doesn't conform to NSCoding then the object won't get persisted.
  875. if (isNSArrayType(className))
  876. {
  877. NSString *xRefQuery = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@_%@ (parent_pk, array_index INTEGER, fk INTEGER, fk_table_name TEXT, object_data TEXT, object_class BLOB, PRIMARY KEY (parent_pk, array_index))", [self tableName], [propName stringAsSQLColumnName]];
  878. char *errmsg = NULL;
  879. if (sqlite3_exec (database, [xRefQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  880. NSLog(@"Error Message: %s", errmsg);
  881. }
  882. else if (isNSDictionaryType(className))
  883. {
  884. NSString *xRefQuery = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@_%@ (parent_pk integer, dictionary_key TEXT, fk INTEGER, fk_table_name TEXT, object_data BLOB, object_class TEXT, PRIMARY KEY (parent_pk, dictionary_key))", [self tableName], [propName stringAsSQLColumnName]];
  885. char *errmsg = NULL;
  886. if (sqlite3_exec (database, [xRefQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  887. NSLog(@"Error Message: %s", errmsg);
  888. }
  889. else if (isNSSetType(className))
  890. {
  891. NSString *xRefQuery = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@_%@ (parent_pk INTEGER, fk INTEGER, fk_table_name TEXT, object_data BLOB, object_class TEXT)", [self tableName], [propName stringAsSQLColumnName]];
  892. char *errmsg = NULL;
  893. if (sqlite3_exec (database, [xRefQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  894. NSLog(@"Error Message: %s", errmsg);
  895. }
  896. else
  897. {
  898. Class propClass = objc_lookUpClass([className UTF8String]);
  899. if ([propClass isSubclassOfClass:[SQLitePersistentObject class]])
  900. {
  901. // Store persistent objects as quasi foreign-key reference. We don't use
  902. // datbase's referential integrity tools, but rather use the memory map
  903. // key to store the table and fk in a single text field
  904. [createSQL appendFormat:@", %@ TEXT", propName];
  905. }
  906. else if ([propClass canBeStoredInSQLite])
  907. {
  908. [createSQL appendFormat:@", %@ %@", propName, [propClass columnTypeForObjectStorage]];
  909. }
  910. }
  911. }
  912. }
  913. [createSQL appendString:@")"];
  914. char *errmsg = NULL;
  915. if (sqlite3_exec (database, [createSQL UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  916. NSLog(@"Error Message: %s", errmsg);
  917. NSArray *theIndices = [self indices];
  918. if (theIndices != nil)
  919. {
  920. if ([theIndices count] > 0)
  921. {
  922. for (NSArray *oneIndex in theIndices)
  923. {
  924. NSMutableString *indexName = [NSMutableString stringWithString:[self tableName]];
  925. NSMutableString *fieldCondition = [NSMutableString string];
  926. BOOL first = YES;
  927. for (NSString *oneField in oneIndex)
  928. {
  929. [indexName appendFormat:@"_%@", [oneField stringAsSQLColumnName]];
  930. if (first)
  931. first = NO;
  932. else
  933. [fieldCondition appendString:@", "];
  934. [fieldCondition appendString:[oneField stringAsSQLColumnName]];
  935. }
  936. NSString *indexQuery = [NSString stringWithFormat:@"create index if not exists %@ on %@ (%@)", indexName, [self tableName], fieldCondition];
  937. errmsg = NULL;
  938. if (sqlite3_exec (database, [indexQuery UTF8String], NULL, NULL, &errmsg) != SQLITE_OK)
  939. NSLog(@"Error creating indices on %@: %s", [self tableName], errmsg);
  940. }
  941. }
  942. }
  943. }
  944. }
  945. - (void)setPk:(int)newPk
  946. {
  947. pk = newPk;
  948. }
  949. #pragma mark -
  950. #pragma mark Memory Map Methods
  951. - (NSString *)memoryMapKey
  952. {
  953. return [NSString stringWithFormat:@"%@-%d", [self className], [self pk]];
  954. }
  955. + (void)registerObjectInMemory:(SQLitePersistentObject *)theObject
  956. {
  957. if (objectMap == nil)
  958. objectMap = [[NSMutableDictionary alloc] init];
  959. [objectMap setObject:theObject forKey:[theObject memoryMapKey]];
  960. }
  961. + (void)unregisterObject:(SQLitePersistentObject *)theObject
  962. {
  963. if (objectMap == nil)
  964. objectMap = [[NSMutableDictionary alloc] init];
  965. // We have to make sure we're not removing objects from memory map when deleting partially created ones...
  966. SQLitePersistentObject *compare = [objectMap objectForKey:[theObject memoryMapKey]];
  967. if (compare == theObject)
  968. [objectMap removeObjectForKey:[theObject memoryMapKey]];
  969. }
  970. @end