]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSSQLDatabaseObject.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / 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