CKKSSQLDatabaseObject.m
1 /* 2 * Copyright (c) 2016 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 #import <Foundation/Foundation.h> 25 #import "CKKSSQLDatabaseObject.h" 26 #include "keychain/securityd/SecItemServer.h" 27 #include "keychain/securityd/SecItemDb.h" 28 29 #import "keychain/ckks/CKKS.h" 30 #import "CKKSKeychainView.h" 31 32 @interface CKKSSQLResult () 33 @property (nullable) NSString* stringValue; 34 @end 35 36 @implementation CKKSSQLResult 37 - (instancetype)init:(NSString* _Nullable)value 38 { 39 if((self = [super init])) { 40 _stringValue = value; 41 } 42 return self; 43 } 44 45 - (BOOL)asBOOL 46 { 47 return [self.stringValue boolValue]; 48 } 49 50 - (NSInteger)asNSInteger 51 { 52 return [self.stringValue integerValue]; 53 } 54 55 - (NSString* _Nullable)asString 56 { 57 return self.stringValue; 58 } 59 60 - (NSNumber* _Nullable)asNSNumberInteger 61 { 62 if(self.stringValue == nil) { 63 return nil; 64 } 65 return [NSNumber numberWithInteger: [self.stringValue integerValue]]; 66 } 67 68 - (NSDate* _Nullable)asISO8601Date 69 { 70 if(self.stringValue == nil) { 71 return nil; 72 } 73 74 NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init]; 75 return [dateFormat dateFromString:self.stringValue]; 76 } 77 78 - (NSData* _Nullable)asBase64DecodedData 79 { 80 if(self.stringValue == nil) { 81 return nil; 82 } 83 return [[NSData alloc] initWithBase64EncodedString:self.stringValue options:0]; 84 } 85 @end 86 87 __thread bool CKKSSQLInTransaction = false; 88 __thread bool CKKSSQLInWriteTransaction = false; 89 90 @implementation CKKSSQLDatabaseObject 91 92 + (bool) saveToDatabaseTable: (NSString*) table row: (NSDictionary*) row connection: (SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error { 93 __block CFErrorRef cferror = NULL; 94 95 #if DEBUG 96 NSAssert(CKKSSQLInTransaction, @"Must be in a transaction to perform database writes"); 97 NSAssert(CKKSSQLInWriteTransaction, @"Must be in a write transaction to perform database writes"); 98 #endif 99 100 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) { 101 NSString * columns = [row.allKeys componentsJoinedByString:@", "]; 102 NSMutableString * values = [[NSMutableString alloc] init]; 103 for(NSUInteger i = 0; i < [row.allKeys count]; i++) { 104 if(i != 0) { 105 [values appendString: @",?"]; 106 } else { 107 [values appendString: @"?"]; 108 } 109 } 110 111 NSString *sql = [[NSString alloc] initWithFormat: @"INSERT OR REPLACE into %@ (%@) VALUES (%@);", table, columns, values]; 112 113 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) { 114 [row.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) { 115 SecDbBindObject(stmt, (int)(i+1), (__bridge CFStringRef) row[key], &cferror); 116 }]; 117 118 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) { 119 // don't do anything, I guess? 120 }); 121 }); 122 123 return true; 124 }; 125 126 if(dbconn) { 127 doWithConnection(dbconn); 128 } else { 129 kc_with_dbt(true, &cferror, doWithConnection); 130 } 131 132 bool ret = cferror == NULL; 133 134 SecTranslateError(error, cferror); 135 136 return ret; 137 } 138 139 + (NSString*) makeWhereClause: (NSDictionary*) whereDict { 140 if(!whereDict) { 141 return @""; 142 } 143 NSMutableString * whereClause = [[NSMutableString alloc] init]; 144 __block bool conjunction = false; 145 [whereDict enumerateKeysAndObjectsUsingBlock: ^(NSString* key, NSNumber* value, BOOL* stop) { 146 if(!conjunction) { 147 [whereClause appendFormat: @" WHERE "]; 148 } else { 149 [whereClause appendFormat: @" AND "]; 150 } 151 152 if([value class] == [CKKSSQLWhereValue class]) { 153 CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)value; 154 [whereClause appendFormat: @"%@%@(?)", key, CKKSSQLWhereComparatorAsString(obj.sqlOp)]; 155 156 } else if([value class] == [CKKSSQLWhereColumn class]) { 157 CKKSSQLWhereColumn* obj = (CKKSSQLWhereColumn*)value; 158 [whereClause appendFormat: @"%@%@%@", 159 key, 160 CKKSSQLWhereComparatorAsString(obj.sqlOp), 161 CKKSSQLWhereColumnNameAsString(obj.columnName)]; 162 163 } else if([value isMemberOfClass:[CKKSSQLWhereIn class]]) { 164 CKKSSQLWhereIn* obj = (CKKSSQLWhereIn*)value; 165 166 NSMutableArray* q = [NSMutableArray arrayWithCapacity:obj.values.count]; 167 for(NSString* value in obj.values) { 168 [q addObject: @"?"]; 169 (void)value; 170 } 171 172 NSString* binds = [q componentsJoinedByString:@", "]; 173 174 [whereClause appendFormat:@"%@ IN (%@)", key, binds]; 175 176 } else { 177 [whereClause appendFormat: @"%@=(?)", key]; 178 } 179 180 conjunction = true; 181 }]; 182 return whereClause; 183 } 184 185 + (NSString*) groupByClause: (NSArray*) columns { 186 if(!columns) { 187 return @""; 188 } 189 NSMutableString * groupByClause = [[NSMutableString alloc] init]; 190 __block bool conjunction = false; 191 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) { 192 if(!conjunction) { 193 [groupByClause appendFormat: @" GROUP BY "]; 194 } else { 195 [groupByClause appendFormat: @", "]; 196 } 197 198 [groupByClause appendFormat: @"%@", column]; 199 200 conjunction = true; 201 }]; 202 return groupByClause; 203 } 204 205 + (NSString*)orderByClause: (NSArray*) columns { 206 if(!columns || columns.count == 0u) { 207 return @""; 208 } 209 NSMutableString * orderByClause = [[NSMutableString alloc] init]; 210 __block bool conjunction = false; 211 [columns enumerateObjectsUsingBlock: ^(NSString* column, NSUInteger i, BOOL* stop) { 212 if(!conjunction) { 213 [orderByClause appendFormat: @" ORDER BY "]; 214 } else { 215 [orderByClause appendFormat: @", "]; 216 } 217 218 [orderByClause appendFormat: @"%@", column]; 219 220 conjunction = true; 221 }]; 222 return orderByClause; 223 } 224 225 + (void)bindWhereClause:(sqlite3_stmt*)stmt whereDict:(NSDictionary*)whereDict cferror:(CFErrorRef*)cferror 226 { 227 __block int whereLocation = 1; 228 229 [whereDict.allKeys enumerateObjectsUsingBlock:^(id _Nonnull key, NSUInteger i, BOOL * _Nonnull stop) { 230 if([whereDict[key] class] == [CKKSSQLWhereValue class]) { 231 CKKSSQLWhereValue* obj = (CKKSSQLWhereValue*)whereDict[key]; 232 SecDbBindObject(stmt, whereLocation, (__bridge CFStringRef)obj.value, cferror); 233 whereLocation++; 234 235 } else if([whereDict[key] class] == [CKKSSQLWhereColumn class]) { 236 // skip 237 } else if([whereDict[key] isMemberOfClass:[CKKSSQLWhereIn class]]) { 238 CKKSSQLWhereIn* obj = (CKKSSQLWhereIn*)whereDict[key]; 239 240 for(NSString* value in obj.values) { 241 SecDbBindObject(stmt, whereLocation, (__bridge CFStringRef)value, cferror); 242 whereLocation++; 243 } 244 245 } else { 246 SecDbBindObject(stmt, whereLocation, (__bridge CFStringRef) whereDict[key], cferror); 247 whereLocation++; 248 } 249 }]; 250 } 251 252 + (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error { 253 __block CFErrorRef cferror = NULL; 254 255 #if DEBUG 256 NSAssert(CKKSSQLInTransaction, @"Must be in a transaction to perform database writes"); 257 NSAssert(CKKSSQLInWriteTransaction, @"Must be in a write transaction to perform database writes"); 258 #endif 259 260 bool (^doWithConnection)(SecDbConnectionRef) = ^bool (SecDbConnectionRef dbconn) { 261 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict]; 262 263 NSString * sql = [[NSString alloc] initWithFormat: @"DELETE FROM %@%@;", table, whereClause]; 264 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) { 265 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; 266 267 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) { 268 }); 269 }); 270 return true; 271 }; 272 273 if(dbconn) { 274 doWithConnection(dbconn); 275 } else { 276 kc_with_dbt(true, &cferror, doWithConnection); 277 } 278 279 // Deletes finish in a single step, so if we didn't get an error, the delete 'happened' 280 bool status = (cferror == nil); 281 282 if(error) { 283 *error = (NSError*) CFBridgingRelease(cferror); 284 } else { 285 CFReleaseNull(cferror); 286 } 287 288 return status; 289 } 290 291 + (bool)queryDatabaseTable:(NSString*)table 292 where:(NSDictionary*)whereDict 293 columns:(NSArray*)names 294 groupBy:(NSArray*)groupColumns 295 orderBy:(NSArray*)orderColumns 296 limit:(ssize_t)limit 297 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*)) processRow 298 error:(NSError * __autoreleasing *) error { 299 __block CFErrorRef cferror = NULL; 300 301 kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) { 302 NSString * columns = [names componentsJoinedByString:@", "]; 303 NSString * whereClause = [CKKSSQLDatabaseObject makeWhereClause: whereDict]; 304 NSString * groupByClause = [CKKSSQLDatabaseObject groupByClause: groupColumns]; 305 NSString * orderByClause = [CKKSSQLDatabaseObject orderByClause: orderColumns]; 306 NSString * limitClause = (limit > 0 ? [NSString stringWithFormat:@" LIMIT %lu", limit] : @""); 307 308 NSString * sql = [[NSString alloc] initWithFormat: @"SELECT %@ FROM %@%@%@%@%@;", columns, table, whereClause, groupByClause, orderByClause, limitClause]; 309 SecDbPrepare(dbconn, (__bridge CFStringRef) sql, &cferror, ^void (sqlite3_stmt *stmt) { 310 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; 311 312 SecDbStep(dbconn, stmt, &cferror, ^(bool *stop) { 313 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init]; 314 315 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) { 316 const char * col = (const char *) sqlite3_column_text(stmt, (int)i); 317 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil]; 318 }]; 319 320 processRow(row); 321 }); 322 }); 323 return true; 324 }); 325 326 bool ret = (cferror == NULL); 327 SecTranslateError(error, cferror); 328 return ret; 329 } 330 331 + (NSString *)quotedString:(NSString *)string 332 { 333 char *quotedMaxField = sqlite3_mprintf("%q", [string UTF8String]); 334 if (quotedMaxField == NULL) { 335 abort(); 336 } 337 NSString *rstring = [NSString stringWithUTF8String:quotedMaxField]; 338 sqlite3_free(quotedMaxField); 339 return rstring; 340 } 341 342 + (bool)queryMaxValueForField:(NSString*)maxField 343 inTable:(NSString*)table 344 where:(NSDictionary*)whereDict 345 columns:(NSArray*)names 346 processRow:(void (^)(NSDictionary<NSString*, CKKSSQLResult*>*))processRow 347 { 348 __block CFErrorRef cferror = NULL; 349 350 kc_with_dbt(false, &cferror, ^bool(SecDbConnectionRef dbconn) { 351 NSString *quotedMaxField = [self quotedString:maxField]; 352 NSString *quotedTable = [self quotedString:table]; 353 354 NSMutableArray<NSString *>* quotedNames = [NSMutableArray array]; 355 for (NSString *element in names) { 356 [quotedNames addObject:[self quotedString:element]]; 357 } 358 359 NSString* columns = [[quotedNames componentsJoinedByString:@", "] stringByAppendingFormat:@", %@", quotedMaxField]; 360 NSString* whereClause = [CKKSSQLDatabaseObject makeWhereClause:whereDict]; 361 362 NSString* sql = [[NSString alloc] initWithFormat:@"SELECT %@ FROM %@%@", columns, quotedTable, whereClause]; 363 SecDbPrepare(dbconn, (__bridge CFStringRef)sql, &cferror, ^(sqlite3_stmt* stmt) { 364 [self bindWhereClause:stmt whereDict:whereDict cferror:&cferror]; 365 366 SecDbStep(dbconn, stmt, &cferror, ^(bool*stop) { 367 __block NSMutableDictionary<NSString*, CKKSSQLResult*>* row = [[NSMutableDictionary alloc] init]; 368 369 [names enumerateObjectsUsingBlock:^(id _Nonnull name, NSUInteger i, BOOL * _Nonnull stop) { 370 const char * col = (const char *) sqlite3_column_text(stmt, (int)i); 371 row[name] = [[CKKSSQLResult alloc] init:col ? [NSString stringWithUTF8String:col] : nil]; 372 }]; 373 374 processRow(row); 375 }); 376 }); 377 378 return true; 379 }); 380 381 bool ret = (cferror == NULL); 382 return ret; 383 } 384 385 + (BOOL)performCKKSTransaction:(CKKSDatabaseTransactionResult (^)(void))block 386 { 387 CFErrorRef cferror = NULL; 388 bool ok = kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) { 389 CFErrorRef cferrorInternal = NULL; 390 bool ret = kc_transaction_type(dbconn, kSecDbExclusiveRemoteCKKSTransactionType, &cferrorInternal, ^bool{ 391 CKKSDatabaseTransactionResult result = CKKSDatabaseTransactionRollback; 392 393 CKKSSQLInTransaction = true; 394 CKKSSQLInWriteTransaction = true; 395 result = block(); 396 CKKSSQLInWriteTransaction = false; 397 CKKSSQLInTransaction = false; 398 return result == CKKSDatabaseTransactionCommit; 399 }); 400 if(cferrorInternal) { 401 ckkserror_global("ckkssql", "error performing database transaction, major problems ahead: %@", cferrorInternal); 402 } 403 CFReleaseNull(cferrorInternal); 404 return ret; 405 }); 406 407 if(cferror) { 408 ckkserror_global("ckkssql", "error performing database operation, major problems ahead: %@", cferror); 409 } 410 CFReleaseNull(cferror); 411 return ok; 412 } 413 414 + (BOOL)performCKKSReadonlyTransaction:(void(^)(void))block 415 { 416 CFErrorRef cferror = NULL; 417 bool ok = kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbconn) { 418 CFErrorRef cferrorInternal = NULL; 419 bool ret = kc_transaction_type(dbconn, kSecDbNormalTransactionType, &cferrorInternal, ^bool{ 420 CKKSSQLInTransaction = true; 421 block(); 422 CKKSSQLInTransaction = false; 423 return true; 424 }); 425 if(cferrorInternal) { 426 ckkserror_global("ckkssql", "error performing database transaction, major problems ahead: %@", cferrorInternal); 427 } 428 CFReleaseNull(cferrorInternal); 429 return ret; 430 }); 431 432 if(cferror) { 433 ckkserror_global("ckkssql", "error performing database operation, major problems ahead: %@", cferror); 434 } 435 CFReleaseNull(cferror); 436 return ok; 437 } 438 439 #pragma mark - Instance methods 440 441 - (bool) saveToDatabase: (NSError * __autoreleasing *) error { 442 return [self saveToDatabaseWithConnection:nil error: error]; 443 } 444 445 - (bool) saveToDatabaseWithConnection: (SecDbConnectionRef) conn error: (NSError * __autoreleasing *) error { 446 // Todo: turn this into a transaction 447 448 NSDictionary* currentWhereClause = [self whereClauseToFindSelf]; 449 450 // First, if we were loaded from the database and the where clause has changed, delete the old record. 451 if(self.originalSelfWhereClause && ![self.originalSelfWhereClause isEqualToDictionary: currentWhereClause]) { 452 secdebug("ckkssql", "Primary key changed; removing old row at %@", self.originalSelfWhereClause); 453 if(![CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: self.originalSelfWhereClause connection:conn error: error]) { 454 return false; 455 } 456 } 457 458 bool ok = [CKKSSQLDatabaseObject saveToDatabaseTable: [[self class] sqlTable] 459 row: [self sqlValues] 460 connection: conn 461 error: error]; 462 463 if(ok) { 464 secdebug("ckkssql", "Saved %@", self); 465 } else { 466 secdebug("ckkssql", "Couldn't save %@: %@", self, error ? *error : @"unknown"); 467 } 468 return ok; 469 } 470 471 - (bool) deleteFromDatabase: (NSError * __autoreleasing *) error { 472 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[[self class] sqlTable] where: [self whereClauseToFindSelf] connection:nil error: error]; 473 474 if(ok) { 475 secdebug("ckkssql", "Deleted %@", self); 476 } else { 477 secdebug("ckkssql", "Couldn't delete %@: %@", self, error ? *error : @"unknown"); 478 } 479 return ok; 480 } 481 482 + (bool) deleteAll: (NSError * __autoreleasing *) error { 483 bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: nil connection:nil error: error]; 484 485 if(ok) { 486 secdebug("ckkssql", "Deleted all %@", self); 487 } else { 488 secdebug("ckkssql", "Couldn't delete all %@: %@", self, error ? *error : @"unknown"); 489 } 490 return ok; 491 } 492 493 + (instancetype) fromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error { 494 id ret = [self tryFromDatabaseWhere: whereDict error:error]; 495 496 if(!ret && error) { 497 *error = [NSError errorWithDomain:@"securityd" 498 code:errSecItemNotFound 499 userInfo:@{NSLocalizedDescriptionKey: 500 [NSString stringWithFormat: @"%@ does not exist in database where %@", [self class], whereDict]}]; 501 } 502 503 return ret; 504 } 505 506 + (instancetype _Nullable) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error { 507 __block id ret = nil; 508 509 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable] 510 where: whereDict 511 columns: [self sqlColumns] 512 groupBy: nil 513 orderBy:nil 514 limit: -1 515 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) { 516 ret = [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]; 517 } 518 error: error]; 519 520 return ret; 521 } 522 523 + (NSArray*) all: (NSError * __autoreleasing *) error { 524 return [self allWhere: nil error:error]; 525 } 526 527 + (NSArray*) allWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error { 528 __block NSMutableArray* items = [[NSMutableArray alloc] init]; 529 530 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable] 531 where: whereDict 532 columns: [self sqlColumns] 533 groupBy: nil 534 orderBy:nil 535 limit: -1 536 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) { 537 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]]; 538 } 539 error: error]; 540 541 return items; 542 } 543 544 + (NSArray*)fetch: (size_t)count error: (NSError * __autoreleasing *) error { 545 return [self fetch: count where:nil orderBy:nil error:error]; 546 } 547 548 + (NSArray*)fetch: (size_t)count where:(NSDictionary*)whereDict error: (NSError * __autoreleasing *) error { 549 return [self fetch: count where:whereDict orderBy:nil error:error]; 550 } 551 552 + (NSArray*)fetch:(size_t)count 553 where:(NSDictionary*)whereDict 554 orderBy:(NSArray*) orderColumns 555 error:(NSError * __autoreleasing *) error { 556 __block NSMutableArray* items = [[NSMutableArray alloc] init]; 557 558 [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable] 559 where: whereDict 560 columns: [self sqlColumns] 561 groupBy:nil 562 orderBy:orderColumns 563 limit: (ssize_t) count 564 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) { 565 [items addObject: [[self fromDatabaseRow: row] memoizeOriginalSelfWhereClause]]; 566 } 567 error: error]; 568 569 return items; 570 } 571 572 - (instancetype) memoizeOriginalSelfWhereClause { 573 _originalSelfWhereClause = [self whereClauseToFindSelf]; 574 return self; 575 } 576 577 #pragma mark - Subclass methods 578 579 + (instancetype)fromDatabaseRow:(NSDictionary<NSString *, CKKSSQLResult*>*)row { 580 @throw [NSException exceptionWithName:NSInternalInconsistencyException 581 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)] 582 userInfo:nil]; 583 } 584 585 + (NSString*) sqlTable { 586 @throw [NSException exceptionWithName:NSInternalInconsistencyException 587 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)] 588 userInfo:nil]; 589 } 590 591 + (NSArray<NSString*>*) sqlColumns { 592 @throw [NSException exceptionWithName:NSInternalInconsistencyException 593 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)] 594 userInfo:nil]; 595 } 596 597 - (NSDictionary<NSString*,NSString*>*) sqlValues { 598 @throw [NSException exceptionWithName:NSInternalInconsistencyException 599 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)] 600 userInfo:nil]; 601 } 602 603 - (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf { 604 @throw [NSException exceptionWithName:NSInternalInconsistencyException 605 reason:[NSString stringWithFormat:@"A subclass must override %@", NSStringFromSelector(_cmd)] 606 userInfo:nil]; 607 } 608 609 - (instancetype)copyWithZone:(NSZone *)zone { 610 CKKSSQLDatabaseObject *dbCopy = [[[self class] allocWithZone:zone] init]; 611 dbCopy->_originalSelfWhereClause = _originalSelfWhereClause; 612 return dbCopy; 613 } 614 @end 615 616 NSString* CKKSSQLWhereComparatorAsString(CKKSSQLWhereComparator comparator) 617 { 618 switch(comparator) { 619 case CKKSSQLWhereComparatorEquals: 620 return @"="; 621 case CKKSSQLWhereComparatorNotEquals: 622 return @"<>"; 623 case CKKSSQLWhereComparatorGreaterThan: 624 return @">"; 625 case CKKSSQLWhereComparatorLessThan: 626 return @"<"; 627 } 628 } 629 630 NSString* CKKSSQLWhereColumnNameAsString(CKKSSQLWhereColumnName columnName) 631 { 632 switch(columnName) { 633 case CKKSSQLWhereColumnNameUUID: 634 return @"uuid"; 635 case CKKSSQLWhereColumnNameParentKeyUUID: 636 return @"parentKeyUUID"; 637 } 638 } 639 640 #pragma mark - CKKSSQLWhereColumn 641 642 @implementation CKKSSQLWhereColumn 643 - (instancetype)initWithOperation:(CKKSSQLWhereComparator)op columnName:(CKKSSQLWhereColumnName)column 644 { 645 if((self = [super init])) { 646 _sqlOp = op; 647 _columnName = column; 648 } 649 return self; 650 } 651 + (instancetype)op:(CKKSSQLWhereComparator)op column:(CKKSSQLWhereColumnName)columnName 652 { 653 return [[CKKSSQLWhereColumn alloc] initWithOperation:op columnName:columnName]; 654 } 655 @end 656 657 #pragma mark - CKKSSQLWhereObject 658 659 @implementation CKKSSQLWhereValue 660 - (instancetype)initWithOperation:(CKKSSQLWhereComparator)op value:(NSString*)value 661 { 662 if((self = [super init])) { 663 _sqlOp = op; 664 _value = value; 665 } 666 return self; 667 } 668 + (instancetype)op:(CKKSSQLWhereComparator)op value:(NSString*)value 669 { 670 return [[CKKSSQLWhereValue alloc] initWithOperation:op value:value]; 671 672 } 673 @end 674 675 #pragma mark - CKKSSQLWhereIn 676 677 @implementation CKKSSQLWhereIn : NSObject 678 - (instancetype)initWithValues:(NSArray<NSString*>*)values 679 { 680 if((self = [super init])) { 681 _values = values; 682 } 683 return self; 684 } 685 @end