From d33ebd2468466af4b7ea282c580511e56d30e05e Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 30 Oct 2024 10:52:06 +0530 Subject: [PATCH 01/41] - Added trigger matching for "firstTimeOnly" property. - TODO: Call IsEventFirstTime from the new db/localDataStore class --- .../InApps/Matchers/CTTriggerAdapter.h | 1 + .../InApps/Matchers/CTTriggerAdapter.m | 1 + .../InApps/Matchers/CTTriggersMatcher.m | 14 ++++ .../InApps/CTTriggersMatcherTest.m | 81 +++++++++++++++++++ 4 files changed, 97 insertions(+) diff --git a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h index 6f326107..62239c97 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h +++ b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.h @@ -23,6 +23,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSInteger itemsCount; @property (nonatomic, readonly) NSInteger geoRadiusCount; @property (nonatomic, strong, readonly) NSString *profileAttrName; +@property (nonatomic, assign) BOOL firstTimeOnly; - (CTTriggerCondition * _Nullable)propertyAtIndex:(NSInteger)index; - (CTTriggerCondition * _Nullable)itemAtIndex:(NSInteger)index; diff --git a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m index c6e2eb0a..c6d7a05a 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggerAdapter.m @@ -30,6 +30,7 @@ - (instancetype)initWithJSON:(NSDictionary *)triggerJSON { self.items = triggerJSON[@"itemProperties"]; self.geoRadius = triggerJSON[@"geoRadius"]; self.profileAttrName = triggerJSON[@"profileAttrName"]; + self.firstTimeOnly = triggerJSON[@"firstTimeOnly"]; } return self; } diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index b6366b13..70a2b33c 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -41,6 +41,10 @@ - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { return NO; } + if (![self matchFirstTimeOnly:event trigger:trigger]) { + return NO; + } + if (![self matchGeoRadius:event trigger:trigger]) { return NO; } @@ -116,4 +120,14 @@ - (BOOL)matchCharged:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { return YES; } +- (BOOL)matchFirstTimeOnly:(CTEventAdapter *)event trigger:(CTTriggerAdapter *)trigger { + if (!trigger.firstTimeOnly) { + return YES; + } + // TODO: Call IsEventFirstTime from the new db/localDataStore class +// return [CTLocalDataStore IsEventFirstTime:eventName]; + + return YES; +} + @end diff --git a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m index df26c716..8ee7c1fc 100644 --- a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m +++ b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m @@ -1821,4 +1821,85 @@ - (void)testMatchEventWithGeoRadiusButNotParams { XCTAssertFalse(match); } +#pragma mark FirstTimeOnly + +- (void)testMatchEventWithFirstTimeOnly { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"firstTimeOnly": @YES + } + ]; + + // TODO: Update tests after db/localdatastore is updated with db methods + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{}]; + XCTAssertTrue(match); +} + +- (void)testMatchChargedEventWithFirstTimeOnly { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"Charged", + @"firstTimeOnly": @YES, + @"eventProperties": @[ + @{ + @"propertyName": @"prop1", + @"operator": @1, + @"propertyValue": @150 + }], + @"itemProperties": @[ + @{ + @"propertyName": @"product_name", + @"operator": @1, + @"propertyValue": @"product 1" + }] + } + ]; + + // TODO: Update tests after db/localdatastore is updated with db methods + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ + @"prop1": @150, + } items:@[ + @{ + @"product_name": @"product 1", + @"price": @5.99 + }, + @{ + @"product_name": @"product 2", + @"price": @5.50 + } + ]]; + XCTAssertTrue(match); +} + +- (void)testMatchEventFirstTimeOnlyWithGeoRadius { + NSArray *whenTriggers = @[ + @{ + @"eventName": @"event1", + @"firstTimeOnly": @YES, + @"geoRadius": @[ + @{ + @"lat": @19.07609, + @"lng": @72.877426, + @"rad": @2 + }] + } + ]; + + // Distance ~1.1km + CLLocationCoordinate2D location1km = CLLocationCoordinate2DMake(19.08609, 72.877426); + + CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:@"event1" eventProperties:@{} andLocation:location1km]; + + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + + BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:event]; + XCTAssertTrue(match); +} + @end + + From 5f0aabda47d13183845d5abc632dd049214fa84e Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 30 Oct 2024 20:07:29 +0530 Subject: [PATCH 02/41] - Storing profile key as events - Renamed one letter object names to fit the context - TODO: Call appropriate persist method from the new db/localDataStore class --- CleverTapSDK/CTLocalDataStore.m | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index e77e3482..051c95e4 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -184,23 +184,23 @@ - (void)persistEvent:(NSDictionary *)event { if (!event || !event[CLTAP_EVENT_NAME]) return; [self runOnBackgroundQueue:^{ NSString *eventName = event[CLTAP_EVENT_NAME]; - NSDictionary *s = [self getStoredEvents]; - if (!s) s = @{}; + NSDictionary *storedEvents = [self getStoredEvents]; + if (!storedEvents) storedEvents = @{}; NSTimeInterval now = [[[NSDate alloc] init] timeIntervalSince1970]; - NSArray *ed = s[eventName]; - if (!ed || ed.count < 3) { + NSArray *eventData = storedEvents[eventName]; + if (!eventData || eventData.count < 3) { // This event has been recorded for the very first time // Set the count to 0, first and last to now // Count will be incremented soon after this block - ed = @[@0.0f, @(now), @(now)]; + eventData = @[@0.0f, @(now), @(now)]; } - NSMutableArray *ped = [ed mutableCopy]; - double currentCount = ((NSNumber *) ped[0]).doubleValue; + NSMutableArray *eventDataCopy = [eventData mutableCopy]; + double currentCount = ((NSNumber *) eventDataCopy[0]).doubleValue; currentCount++; - ped[0] = @(currentCount); - ped[2] = @(now); - NSMutableDictionary *store = [s mutableCopy]; - store[eventName] = ped; + eventDataCopy[0] = @(currentCount); + eventDataCopy[2] = @(now); + NSMutableDictionary *store = [storedEvents mutableCopy]; + store[eventName] = eventDataCopy; [self setStoredEvents:store]; }]; } @@ -643,6 +643,14 @@ - (void)_setProfileValue:(id)value forKey:(NSString *)key fromUpstream:(BOOL)fro if (!fromUpstream) { [self updateLocalProfileUpdateExpiryTimeForKey:key]; } + + // PERSIST PROFILE KEY AS EVENT + NSArray *systemProfileKeys = @[CLTAP_SYS_CARRIER, CLTAP_SYS_CC, CLTAP_SYS_TZ]; + if (![systemProfileKeys containsObject:key]) { + NSDictionary *profileEvent = @{CLTAP_EVENT_NAME: key}; + // TODO: Call appropriate persist method from the new db/localDataStore class + [self persistEvent:profileEvent]; + } } @catch (NSException *exception) { CleverTapLogInternal(self.config.logLevel, @"%@: Exception setting profile field %@ in session cache for value %@", self, key, value); From ecc39c66cd82ab0891a561fd598471f5347fa8a4 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Tue, 5 Nov 2024 10:38:16 +0530 Subject: [PATCH 03/41] unit tests for data store --- CleverTapSDKTests/CTLocalDataStoreTests.m | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m index 270d2804..0def94fd 100644 --- a/CleverTapSDKTests/CTLocalDataStoreTests.m +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -10,6 +10,7 @@ #import "CTLocalDataStore.h" #import "CTProfileBuilder.h" #import "CTConstants.h" +#import "XCTestCase+XCTestCase_Tests.h" @interface CTLocalDataStoreTests : XCTestCase @property (nonatomic, strong) CTLocalDataStore *dataStore; @@ -109,4 +110,28 @@ - (void)testGetUserAttributeChangePropertiesWithIncrementCommand { OCMVerify(mockGetProfileFieldForKey); } +- (void)testSetAndGetProfileValueForKey { + NSDictionary *profile = @{@"someKey": @"someValue"}; + sleep(1); // Datastore takes a second to initialise in the background thread + [self.dataStore setProfileFields:profile]; + XCTAssertEqualObjects([self.dataStore getProfileFieldForKey:@"someKey"], @"someValue"); +} + +- (void)testSetProfileFieldWithKeyAndValue { + sleep(1); // Datastore takes a second to initialise in the background thread + [self.dataStore setProfileFieldWithKey:@"someKey" andValue:@"someValue"]; + XCTAssertEqualObjects([self.dataStore getProfileFieldForKey:@"someKey"], @"someValue"); +} + +- (void)testPersistEventAndGetEventDetail { + NSString *eventName = [self randomString]; + NSDictionary *event = @{CLTAP_EVENT_NAME: eventName}; + [self.dataStore persistEvent:event]; + sleep(1); + CleverTapEventDetail *eventDetails = [self.dataStore getEventDetail:eventName]; + XCTAssertEqual(eventDetails.count, 1); + XCTAssertGreaterThan(eventDetails.firstTime, 0); + XCTAssertGreaterThan(eventDetails.lastTime, 0); +} + @end From 8a42161a99e2e1c35139b2200c9125dda717f815 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 5 Nov 2024 11:24:27 +0530 Subject: [PATCH 04/41] feat(MC-2082) - Adds new event storage - Added new class CTEventDatabase to store user events in database. - Added create table, insert, update and check event methods. --- CleverTapSDK.xcodeproj/project.pbxproj | 16 ++ CleverTapSDK/EventDatabase/CTEventDatabase.h | 29 ++++ CleverTapSDK/EventDatabase/CTEventDatabase.m | 173 +++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 CleverTapSDK/EventDatabase/CTEventDatabase.h create mode 100644 CleverTapSDK/EventDatabase/CTEventDatabase.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 46b1ca5e..0085d12f 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -165,6 +165,8 @@ 4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */; }; 48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030F292EB50D00C06E2F /* CTLocalInApp.h */; settings = {ATTRIBUTES = (Public, ); }; }; 48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; }; + 4847D16D2CCB90E0008DC327 /* CTEventDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */; }; + 4847D16F2CCB90F5008DC327 /* CTEventDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */; }; 487854072BF4BC4E00565685 /* CTFileDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 487854062BF4BC4E00565685 /* CTFileDownloaderTests.m */; }; 48A2C4B92BD67DDC006C61BC /* sampleTXTStub.txt in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B72BD67DDB006C61BC /* sampleTXTStub.txt */; }; 48A2C4BA2BD67DDC006C61BC /* samplePDFStub.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B82BD67DDB006C61BC /* samplePDFStub.pdf */; }; @@ -772,6 +774,8 @@ 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CleverTap+PushPermission.h"; sourceTree = ""; }; 4808030F292EB50D00C06E2F /* CTLocalInApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTLocalInApp.h; sourceTree = ""; }; 48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = ""; }; + 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTEventDatabase.h; sourceTree = ""; }; + 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventDatabase.m; sourceTree = ""; }; 487854062BF4BC4E00565685 /* CTFileDownloaderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTFileDownloaderTests.m; sourceTree = ""; }; 48A2C4B72BD67DDB006C61BC /* sampleTXTStub.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = sampleTXTStub.txt; sourceTree = ""; }; 48A2C4B82BD67DDB006C61BC /* samplePDFStub.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = samplePDFStub.pdf; sourceTree = ""; }; @@ -1307,6 +1311,15 @@ name = Frameworks; sourceTree = ""; }; + 4847D16B2CCB9018008DC327 /* EventDatabase */ = { + isa = PBXGroup; + children = ( + 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */, + 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */, + ); + path = EventDatabase; + sourceTree = ""; + }; 495EA4BE25554718006CADFF /* resources */ = { isa = PBXGroup; children = ( @@ -1706,6 +1719,7 @@ D0C7BBBF207D82C0001345EF /* CleverTapSDK */ = { isa = PBXGroup; children = ( + 4847D16B2CCB9018008DC327 /* EventDatabase */, 6B9E95B62C2AE6740002D557 /* FileDownload */, 3242D7DA2B1DDA2E00A5E37A /* PrivacyInfo.xcprivacy */, 4803951A2A7ABAD200C4D254 /* CTAES.h */, @@ -1972,6 +1986,7 @@ 4E25E3C1278887A70008C888 /* CTIdentityRepo.h in Headers */, 071EB4F3217F6427008F0FAB /* CTInAppNotification.h in Headers */, F9356ED42487FE4600B4F507 /* CleverTapPushNotificationDelegate.h in Headers */, + 4847D16D2CCB90E0008DC327 /* CTEventDatabase.h in Headers */, 07B9453B219EA34300D4C542 /* CTInboxSimpleMessageCell.h in Headers */, 6BF5A5982ACEE22100CDED20 /* CleverTapJSInterfacePrivate.h in Headers */, 07053B7221E653E70085B44A /* UIView+CTToast.h in Headers */, @@ -2704,6 +2719,7 @@ 07B94540219EA34300D4C542 /* CTInboxController.m in Sources */, 4E25E3C4278887A70008C888 /* CTLegacyIdentityRepo.m in Sources */, 078C63AA22420321001FDDB8 /* CleverTapJSInterface.m in Sources */, + 4847D16F2CCB90F5008DC327 /* CTEventDatabase.m in Sources */, 4E49AE55275D24570074A774 /* CTValidationResultStack.m in Sources */, D01A0895207EC2D400423D6F /* CleverTapInstanceConfig.m in Sources */, 071EB4C8217F6427008F0FAB /* CTDismissButton.m in Sources */, diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h new file mode 100644 index 00000000..d1144dd7 --- /dev/null +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -0,0 +1,29 @@ +// +// CTEventDatabase.h +// CleverTapSDK +// +// Created by Nishant Kumar on 25/10/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import +#import "CleverTapInstanceConfig.h" + +@interface CTEventDatabase : NSObject + ++ (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config; +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config; + +- (BOOL)createTable; + +- (BOOL)insertData:(NSString *)eventName + deviceID:(NSString *)deviceID; + +- (BOOL)updateEvent:(NSString *)eventName + forDeviceID:(NSString *)deviceID; + +- (BOOL)eventExists:(NSString *)eventName + forDeviceID:(NSString *)deviceID; + +@end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m new file mode 100644 index 00000000..dd7ba446 --- /dev/null +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -0,0 +1,173 @@ +// +// CTEventDatabase.m +// CleverTapSDK +// +// Created by Nishant Kumar on 25/10/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTEventDatabase.h" +#import "CTConstants.h" + +@interface CTEventDatabase() + +@property (nonatomic, strong) CleverTapInstanceConfig *config; + +@end + +@implementation CTEventDatabase { + sqlite3 *_eventDatabase; + dispatch_queue_t _databaseQueue; +} + ++ (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config { + static CTEventDatabase *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] initWithConfig:config]; + }); + return sharedInstance; +} + +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { + if (self = [super init]) { + _config = config; + _databaseQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.eventDatabaseQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_CONCURRENT); + [self openDatabase]; + } + return self; +} + +- (NSString *)databasePath { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + return [documentsDirectory stringByAppendingPathComponent:@"CleverTap-Events.db"]; +} + +- (BOOL)openDatabase { + NSString *databasePath = [self databasePath]; + + if (sqlite3_open([databasePath UTF8String], &_eventDatabase) == SQLITE_OK) { + return YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to open database - CleverTap-Events.db", self); + return NO; + } +} + +- (void)closeDatabase { + if (_eventDatabase) { + sqlite3_close(_eventDatabase); + _eventDatabase = NULL; + } +} + +- (BOOL)createTable { + __block BOOL success = NO; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg; + const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (eventName, deviceID))"; + if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Create Table SQL error: %s", self, errMsg); + sqlite3_free(errMsg); + } + }); + + return success; +} + +- (BOOL)insertData:(NSString *)eventName + deviceID:(NSString *)deviceID { + BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; + if (eventExists) { + CleverTapLogInternal(self.config.logLevel, @"%@ Insert SQL - Event name: %@ and DeviceID: %@ already exists.", self, eventName, deviceID); + return NO; + } + + __block BOOL success = NO; + // For new event, set count as 1 + NSInteger count = 1; + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg; + NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CTUserEventLogs (eventName, count, firstTs, lastTs, deviceID) VALUES ('%@', %ld, %ld, %ld, '%@')", eventName, (long)count, (long)currentTs, (long)currentTs, deviceID]; + if (sqlite3_exec(_eventDatabase, [insertSQL UTF8String], NULL, NULL, &errMsg) == SQLITE_OK) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Insert Table SQL error: %s", self, errMsg); + sqlite3_free(errMsg); + } + }); + + return success; +} + +- (BOOL)updateEvent:(NSString *)eventName + forDeviceID:(NSString *)deviceID { + BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; + if (!eventExists) { + CleverTapLogInternal(self.config.logLevel, @"%@ Update SQL - Event name: %@ and DeviceID: %@ doesn't exists.", self, eventName, deviceID); + return NO; + } + + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + NSString *updateSQL = [NSString stringWithFormat: + @"UPDATE CTUserEventLogs SET count = count + 1, lastTs = %ld WHERE eventName = '%@' AND deviceID = '%@';", + (long)currentTs, eventName, deviceID]; + __block BOOL success = NO; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg; + int result = sqlite3_exec(_eventDatabase, [updateSQL UTF8String], NULL, NULL, &errMsg); + + if (result == SQLITE_OK) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Update Table SQL error: %s", self, errMsg); + } + }); + + return success; +} + +- (BOOL)eventExists:(NSString *)eventName + forDeviceID:(NSString *)deviceID { + NSString *query = [NSString stringWithFormat:@"SELECT COUNT(*) FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"]; + __block BOOL exists = NO; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + + if (sqlite3_prepare_v2(_eventDatabase, [query UTF8String], -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + if (sqlite3_step(statement) == SQLITE_ROW) { + // Check if the count is greater than 0 + int count = sqlite3_column_int(statement, 0); + if (count > 0) { + exists = YES; + } + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL check query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return exists; +} + +- (void)dealloc { + dispatch_sync(_databaseQueue, ^{ + [self closeDatabase]; + }); +} + +@end From 16604fc127f877715550ece59dfdeeeedf1b80d0 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 6 Nov 2024 10:26:49 +0530 Subject: [PATCH 05/41] Added getter methods and added unit tests for each method. --- CleverTapSDK.xcodeproj/project.pbxproj | 12 ++ CleverTapSDK/EventDatabase/CTEventDatabase.h | 11 ++ CleverTapSDK/EventDatabase/CTEventDatabase.m | 97 ++++++++++++ .../EventDatabase/CTEventDatabaseTests.m | 144 ++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 0085d12f..92065fbc 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -167,6 +167,7 @@ 48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; }; 4847D16D2CCB90E0008DC327 /* CTEventDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */; }; 4847D16F2CCB90F5008DC327 /* CTEventDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */; }; + 4847D1722CDA17C2008DC327 /* CTEventDatabaseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4847D1712CDA17C2008DC327 /* CTEventDatabaseTests.m */; }; 487854072BF4BC4E00565685 /* CTFileDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 487854062BF4BC4E00565685 /* CTFileDownloaderTests.m */; }; 48A2C4B92BD67DDC006C61BC /* sampleTXTStub.txt in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B72BD67DDB006C61BC /* sampleTXTStub.txt */; }; 48A2C4BA2BD67DDC006C61BC /* samplePDFStub.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B82BD67DDB006C61BC /* samplePDFStub.pdf */; }; @@ -776,6 +777,7 @@ 48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = ""; }; 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTEventDatabase.h; sourceTree = ""; }; 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventDatabase.m; sourceTree = ""; }; + 4847D1712CDA17C2008DC327 /* CTEventDatabaseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventDatabaseTests.m; sourceTree = ""; }; 487854062BF4BC4E00565685 /* CTFileDownloaderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTFileDownloaderTests.m; sourceTree = ""; }; 48A2C4B72BD67DDB006C61BC /* sampleTXTStub.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = sampleTXTStub.txt; sourceTree = ""; }; 48A2C4B82BD67DDB006C61BC /* samplePDFStub.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = samplePDFStub.pdf; sourceTree = ""; }; @@ -1320,6 +1322,14 @@ path = EventDatabase; sourceTree = ""; }; + 4847D1702CDA1788008DC327 /* EventDatabase */ = { + isa = PBXGroup; + children = ( + 4847D1712CDA17C2008DC327 /* CTEventDatabaseTests.m */, + ); + path = EventDatabase; + sourceTree = ""; + }; 495EA4BE25554718006CADFF /* resources */ = { isa = PBXGroup; children = ( @@ -1592,6 +1602,7 @@ D02AC2D9276044F70031C1BE /* CleverTapSDKTests */ = { isa = PBXGroup; children = ( + 4847D1702CDA1788008DC327 /* EventDatabase */, 6B9E95AD2C285F2F0002D557 /* FileDownload */, 4E2CF1432AC56D8F00441E8B /* CTEncryptionTests.m */, 6A4427C32AA6513C0098866F /* InApps */, @@ -2528,6 +2539,7 @@ files = ( 6BA3B2E12B05411C004E834B /* InAppHelper.m in Sources */, 32394C2529FA272600956058 /* CTValidatorTest.m in Sources */, + 4847D1722CDA17C2008DC327 /* CTEventDatabaseTests.m in Sources */, 6BB778CE2BEE48C300A41628 /* CTCustomTemplateInAppDataTest.m in Sources */, 6BA3B2E82B07E207004E834B /* CTTriggersMatcher+Tests.m in Sources */, 6BBF05CE2C58E3FB0047E3D9 /* NSURLSessionMock.m in Sources */, diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index d1144dd7..f1e6632a 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -26,4 +26,15 @@ - (BOOL)eventExists:(NSString *)eventName forDeviceID:(NSString *)deviceID; +- (NSInteger)getCountForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID; + +- (NSInteger)getFirstTimestampForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID; + +- (NSInteger)getLastTimestampForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID; + +- (BOOL)deleteTable; + @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index dd7ba446..4d6788f8 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -170,4 +170,101 @@ - (void)dealloc { }); } +- (NSInteger)getCountForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID { + const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + __block NSInteger count = 0; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + if (sqlite3_step(statement) == SQLITE_ROW) { + count = sqlite3_column_int(statement, 0); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + } + + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return count; +} + +- (NSInteger)getFirstTimestampForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID { + const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + __block NSInteger firstTs = 0; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + if (sqlite3_step(statement) == SQLITE_ROW) { + firstTs = sqlite3_column_int(statement, 0); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + } + + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return firstTs; +} + +- (NSInteger)getLastTimestampForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID { + const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + __block NSInteger lastTs = 0; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + if (sqlite3_step(statement) == SQLITE_ROW) { + lastTs = sqlite3_column_int(statement, 0); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + } + + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return lastTs; +} + +- (BOOL)deleteTable { + NSString *querySQL = [NSString stringWithFormat:@"DROP TABLE IF EXISTS CTUserEventLogs;"]; + __block BOOL success = NO; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg = NULL; + int result = sqlite3_exec(_eventDatabase, [querySQL UTF8String], NULL, NULL, &errMsg); + + if (result == SQLITE_OK) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting table CTUserEventLogs: %s", self, errMsg); + success = NO; + } + }); + + return success; +} + @end diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m new file mode 100644 index 00000000..30881a3b --- /dev/null +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -0,0 +1,144 @@ +// +// CTEventDatabaseTests.m +// CleverTapSDKTests +// +// Created by Nishant Kumar on 05/11/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import +#import "CTEventDatabase.h" +#import "CleverTapInstanceConfig.h" + +static NSString *kEventName = @"Test Event"; +static NSString *kDeviceID = @"Test Device"; + +@interface CTEventDatabaseTests : XCTestCase + +@property (nonatomic, strong) CleverTapInstanceConfig *config; +@property (nonatomic, strong) CTEventDatabase *eventDatabase; + +@end + +@implementation CTEventDatabaseTests + +- (void)setUp { + [super setUp]; + + self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; + self.eventDatabase = [CTEventDatabase sharedInstanceWithConfig:self.config]; + [self.eventDatabase createTable]; + +} + +- (void)tearDown { + [super tearDown]; + + [self.eventDatabase deleteTable]; +} + +- (void)testInsertEventName { + BOOL insertSuccess = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + XCTAssertTrue(insertSuccess); +} + +- (void)testInsertEventNameAgain { + BOOL insertSuccess = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + XCTAssertTrue(insertSuccess); + + // Insert same eventName and deviceID again + BOOL insertSuccessAgain = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + XCTAssertFalse(insertSuccessAgain); +} + +- (void)testEventNameExists { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + BOOL eventExists = [self.eventDatabase eventExists:kEventName forDeviceID:kDeviceID]; + XCTAssertTrue(eventExists); +} + +- (void)testEventNameNotExists { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + BOOL eventExists = [self.eventDatabase eventExists:@"Test Event 1" forDeviceID:kDeviceID]; + XCTAssertFalse(eventExists); +} + +- (void)testUpdateEventSuccess { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + BOOL updateSuccess = [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + XCTAssertTrue(updateSuccess); +} + +- (void)testUpdateEventFailure { + BOOL updateSuccess = [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + XCTAssertFalse(updateSuccess); +} + +- (void)testGetCountForEventName { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + + // count should be 2. + NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCount, 2); +} + +- (void)testGetCountForEventNameNotExists { + // count should be 0. + NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCount, 0); +} + +- (void)testFirstTimestampForEventName { + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + NSInteger firstTs = [self.eventDatabase getFirstTimestampForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(firstTs, currentTs); +} + +- (void)testLastTimestampForEventName { + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + NSInteger lastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(lastTs, currentTs); +} + +- (void)testLastTimestampForEventNameUpdated { + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + NSInteger lastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(lastTs, currentTs); + + NSInteger newCurrentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + NSInteger newLastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(newLastTs, newCurrentTs); + +} + +- (void)testDeleteTableSuccess { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCount, 1); + + // Delete table. + BOOL deleteSuccess = [self.eventDatabase deleteTable]; + XCTAssertTrue(deleteSuccess); + NSInteger eventCountAfterDelete = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCountAfterDelete, 0); + +} + +- (NSString *)databasePath { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + return [documentsDirectory stringByAppendingPathComponent:@"CleverTap-Events.db"]; +} + +@end From f37f04bbb19b0029f4577f92e6029d79c7b5243f Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Mon, 11 Nov 2024 12:18:04 +0530 Subject: [PATCH 06/41] added in-memory storage for first time events --- CleverTapSDK/CTLocalDataStore.h | 2 ++ CleverTapSDK/CTLocalDataStore.m | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/CleverTapSDK/CTLocalDataStore.h b/CleverTapSDK/CTLocalDataStore.h index 2bbd9c02..5c8ca8b6 100644 --- a/CleverTapSDK/CTLocalDataStore.h +++ b/CleverTapSDK/CTLocalDataStore.h @@ -44,4 +44,6 @@ - (void)changeUser; +- (BOOL)isEventLoggedFirstTime:(NSString*)eventName; + @end diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index e77e3482..c9a87210 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -15,6 +15,7 @@ #import "CTDispatchQueueManager.h" #import "CTMultiDelegateManager.h" #import "CTProfileBuilder.h" +#import "CTEventDatabase.h" static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey; static const double kProfilePersistenceIntervalSeconds = 30.f; @@ -34,6 +35,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) CTDeviceInfo *deviceInfo; @property (nonatomic, strong) NSArray *piiKeys; @property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; +@property (nonatomic, strong) NSMutableSet *userEventLogs; @end @@ -44,6 +46,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _config = config; _deviceInfo = deviceInfo; self.dispatchQueueManager = dispatchQueueManager; + self.userEventLogs = [NSMutableSet set]; localProfileUpdateExpiryStore = [NSMutableDictionary new]; _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); @@ -102,6 +105,9 @@ - (void)changeUser { self->localProfileForSession = [self _inflateLocalProfile]; } }]; + @synchronized (self.userEventLogs) { + [self.userEventLogs removeAllObjects]; + } [self clearStoredEvents]; } @@ -610,6 +616,21 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { [self removeProfileFieldsWithKeys:keys fromUpstream:NO]; } +- (BOOL)isEventLoggedFirstTime:(NSString*)eventName { + if ([self.userEventLogs containsObject:eventName]) { + return NO; + } + CTEventDatabase *dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; + NSInteger count = [dbHelper getCountForEventName:eventName deviceID:self.deviceInfo.deviceId]; + if (count > 1) { + @synchronized (self.userEventLogs) { + [self.userEventLogs addObject:eventName]; + } + + } + return count == 1; +} + #pragma mark - Private Local Profile Getters and Setters and disk persistence handling From 099452487ba8923eb54fbf9b752195bcfe018c37 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 2 Dec 2024 10:43:54 +0530 Subject: [PATCH 07/41] Added get event details method --- CleverTapSDK/EventDatabase/CTEventDatabase.h | 6 ++ CleverTapSDK/EventDatabase/CTEventDatabase.m | 67 +++++++++++++++++++ .../EventDatabase/CTEventDatabaseTests.m | 26 +++++++ 3 files changed, 99 insertions(+) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index f1e6632a..7aea0482 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -9,6 +9,7 @@ #import #import #import "CleverTapInstanceConfig.h" +#import "CleverTapEventDetail.h" @interface CTEventDatabase : NSObject @@ -35,6 +36,11 @@ - (NSInteger)getLastTimestampForEventName:(NSString *)eventName deviceID:(NSString *)deviceID; +- (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID; + +- (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID; + - (BOOL)deleteTable; @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 4d6788f8..749d6d9c 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -248,6 +248,73 @@ - (NSInteger)getLastTimestampForEventName:(NSString *)eventName return lastTs; } +- (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName + deviceID:(NSString *)deviceID { + NSString *querySQL = @"SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + __block CleverTapEventDetail *eventDetail = nil; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, [querySQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + if (sqlite3_step(statement) == SQLITE_ROW) { + const char *eventName = (const char *)sqlite3_column_text(statement, 0); + NSInteger count = sqlite3_column_int(statement, 1); + NSInteger firstTs = sqlite3_column_int(statement, 2); + NSInteger lastTs = sqlite3_column_int(statement, 3); + + eventDetail = [[CleverTapEventDetail alloc] init]; + eventDetail.count = count; + eventDetail.firstTime = firstTs; + eventDetail.lastTime = lastTs; + eventDetail.eventName = [NSString stringWithUTF8String:eventName]; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + } + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return eventDetail; +} + +- (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID { + NSString *querySQL = @"SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE deviceID = ?;"; + __block NSMutableArray *eventDataArray = [NSMutableArray array]; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, [querySQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + while (sqlite3_step(statement) == SQLITE_ROW) { + const char *eventName = (const char *)sqlite3_column_text(statement, 0); + NSInteger count = sqlite3_column_int(statement, 1); + NSInteger firstTs = sqlite3_column_int(statement, 2); + NSInteger lastTs = sqlite3_column_int(statement, 3); + + CleverTapEventDetail *ed = [[CleverTapEventDetail alloc] init]; + ed.count = count; + ed.firstTime = firstTs; + ed.lastTime = lastTs; + ed.eventName = [NSString stringWithUTF8String:eventName]; + + // Adding the CleverTapEventDetail to the result array + [eventDataArray addObject:ed]; + } + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return [eventDataArray copy]; +} + - (BOOL)deleteTable { NSString *querySQL = [NSString stringWithFormat:@"DROP TABLE IF EXISTS CTUserEventLogs;"]; __block BOOL success = NO; diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 30881a3b..cce5ebe0 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -135,6 +135,32 @@ - (void)testDeleteTableSuccess { } +- (void)testEventDetailsForDeviceID { + NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + + CleverTapEventDetail *eventDetail = [self.eventDatabase getEventDetailForEventName:kEventName deviceID:kDeviceID]; + XCTAssertEqualObjects(eventDetail.eventName, kEventName); + XCTAssertEqual(eventDetail.firstTime, currentTs); + XCTAssertEqual(eventDetail.lastTime, currentTs); + XCTAssertEqual(eventDetail.count, 1); +} + +- (void)testAllEventsForDeviceID { + [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertData:@"Test Event 1" deviceID:kDeviceID]; + [self.eventDatabase insertData:@"Test Event 2" deviceID:@"Test Device 1"]; + + NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + XCTAssertEqualObjects(allEvents[0].eventName, kEventName); + XCTAssertEqualObjects(allEvents[1].eventName, @"Test Event 1"); + XCTAssertEqual(allEvents.count, 2); + + allEvents = [self.eventDatabase getAllEventsForDeviceID:@"Test Device 1"]; + XCTAssertEqualObjects(allEvents[0].eventName, @"Test Event 2"); + XCTAssertEqual(allEvents.count, 1); +} + - (NSString *)databasePath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; From bd61452d90a3faca105b2bccc4246cd4457afd85 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 2 Dec 2024 12:06:52 +0530 Subject: [PATCH 08/41] feat(MC-2106): Add event storage limits --- CleverTapSDK/EventDatabase/CTEventDatabase.h | 3 + CleverTapSDK/EventDatabase/CTEventDatabase.m | 70 +++++++++++++++++++ .../EventDatabase/CTEventDatabaseTests.m | 36 ++++++++++ 3 files changed, 109 insertions(+) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 7aea0482..d1fa693c 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -43,4 +43,7 @@ - (BOOL)deleteTable; +- (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit + numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup; + @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 749d6d9c..32e858ca 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -9,6 +9,9 @@ #import "CTEventDatabase.h" #import "CTConstants.h" +static const NSInteger kMaxRowLimit = (2048 + 256) * 5; +static const NSInteger kNumberOfRowsToCleanup = 2048 + 256; // (2048 events + 256 profile props = 1 user) + @interface CTEventDatabase() @property (nonatomic, strong) CleverTapInstanceConfig *config; @@ -34,6 +37,10 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { _config = config; _databaseQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.eventDatabaseQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_CONCURRENT); [self openDatabase]; + + // Perform cleanup/deletion of rows on instance creation if total row count + // exceeds mac threshold limit. + [self deleteLeastRecentlyUsedRows:kMaxRowLimit numberOfRowsToCleanup:kNumberOfRowsToCleanup]; } return self; } @@ -334,4 +341,67 @@ - (BOOL)deleteTable { return success; } +- (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit + numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup { + __block BOOL success = NO; + + dispatch_sync(_databaseQueue, ^{ + // Begin a transaction to ensure atomicity + sqlite3_exec(_eventDatabase, "BEGIN TRANSACTION;", NULL, NULL, NULL); + + // Create an index on the `lastTs` column if it doesn't exist which will improve performance + // while deletion when table is large + const char *createIndexSQL = "CREATE INDEX IF NOT EXISTS idx_lastTs ON CTUserEventLogs(lastTs);"; + char *errMsg = NULL; + int indexResult = sqlite3_exec(_eventDatabase, createIndexSQL, NULL, NULL, &errMsg); + + if (indexResult != SQLITE_OK) { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to create index on lastTs: %s", self, errMsg); + sqlite3_free(errMsg); + sqlite3_exec(_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); // Rollback transaction if index creation fails + return; + } + + NSString *countQuerySQL = @"SELECT COUNT(*) FROM CTUserEventLogs;"; + sqlite3_stmt *countStatement; + if (sqlite3_prepare_v2(_eventDatabase, [countQuerySQL UTF8String], -1, &countStatement, NULL) == SQLITE_OK) { + if (sqlite3_step(countStatement) == SQLITE_ROW) { + NSInteger currentRowCount = sqlite3_column_int(countStatement, 0); + + // Calculate the number of rows to delete + NSInteger rowsToDelete = currentRowCount - (maxRowLimit - numberOfRowsToCleanup); + + if (rowsToDelete > 0) { + // Delete the least recently used rows based on lastTs + NSString *deleteSQL = [NSString stringWithFormat: + @"DELETE FROM CTUserEventLogs WHERE (eventName, deviceID) IN (" + @"SELECT eventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT %ld);", + (long)rowsToDelete]; + int result = sqlite3_exec(_eventDatabase, [deleteSQL UTF8String], NULL, NULL, &errMsg); + if (result == SQLITE_OK) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting rows: %s", self, errMsg); + sqlite3_free(errMsg); + } + } + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to count rows in CTUserEventLogs", self); + } + sqlite3_finalize(countStatement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + + // Commit or rollback the transaction based on success + if (success) { + sqlite3_exec(_eventDatabase, "COMMIT;", NULL, NULL, NULL); + } else { + sqlite3_exec(_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); + } + }); + + return success; +} + @end diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index cce5ebe0..b3ecbe13 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -161,6 +161,42 @@ - (void)testAllEventsForDeviceID { XCTAssertEqual(allEvents.count, 1); } +- (void)testLeastRecentlyUsedRowsDeleted { + int maxRow = 10; + int numberOfRowsToCleanup = 2; + int totalRowCount = 13; + for (int i = 0; i < totalRowCount; i++) { + NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; + [self.eventDatabase insertData:eventName deviceID:kDeviceID]; + } + NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + XCTAssertEqual(allEvents.count, totalRowCount); + + // When deleteLeastRecentlyUsedRows is called using max row limit and numberOfRowsToCleanup + // the deleted row count will be `totalRowCount - (maxRow - numberOfRowsToCleanup)` + [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup]; + allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + int deletedRowCount = totalRowCount - (maxRow - numberOfRowsToCleanup); + XCTAssertEqual(allEvents.count, totalRowCount - deletedRowCount); +} + +- (void)testLeastRecentlyUsedRowsNotDeleted { + int maxRow = 10; + int numberOfRowsToCleanup = 2; + int totalRowCount = 7; + for (int i = 0; i < totalRowCount; i++) { + NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; + [self.eventDatabase insertData:eventName deviceID:kDeviceID]; + } + NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + XCTAssertEqual(allEvents.count, totalRowCount); + + // Here any row will not be deleted as it is within limit. + [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup]; + allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + XCTAssertEqual(allEvents.count, totalRowCount); +} + - (NSString *)databasePath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; From ea6258798b39b4213f632787f8103061f8c0e1bd Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 3 Dec 2024 12:20:46 +0530 Subject: [PATCH 09/41] Added createTable at start in initWithConfig --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 749d6d9c..e93f710c 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -33,7 +33,10 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { if (self = [super init]) { _config = config; _databaseQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.eventDatabaseQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_CONCURRENT); - [self openDatabase]; + BOOL isDBOpen = [self openDatabase]; + if (isDBOpen) { + [self createTable]; + } } return self; } From e2bda03b0e349d10836322de047f23bbdef77342 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Tue, 3 Dec 2024 12:51:54 +0530 Subject: [PATCH 10/41] - optimised dbHelper creation - synchronised userEventLogs set access --- CleverTapSDK/CTLocalDataStore.m | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index c9a87210..36c935ba 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -36,6 +36,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) NSArray *piiKeys; @property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; @property (nonatomic, strong) NSMutableSet *userEventLogs; +@property (nonatomic, strong) CTEventDatabase *dbHelper; @end @@ -617,16 +618,19 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { } - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { - if ([self.userEventLogs containsObject:eventName]) { - return NO; + @synchronized (self.userEventLogs) { + if ([self.userEventLogs containsObject:eventName]) { + return NO; + } } - CTEventDatabase *dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; - NSInteger count = [dbHelper getCountForEventName:eventName deviceID:self.deviceInfo.deviceId]; + if (!self.dbHelper) { + self.dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; + } + NSInteger count = [self.dbHelper getCountForEventName:eventName deviceID:self.deviceInfo.deviceId]; if (count > 1) { @synchronized (self.userEventLogs) { [self.userEventLogs addObject:eventName]; } - } return count == 1; } From 5438d1ef51666df109b64ade280a99c79c117f70 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Tue, 3 Dec 2024 14:48:21 +0530 Subject: [PATCH 11/41] updated matchOnly logic to check for profile attribute name or event name --- CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index 70a2b33c..537a3112 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -125,7 +125,8 @@ - (BOOL)matchFirstTimeOnly:(CTEventAdapter *)event trigger:(CTTriggerAdapter *)t return YES; } // TODO: Call IsEventFirstTime from the new db/localDataStore class -// return [CTLocalDataStore IsEventFirstTime:eventName]; +// NSString *nameToCheck = trigger.profileAttrName ?: trigger.eventName; +// return [CTLocalDataStore IsEventFirstTime:nameToCheck]; return YES; } From 7a886f0a37b9d0a47c79749b7e86bd95f1b9bc0d Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 3 Dec 2024 17:55:43 +0530 Subject: [PATCH 12/41] Added support for database schema version --- CleverTapSDK/CTConstants.h | 3 + CleverTapSDK/EventDatabase/CTEventDatabase.h | 2 + CleverTapSDK/EventDatabase/CTEventDatabase.m | 116 +++++++++++++----- .../EventDatabase/CTEventDatabaseTests.m | 5 + 4 files changed, 98 insertions(+), 28 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c1f91ae1..d234357c 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -274,3 +274,6 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" #define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" #define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"Email", @"Phone", @"Name"]); + +#pragma mark Constants for Event Database +#define CLTAP_DATABASE_VERSION 1 diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 7aea0482..48ae0826 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -18,6 +18,8 @@ - (BOOL)createTable; +- (NSInteger)getDatabaseVersion; + - (BOOL)insertData:(NSString *)eventName deviceID:(NSString *)deviceID; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index e93f710c..774ca0f6 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -33,38 +33,11 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { if (self = [super init]) { _config = config; _databaseQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.eventDatabaseQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_CONCURRENT); - BOOL isDBOpen = [self openDatabase]; - if (isDBOpen) { - [self createTable]; - } + [self openDatabase]; } return self; } -- (NSString *)databasePath { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - return [documentsDirectory stringByAppendingPathComponent:@"CleverTap-Events.db"]; -} - -- (BOOL)openDatabase { - NSString *databasePath = [self databasePath]; - - if (sqlite3_open([databasePath UTF8String], &_eventDatabase) == SQLITE_OK) { - return YES; - } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to open database - CleverTap-Events.db", self); - return NO; - } -} - -- (void)closeDatabase { - if (_eventDatabase) { - sqlite3_close(_eventDatabase); - _eventDatabase = NULL; - } -} - - (BOOL)createTable { __block BOOL success = NO; @@ -73,6 +46,9 @@ - (BOOL)createTable { const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (eventName, deviceID))"; if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { success = YES; + + // Set the database version to the initial version, ie 1. + [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; } else { CleverTapLogInternal(self.config.logLevel, @"%@ Create Table SQL error: %s", self, errMsg); sqlite3_free(errMsg); @@ -82,6 +58,25 @@ - (BOOL)createTable { return success; } +- (NSInteger)getDatabaseVersion { + const char *querySQL = "PRAGMA user_version;"; + __block NSInteger version = 0; + + dispatch_sync(_databaseQueue, ^{ + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_step(statement) == SQLITE_ROW) { + version = sqlite3_column_int(statement, 0); + } + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + } + }); + + return version; +} + - (BOOL)insertData:(NSString *)eventName deviceID:(NSString *)deviceID { BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; @@ -337,4 +332,69 @@ - (BOOL)deleteTable { return success; } +#pragma mark - Private methods + +- (BOOL)openDatabase { + NSString *databasePath = [self databasePath]; + + if (![self isDatabaseFileExists]) { + // If the database file does not exist, create the schema for the first time + if (![self createTable]) { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to create database schema for the first time", self); + return NO; + } + } + + if (sqlite3_open([databasePath UTF8String], &_eventDatabase) == SQLITE_OK) { + // After opening, check and update the version if needed + [self checkAndUpdateDatabaseVersion]; + return YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to open database - CleverTap-Events.db", self); + return NO; + } +} + +- (void)closeDatabase { + if (_eventDatabase) { + sqlite3_close(_eventDatabase); + _eventDatabase = NULL; + } +} + +- (void)setDatabaseVersion:(NSInteger)version { + NSString *updateSQL = [NSString stringWithFormat:@"PRAGMA user_version = %ld;", (long)version]; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg; + int result = sqlite3_exec(_eventDatabase, [updateSQL UTF8String], NULL, NULL, &errMsg); + + if (result != SQLITE_OK) { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error: %s", self, errMsg); + sqlite3_free(errMsg); + } + }); +} + +- (void)checkAndUpdateDatabaseVersion { + NSInteger currentVersion = [self getDatabaseVersion]; + + if (currentVersion < CLTAP_DATABASE_VERSION) { + // Handle version changes here in future. + [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; + CleverTapLogInternal(self.config.logLevel, @"%@ Schema migration required. Current version: %ld, Target version: %ld", self, (long)currentVersion, (long)CLTAP_DATABASE_VERSION); + } +} + +- (NSString *)databasePath { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + return [documentsDirectory stringByAppendingPathComponent:@"CleverTap-Events.db"]; +} + +- (BOOL)isDatabaseFileExists { + NSString *databasePath = [self databasePath]; + return [[NSFileManager defaultManager] fileExistsAtPath:databasePath]; +} + @end diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index cce5ebe0..0f565abd 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -37,6 +37,11 @@ - (void)tearDown { [self.eventDatabase deleteTable]; } +- (void)testGetDatabaseVersion { + NSInteger dbVersion = [self.eventDatabase getDatabaseVersion]; + XCTAssertEqual(dbVersion, 1); +} + - (void)testInsertEventName { BOOL insertSuccess = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; XCTAssertTrue(insertSuccess); From 0598ae1fd55fdb0e599c3298abb2a7d7e0dd8718 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 3 Dec 2024 18:54:20 +0530 Subject: [PATCH 13/41] Updated query to use sqlite3 bind methods instead of NSString. --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 94 +++++++++++++------- 1 file changed, 60 insertions(+), 34 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 774ca0f6..9dc9002c 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -89,15 +89,27 @@ - (BOOL)insertData:(NSString *)eventName // For new event, set count as 1 NSInteger count = 1; NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + const char *insertSQL = "INSERT INTO CTUserEventLogs (eventName, count, firstTs, lastTs, deviceID) VALUES (?, ?, ?, ?, ?)"; dispatch_sync(_databaseQueue, ^{ - char *errMsg; - NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CTUserEventLogs (eventName, count, firstTs, lastTs, deviceID) VALUES ('%@', %ld, %ld, %ld, '%@')", eventName, (long)count, (long)currentTs, (long)currentTs, deviceID]; - if (sqlite3_exec(_eventDatabase, [insertSQL UTF8String], NULL, NULL, &errMsg) == SQLITE_OK) { - success = YES; + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, insertSQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_int(statement, 2, (int)count); + sqlite3_bind_int(statement, 3, (int)currentTs); + sqlite3_bind_int(statement, 4, (int)currentTs); + sqlite3_bind_text(statement, 5, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + int result = sqlite3_step(statement); + if (result == SQLITE_DONE) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Insert Table SQL error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + } + + sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Insert Table SQL error: %s", self, errMsg); - sqlite3_free(errMsg); + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare insert statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); } }); @@ -113,19 +125,27 @@ - (BOOL)updateEvent:(NSString *)eventName } NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - NSString *updateSQL = [NSString stringWithFormat: - @"UPDATE CTUserEventLogs SET count = count + 1, lastTs = %ld WHERE eventName = '%@' AND deviceID = '%@';", - (long)currentTs, eventName, deviceID]; + const char *updateSQL = + "UPDATE CTUserEventLogs SET count = count + 1, lastTs = ? WHERE eventName = ? AND deviceID = ?"; __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ - char *errMsg; - int result = sqlite3_exec(_eventDatabase, [updateSQL UTF8String], NULL, NULL, &errMsg); - - if (result == SQLITE_OK) { - success = YES; + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_int(statement, 1, (int)currentTs); + sqlite3_bind_text(statement, 2, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 3, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + + int result = sqlite3_step(statement); + if (result == SQLITE_DONE) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Update Table SQL error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + } + + sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Update Table SQL error: %s", self, errMsg); + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare update statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); } }); @@ -134,13 +154,13 @@ - (BOOL)updateEvent:(NSString *)eventName - (BOOL)eventExists:(NSString *)eventName forDeviceID:(NSString *)deviceID { - NSString *query = [NSString stringWithFormat:@"SELECT COUNT(*) FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"]; + const char *query = "SELECT COUNT(*) FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block BOOL exists = NO; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, [query UTF8String], -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(_eventDatabase, query, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -170,7 +190,7 @@ - (void)dealloc { - (NSInteger)getCountForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { - const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger count = 0; dispatch_sync(_databaseQueue, ^{ @@ -196,7 +216,7 @@ - (NSInteger)getCountForEventName:(NSString *)eventName - (NSInteger)getFirstTimestampForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { - const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger firstTs = 0; dispatch_sync(_databaseQueue, ^{ @@ -222,7 +242,7 @@ - (NSInteger)getFirstTimestampForEventName:(NSString *)eventName - (NSInteger)getLastTimestampForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { - const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger lastTs = 0; dispatch_sync(_databaseQueue, ^{ @@ -248,12 +268,12 @@ - (NSInteger)getLastTimestampForEventName:(NSString *)eventName - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { - NSString *querySQL = @"SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?;"; + const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block CleverTapEventDetail *eventDetail = nil; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, [querySQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -281,12 +301,12 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName } - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID { - NSString *querySQL = @"SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE deviceID = ?;"; + const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE deviceID = ?"; __block NSMutableArray *eventDataArray = [NSMutableArray array]; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, [querySQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [deviceID UTF8String], -1, SQLITE_TRANSIENT); while (sqlite3_step(statement) == SQLITE_ROW) { @@ -314,12 +334,12 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName } - (BOOL)deleteTable { - NSString *querySQL = [NSString stringWithFormat:@"DROP TABLE IF EXISTS CTUserEventLogs;"]; + const char *querySQL = "DROP TABLE IF EXISTS CTUserEventLogs"; __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ char *errMsg = NULL; - int result = sqlite3_exec(_eventDatabase, [querySQL UTF8String], NULL, NULL, &errMsg); + int result = sqlite3_exec(_eventDatabase, querySQL, NULL, NULL, &errMsg); if (result == SQLITE_OK) { success = YES; @@ -363,16 +383,22 @@ - (void)closeDatabase { } - (void)setDatabaseVersion:(NSInteger)version { - NSString *updateSQL = [NSString stringWithFormat:@"PRAGMA user_version = %ld;", (long)version]; + const char *updateSQL = "PRAGMA user_version = ?"; dispatch_sync(_databaseQueue, ^{ - char *errMsg; - int result = sqlite3_exec(_eventDatabase, [updateSQL UTF8String], NULL, NULL, &errMsg); - - if (result != SQLITE_OK) { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error: %s", self, errMsg); - sqlite3_free(errMsg); - } + sqlite3_stmt *statement; + if (sqlite3_prepare_v2(self->_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { + sqlite3_bind_int(statement, 1, (int)version); + + int result = sqlite3_step(statement); + if (result != SQLITE_OK) { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + } + + sqlite3_finalize(statement); + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare update statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); + } }); } From 5f42753b40e627941b5fae85002d218de5a1fd5e Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 4 Dec 2024 11:48:22 +0530 Subject: [PATCH 14/41] Added safety null check for database is open or not before every sql query. --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 64 +++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 9dc9002c..17a3e30b 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -39,6 +39,11 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { } - (BOOL)createTable { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ @@ -59,6 +64,11 @@ - (BOOL)createTable { } - (NSInteger)getDatabaseVersion { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return 0; + } + const char *querySQL = "PRAGMA user_version;"; __block NSInteger version = 0; @@ -79,6 +89,11 @@ - (NSInteger)getDatabaseVersion { - (BOOL)insertData:(NSString *)eventName deviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; if (eventExists) { CleverTapLogInternal(self.config.logLevel, @"%@ Insert SQL - Event name: %@ and DeviceID: %@ already exists.", self, eventName, deviceID); @@ -118,6 +133,11 @@ - (BOOL)insertData:(NSString *)eventName - (BOOL)updateEvent:(NSString *)eventName forDeviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; if (!eventExists) { CleverTapLogInternal(self.config.logLevel, @"%@ Update SQL - Event name: %@ and DeviceID: %@ doesn't exists.", self, eventName, deviceID); @@ -154,6 +174,11 @@ - (BOOL)updateEvent:(NSString *)eventName - (BOOL)eventExists:(NSString *)eventName forDeviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + const char *query = "SELECT COUNT(*) FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block BOOL exists = NO; @@ -190,6 +215,11 @@ - (void)dealloc { - (NSInteger)getCountForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return 0; + } + const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger count = 0; @@ -216,6 +246,11 @@ - (NSInteger)getCountForEventName:(NSString *)eventName - (NSInteger)getFirstTimestampForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return 0; + } + const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger firstTs = 0; @@ -242,6 +277,11 @@ - (NSInteger)getFirstTimestampForEventName:(NSString *)eventName - (NSInteger)getLastTimestampForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return 0; + } + const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block NSInteger lastTs = 0; @@ -268,6 +308,11 @@ - (NSInteger)getLastTimestampForEventName:(NSString *)eventName - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName deviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return nil; + } + const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; __block CleverTapEventDetail *eventDetail = nil; @@ -301,6 +346,11 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName } - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return nil; + } + const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE deviceID = ?"; __block NSMutableArray *eventDataArray = [NSMutableArray array]; @@ -334,6 +384,11 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName } - (BOOL)deleteTable { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + const char *querySQL = "DROP TABLE IF EXISTS CTUserEventLogs"; __block BOOL success = NO; @@ -383,11 +438,16 @@ - (void)closeDatabase { } - (void)setDatabaseVersion:(NSInteger)version { - const char *updateSQL = "PRAGMA user_version = ?"; + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return; + } + + NSString *updateSQL = [NSString stringWithFormat:@"PRAGMA user_version = %ld;", (long)version]; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(self->_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, [updateSQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_int(statement, 1, (int)version); int result = sqlite3_step(statement); From 560afa70973a9c2ed0f2a71337a81c4d974f4705 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 4 Dec 2024 12:58:59 +0530 Subject: [PATCH 15/41] added todo to CTLocalDataStore.m to add normalisation --- CleverTapSDK/CTLocalDataStore.m | 1 + 1 file changed, 1 insertion(+) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 36c935ba..0b8d29d6 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -619,6 +619,7 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { @synchronized (self.userEventLogs) { + // TODO: add normalisation if ([self.userEventLogs containsObject:eventName]) { return NO; } From 7b9c9d3f79cdacc41c706655040b29eedfc093e8 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 4 Dec 2024 13:58:47 +0530 Subject: [PATCH 16/41] Added constants to CTConstants file --- CleverTapSDK/CTConstants.h | 3 +++ CleverTapSDK/EventDatabase/CTEventDatabase.m | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CleverTapSDK/CTConstants.h b/CleverTapSDK/CTConstants.h index c1f91ae1..07d35054 100644 --- a/CleverTapSDK/CTConstants.h +++ b/CleverTapSDK/CTConstants.h @@ -274,3 +274,6 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY; #define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel" #define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_" #define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"Email", @"Phone", @"Name"]); + +#define CLTAP_EVENT_DB_MAX_ROW_LIMIT (2048 + 256) * 5; +#define CLTAP_EVENT_DB_ROWS_TO_CLEANUP 2048 + 256; // (2048 events + 256 profile props = 1 user) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 32e858ca..be90ff2a 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -9,9 +9,6 @@ #import "CTEventDatabase.h" #import "CTConstants.h" -static const NSInteger kMaxRowLimit = (2048 + 256) * 5; -static const NSInteger kNumberOfRowsToCleanup = 2048 + 256; // (2048 events + 256 profile props = 1 user) - @interface CTEventDatabase() @property (nonatomic, strong) CleverTapInstanceConfig *config; @@ -40,7 +37,9 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { // Perform cleanup/deletion of rows on instance creation if total row count // exceeds mac threshold limit. - [self deleteLeastRecentlyUsedRows:kMaxRowLimit numberOfRowsToCleanup:kNumberOfRowsToCleanup]; + NSInteger maxRowLimit = CLTAP_EVENT_DB_MAX_ROW_LIMIT; + NSInteger numberOfRowsToCleanup = CLTAP_EVENT_DB_ROWS_TO_CLEANUP; + [self deleteLeastRecentlyUsedRows:maxRowLimit numberOfRowsToCleanup:numberOfRowsToCleanup]; } return self; } From 621cc6b799c0eaa1b8f42c22eff10c38a325039e Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Wed, 4 Dec 2024 14:17:01 +0530 Subject: [PATCH 17/41] Updated delete sql to use sqlite3 bind method --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 37 ++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index be90ff2a..37fec520 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -342,6 +342,11 @@ - (BOOL)deleteTable { - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ @@ -366,22 +371,26 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit if (sqlite3_prepare_v2(_eventDatabase, [countQuerySQL UTF8String], -1, &countStatement, NULL) == SQLITE_OK) { if (sqlite3_step(countStatement) == SQLITE_ROW) { NSInteger currentRowCount = sqlite3_column_int(countStatement, 0); - - // Calculate the number of rows to delete - NSInteger rowsToDelete = currentRowCount - (maxRowLimit - numberOfRowsToCleanup); - - if (rowsToDelete > 0) { + if (currentRowCount > maxRowLimit) { + // Calculate the number of rows to delete + NSInteger rowsToDelete = currentRowCount - (maxRowLimit - numberOfRowsToCleanup); + // Delete the least recently used rows based on lastTs - NSString *deleteSQL = [NSString stringWithFormat: - @"DELETE FROM CTUserEventLogs WHERE (eventName, deviceID) IN (" - @"SELECT eventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT %ld);", - (long)rowsToDelete]; - int result = sqlite3_exec(_eventDatabase, [deleteSQL UTF8String], NULL, NULL, &errMsg); - if (result == SQLITE_OK) { - success = YES; + const char *deleteSQL = "DELETE FROM CTUserEventLogs WHERE (eventName, deviceID) IN (SELECT eventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT ?);"; + sqlite3_stmt *deleteStatement; + if (sqlite3_prepare_v2(_eventDatabase, deleteSQL, -1, &deleteStatement, NULL) == SQLITE_OK) { + sqlite3_bind_int(deleteStatement, 1, (int)rowsToDelete); + + int result = sqlite3_step(deleteStatement); + if (result == SQLITE_DONE) { + success = YES; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting rows: %s", self, sqlite3_errmsg(_eventDatabase)); + } + + sqlite3_finalize(deleteStatement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting rows: %s", self, errMsg); - sqlite3_free(errMsg); + CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); } } } else { From 1865952df3521467b4d40c90406a092565ea023b Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Fri, 6 Dec 2024 11:12:15 +0530 Subject: [PATCH 18/41] feat(MC-2430): Normalize event names and query based on normalized event. --- CleverTapSDK/CTLocalDataStore.m | 3 +- CleverTapSDK/CleverTapEventDetail.h | 2 + CleverTapSDK/CleverTapEventDetail.m | 4 +- CleverTapSDK/EventDatabase/CTEventDatabase.h | 25 ++-- CleverTapSDK/EventDatabase/CTEventDatabase.m | 101 +++++++------- .../EventDatabase/CTEventDatabaseTests.m | 127 +++++++++++++----- 6 files changed, 169 insertions(+), 93 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index d94c3990..7dd020e3 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -627,7 +627,8 @@ - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { if (!self.dbHelper) { self.dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; } - NSInteger count = [self.dbHelper getCountForEventName:eventName deviceID:self.deviceInfo.deviceId]; + // TODO: Add normalized name here + NSInteger count = [self.dbHelper getEventCount:eventName deviceID:self.deviceInfo.deviceId]; if (count > 1) { @synchronized (self.userEventLogs) { [self.userEventLogs addObject:eventName]; diff --git a/CleverTapSDK/CleverTapEventDetail.h b/CleverTapSDK/CleverTapEventDetail.h index e211c743..830595ee 100644 --- a/CleverTapSDK/CleverTapEventDetail.h +++ b/CleverTapSDK/CleverTapEventDetail.h @@ -3,8 +3,10 @@ @interface CleverTapEventDetail : NSObject @property (nonatomic, strong) NSString *eventName; +@property (nonatomic, strong) NSString *normalizedEventName; @property (nonatomic) NSTimeInterval firstTime; @property (nonatomic) NSTimeInterval lastTime; @property (nonatomic) NSUInteger count; +@property (nonatomic, strong) NSString *deviceID; @end diff --git a/CleverTapSDK/CleverTapEventDetail.m b/CleverTapSDK/CleverTapEventDetail.m index f70844d1..90f9356d 100644 --- a/CleverTapSDK/CleverTapEventDetail.m +++ b/CleverTapSDK/CleverTapEventDetail.m @@ -3,8 +3,8 @@ @implementation CleverTapEventDetail - (NSString*) description { - return [NSString stringWithFormat:@"CleverTapEventDetail (event name = %@; first time = %d, last time = %d; count = %lu)", - self.eventName, (int) self.firstTime, (int) self.lastTime, (unsigned long)self.count]; + return [NSString stringWithFormat:@"CleverTapEventDetail (event name = %@; normalized event name = %@; first time = %d, last time = %d; count = %lu; device ID = %@)", + self.eventName, self.normalizedEventName, (int) self.firstTime, (int) self.lastTime, (unsigned long)self.count, self.deviceID]; } @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index b95ad6bf..850f2672 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -20,26 +20,27 @@ - (NSInteger)getDatabaseVersion; -- (BOOL)insertData:(NSString *)eventName - deviceID:(NSString *)deviceID; +- (BOOL)insertEvent:(NSString *)eventName +normalizedEventName:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; -- (BOOL)updateEvent:(NSString *)eventName +- (BOOL)updateEvent:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID; -- (BOOL)eventExists:(NSString *)eventName +- (BOOL)eventExists:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID; -- (NSInteger)getCountForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID; +- (NSInteger)getEventCount:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; -- (NSInteger)getFirstTimestampForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID; +- (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; -- (NSInteger)getLastTimestampForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID; +- (NSInteger)getLastTimestamp:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; -- (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID; +- (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index d6adefae..612fa6cb 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -54,7 +54,7 @@ - (BOOL)createTable { dispatch_sync(_databaseQueue, ^{ char *errMsg; - const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (eventName, deviceID))"; + const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, normalizedEventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (normalizedEventName, deviceID))"; if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { success = YES; @@ -93,14 +93,15 @@ - (NSInteger)getDatabaseVersion { return version; } -- (BOOL)insertData:(NSString *)eventName - deviceID:(NSString *)deviceID { +- (BOOL)insertEvent:(NSString *)eventName +normalizedEventName:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; + BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; if (eventExists) { CleverTapLogInternal(self.config.logLevel, @"%@ Insert SQL - Event name: %@ and DeviceID: %@ already exists.", self, eventName, deviceID); return NO; @@ -110,16 +111,17 @@ - (BOOL)insertData:(NSString *)eventName // For new event, set count as 1 NSInteger count = 1; NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - const char *insertSQL = "INSERT INTO CTUserEventLogs (eventName, count, firstTs, lastTs, deviceID) VALUES (?, ?, ?, ?, ?)"; + const char *insertSQL = "INSERT INTO CTUserEventLogs (eventName, normalizedEventName, count, firstTs, lastTs, deviceID) VALUES (?, ?, ?, ?, ?, ?)"; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, insertSQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_int(statement, 2, (int)count); - sqlite3_bind_int(statement, 3, (int)currentTs); + sqlite3_bind_text(statement, 2, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_int(statement, 3, (int)count); sqlite3_bind_int(statement, 4, (int)currentTs); - sqlite3_bind_text(statement, 5, [deviceID UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_int(statement, 5, (int)currentTs); + sqlite3_bind_text(statement, 6, [deviceID UTF8String], -1, SQLITE_TRANSIENT); int result = sqlite3_step(statement); if (result == SQLITE_DONE) { @@ -137,29 +139,29 @@ - (BOOL)insertData:(NSString *)eventName return success; } -- (BOOL)updateEvent:(NSString *)eventName +- (BOOL)updateEvent:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - BOOL eventExists = [self eventExists:eventName forDeviceID:deviceID]; + BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; if (!eventExists) { - CleverTapLogInternal(self.config.logLevel, @"%@ Update SQL - Event name: %@ and DeviceID: %@ doesn't exists.", self, eventName, deviceID); + CleverTapLogInternal(self.config.logLevel, @"%@ Update SQL - Event name: %@ and DeviceID: %@ doesn't exists.", self, normalizedEventName, deviceID); return NO; } NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; const char *updateSQL = - "UPDATE CTUserEventLogs SET count = count + 1, lastTs = ? WHERE eventName = ? AND deviceID = ?"; + "UPDATE CTUserEventLogs SET count = count + 1, lastTs = ? WHERE normalizedEventName = ? AND deviceID = ?"; __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_int(statement, 1, (int)currentTs); - sqlite3_bind_text(statement, 2, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 2, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 3, [deviceID UTF8String], -1, SQLITE_TRANSIENT); int result = sqlite3_step(statement); @@ -178,21 +180,21 @@ - (BOOL)updateEvent:(NSString *)eventName return success; } -- (BOOL)eventExists:(NSString *)eventName +- (BOOL)eventExists:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - const char *query = "SELECT COUNT(*) FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; + const char *query = "SELECT COUNT(*) FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block BOOL exists = NO; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, query, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(statement) == SQLITE_ROW) { @@ -219,26 +221,26 @@ - (void)dealloc { }); } -- (NSInteger)getCountForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID { +- (NSInteger)getEventCount:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return 0; } - const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; + const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger count = 0; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(statement) == SQLITE_ROW) { count = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); } sqlite3_finalize(statement); @@ -250,26 +252,26 @@ - (NSInteger)getCountForEventName:(NSString *)eventName return count; } -- (NSInteger)getFirstTimestampForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID { +- (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return 0; } - const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; + const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger firstTs = 0; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(statement) == SQLITE_ROW) { firstTs = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); } sqlite3_finalize(statement); @@ -281,26 +283,26 @@ - (NSInteger)getFirstTimestampForEventName:(NSString *)eventName return firstTs; } -- (NSInteger)getLastTimestampForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID { +- (NSInteger)getLastTimestamp:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return 0; } - const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; + const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger lastTs = 0; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(statement) == SQLITE_ROW) { lastTs = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); } sqlite3_finalize(statement); @@ -312,35 +314,40 @@ - (NSInteger)getLastTimestampForEventName:(NSString *)eventName return lastTs; } -- (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName - deviceID:(NSString *)deviceID { +- (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return nil; } - const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE eventName = ? AND deviceID = ?"; + const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block CleverTapEventDetail *eventDetail = nil; dispatch_sync(_databaseQueue, ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); + sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); if (sqlite3_step(statement) == SQLITE_ROW) { const char *eventName = (const char *)sqlite3_column_text(statement, 0); - NSInteger count = sqlite3_column_int(statement, 1); - NSInteger firstTs = sqlite3_column_int(statement, 2); - NSInteger lastTs = sqlite3_column_int(statement, 3); + const char *normalizedEventName = (const char *)sqlite3_column_text(statement, 1); + NSInteger count = sqlite3_column_int(statement, 2); + NSInteger firstTs = sqlite3_column_int(statement, 3); + NSInteger lastTs = sqlite3_column_int(statement, 4); + const char *deviceID = (const char *)sqlite3_column_text(statement, 5); eventDetail = [[CleverTapEventDetail alloc] init]; + eventDetail.eventName = [NSString stringWithUTF8String:eventName]; + eventDetail.normalizedEventName = [NSString stringWithUTF8String:normalizedEventName]; eventDetail.count = count; eventDetail.firstTime = firstTs; eventDetail.lastTime = lastTs; - eventDetail.eventName = [NSString stringWithUTF8String:eventName]; + eventDetail.deviceID = [NSString stringWithUTF8String:deviceID]; + } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, eventName, deviceID); + CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); } sqlite3_finalize(statement); } else { @@ -357,7 +364,7 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName return nil; } - const char *querySQL = "SELECT eventName, count, firstTs, lastTs FROM CTUserEventLogs WHERE deviceID = ?"; + const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE deviceID = ?"; __block NSMutableArray *eventDataArray = [NSMutableArray array]; dispatch_sync(_databaseQueue, ^{ @@ -367,15 +374,19 @@ - (CleverTapEventDetail *)getEventDetailForEventName:(NSString *)eventName while (sqlite3_step(statement) == SQLITE_ROW) { const char *eventName = (const char *)sqlite3_column_text(statement, 0); - NSInteger count = sqlite3_column_int(statement, 1); - NSInteger firstTs = sqlite3_column_int(statement, 2); - NSInteger lastTs = sqlite3_column_int(statement, 3); + const char *normalizedEventName = (const char *)sqlite3_column_text(statement, 1); + NSInteger count = sqlite3_column_int(statement, 2); + NSInteger firstTs = sqlite3_column_int(statement, 3); + NSInteger lastTs = sqlite3_column_int(statement, 4); + const char *deviceID = (const char *)sqlite3_column_text(statement, 5); CleverTapEventDetail *ed = [[CleverTapEventDetail alloc] init]; ed.count = count; ed.firstTime = firstTs; ed.lastTime = lastTs; ed.eventName = [NSString stringWithUTF8String:eventName]; + ed.normalizedEventName = [NSString stringWithUTF8String:normalizedEventName]; + ed.deviceID = [NSString stringWithUTF8String:deviceID]; // Adding the CleverTapEventDetail to the result array [eventDataArray addObject:ed]; @@ -449,7 +460,7 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit NSInteger rowsToDelete = currentRowCount - (maxRowLimit - numberOfRowsToCleanup); // Delete the least recently used rows based on lastTs - const char *deleteSQL = "DELETE FROM CTUserEventLogs WHERE (eventName, deviceID) IN (SELECT eventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT ?);"; + const char *deleteSQL = "DELETE FROM CTUserEventLogs WHERE (normalizedEventName, deviceID) IN (SELECT normalizedEventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT ?);"; sqlite3_stmt *deleteStatement; if (sqlite3_prepare_v2(_eventDatabase, deleteSQL, -1, &deleteStatement, NULL) == SQLITE_OK) { sqlite3_bind_int(deleteStatement, 1, (int)rowsToDelete); diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 74073808..3fadc84a 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -9,6 +9,7 @@ #import #import "CTEventDatabase.h" #import "CleverTapInstanceConfig.h" +#import "CTUtils.h" static NSString *kEventName = @"Test Event"; static NSString *kDeviceID = @"Test Device"; @@ -17,6 +18,7 @@ @interface CTEventDatabaseTests : XCTestCase @property (nonatomic, strong) CleverTapInstanceConfig *config; @property (nonatomic, strong) CTEventDatabase *eventDatabase; +@property (nonatomic, strong) NSString *normalizedEventName; @end @@ -27,6 +29,7 @@ - (void)setUp { self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; self.eventDatabase = [CTEventDatabase sharedInstanceWithConfig:self.config]; + self.normalizedEventName = [CTUtils getNormalizedName:kEventName]; [self.eventDatabase createTable]; } @@ -43,126 +46,178 @@ - (void)testGetDatabaseVersion { } - (void)testInsertEventName { - BOOL insertSuccess = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + BOOL insertSuccess = [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; XCTAssertTrue(insertSuccess); } - (void)testInsertEventNameAgain { - BOOL insertSuccess = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + BOOL insertSuccess = [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; XCTAssertTrue(insertSuccess); // Insert same eventName and deviceID again - BOOL insertSuccessAgain = [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + BOOL insertSuccessAgain = [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; XCTAssertFalse(insertSuccessAgain); } - (void)testEventNameExists { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - BOOL eventExists = [self.eventDatabase eventExists:kEventName forDeviceID:kDeviceID]; + BOOL eventExists = [self.eventDatabase eventExists:self.normalizedEventName forDeviceID:kDeviceID]; + XCTAssertTrue(eventExists); + + NSString *normalizedEventName = [CTUtils getNormalizedName:@"TesT EveNT"]; + eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; + XCTAssertTrue(eventExists); + + normalizedEventName = [CTUtils getNormalizedName:@"TEST EVENT"]; + eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; XCTAssertTrue(eventExists); } - (void)testEventNameNotExists { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - BOOL eventExists = [self.eventDatabase eventExists:@"Test Event 1" forDeviceID:kDeviceID]; + NSString *normalizedEventName = [CTUtils getNormalizedName:@"TesT EveNT 1"]; + BOOL eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; + XCTAssertFalse(eventExists); + + normalizedEventName = [CTUtils getNormalizedName:@"Test.Event"]; + eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; XCTAssertFalse(eventExists); } - (void)testUpdateEventSuccess { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - BOOL updateSuccess = [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + BOOL updateSuccess = [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; XCTAssertTrue(updateSuccess); } - (void)testUpdateEventFailure { - BOOL updateSuccess = [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + BOOL updateSuccess = [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; XCTAssertFalse(updateSuccess); } - (void)testGetCountForEventName { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - [self.eventDatabase updateEvent:kEventName forDeviceID:kDeviceID]; + [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; // count should be 2. - NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCount, 2); } - (void)testGetCountForEventNameNotExists { // count should be 0. - NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCount, 0); } - (void)testFirstTimestampForEventName { NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - NSInteger firstTs = [self.eventDatabase getFirstTimestampForEventName:kEventName deviceID:kDeviceID]; + NSInteger firstTs = [self.eventDatabase getFirstTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(firstTs, currentTs); } - (void)testLastTimestampForEventName { NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - NSInteger lastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(lastTs, currentTs); } - (void)testLastTimestampForEventNameUpdated { NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - NSInteger lastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(lastTs, currentTs); NSInteger newCurrentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - NSInteger newLastTs = [self.eventDatabase getLastTimestampForEventName:kEventName deviceID:kDeviceID]; + [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; + NSInteger newLastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(newLastTs, newCurrentTs); } - (void)testDeleteTableSuccess { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; - NSInteger eventCount = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; + NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCount, 1); // Delete table. BOOL deleteSuccess = [self.eventDatabase deleteTable]; XCTAssertTrue(deleteSuccess); - NSInteger eventCountAfterDelete = [self.eventDatabase getCountForEventName:kEventName deviceID:kDeviceID]; + NSInteger eventCountAfterDelete = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCountAfterDelete, 0); } - (void)testEventDetailsForDeviceID { NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; - CleverTapEventDetail *eventDetail = [self.eventDatabase getEventDetailForEventName:kEventName deviceID:kDeviceID]; + CleverTapEventDetail *eventDetail = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqualObjects(eventDetail.eventName, kEventName); + XCTAssertEqualObjects(eventDetail.normalizedEventName, self.normalizedEventName); XCTAssertEqual(eventDetail.firstTime, currentTs); XCTAssertEqual(eventDetail.lastTime, currentTs); XCTAssertEqual(eventDetail.count, 1); + XCTAssertEqualObjects(eventDetail.deviceID, kDeviceID); } - (void)testAllEventsForDeviceID { - [self.eventDatabase insertData:kEventName deviceID:kDeviceID]; - [self.eventDatabase insertData:@"Test Event 1" deviceID:kDeviceID]; - [self.eventDatabase insertData:@"Test Event 2" deviceID:@"Test Device 1"]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; + + // Insert to same device id kDeviceID + NSString *eventName = @"Test Event 1"; + NSString *normalizedName = [CTUtils getNormalizedName:eventName]; + [self.eventDatabase insertEvent:eventName + normalizedEventName:normalizedName + deviceID:kDeviceID]; + + // Insert to different device id + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:@"Test Device 1"]; NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqualObjects(allEvents[0].eventName, kEventName); - XCTAssertEqualObjects(allEvents[1].eventName, @"Test Event 1"); + XCTAssertEqualObjects(allEvents[1].eventName, eventName); XCTAssertEqual(allEvents.count, 2); allEvents = [self.eventDatabase getAllEventsForDeviceID:@"Test Device 1"]; - XCTAssertEqualObjects(allEvents[0].eventName, @"Test Event 2"); + XCTAssertEqualObjects(allEvents[0].eventName, kEventName); XCTAssertEqual(allEvents.count, 1); } @@ -172,7 +227,10 @@ - (void)testLeastRecentlyUsedRowsDeleted { int totalRowCount = 13; for (int i = 0; i < totalRowCount; i++) { NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; - [self.eventDatabase insertData:eventName deviceID:kDeviceID]; + NSString *normalizedName = [CTUtils getNormalizedName:eventName]; + [self.eventDatabase insertEvent:eventName + normalizedEventName:normalizedName + deviceID:kDeviceID]; } NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqual(allEvents.count, totalRowCount); @@ -191,7 +249,10 @@ - (void)testLeastRecentlyUsedRowsNotDeleted { int totalRowCount = 7; for (int i = 0; i < totalRowCount; i++) { NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; - [self.eventDatabase insertData:eventName deviceID:kDeviceID]; + NSString *normalizedName = [CTUtils getNormalizedName:eventName]; + [self.eventDatabase insertEvent:eventName + normalizedEventName:normalizedName + deviceID:kDeviceID]; } NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqual(allEvents.count, totalRowCount); From d715758414f7eab505d87c457904d0394030a826 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Fri, 6 Dec 2024 12:35:35 +0530 Subject: [PATCH 19/41] Fixes create table is not called on fresh install --- CleverTapSDK/EventDatabase/CTEventDatabase.h | 4 +- CleverTapSDK/EventDatabase/CTEventDatabase.m | 74 ++++++++----------- .../EventDatabase/CTEventDatabaseTests.m | 8 +- 3 files changed, 34 insertions(+), 52 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 850f2672..789afc62 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -16,8 +16,6 @@ + (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config; - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config; -- (BOOL)createTable; - - (NSInteger)getDatabaseVersion; - (BOOL)insertEvent:(NSString *)eventName @@ -44,7 +42,7 @@ normalizedEventName:(NSString *)normalizedEventName - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID; -- (BOOL)deleteTable; +- (BOOL)deleteAllRows; - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 612fa6cb..26c7fff4 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -44,31 +44,6 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { return self; } -- (BOOL)createTable { - if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); - return NO; - } - - __block BOOL success = NO; - - dispatch_sync(_databaseQueue, ^{ - char *errMsg; - const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, normalizedEventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (normalizedEventName, deviceID))"; - if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { - success = YES; - - // Set the database version to the initial version, ie 1. - [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; - } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Create Table SQL error: %s", self, errMsg); - sqlite3_free(errMsg); - } - }); - - return success; -} - - (NSInteger)getDatabaseVersion { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); @@ -400,13 +375,13 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName return [eventDataArray copy]; } -- (BOOL)deleteTable { +- (BOOL)deleteAllRows { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - const char *querySQL = "DROP TABLE IF EXISTS CTUserEventLogs"; + const char *querySQL = "DELETE FROM CTUserEventLogs"; __block BOOL success = NO; dispatch_sync(_databaseQueue, ^{ @@ -416,8 +391,7 @@ - (BOOL)deleteTable { if (result == SQLITE_OK) { success = YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting table CTUserEventLogs: %s", self, errMsg); - success = NO; + CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting all rows from CTUserEventLogs: %s", self, errMsg); } }); @@ -500,17 +474,9 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit - (BOOL)openDatabase { NSString *databasePath = [self databasePath]; - - if (![self isDatabaseFileExists]) { - // If the database file does not exist, create the schema for the first time - if (![self createTable]) { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to create database schema for the first time", self); - return NO; - } - } - if (sqlite3_open([databasePath UTF8String], &_eventDatabase) == SQLITE_OK) { - // After opening, check and update the version if needed + // Create table, check and update the version if needed + [self createTable]; [self checkAndUpdateDatabaseVersion]; return YES; } else { @@ -519,6 +485,31 @@ - (BOOL)openDatabase { } } +- (BOOL)createTable { + if (!_eventDatabase) { + CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + return NO; + } + + __block BOOL success = NO; + + dispatch_sync(_databaseQueue, ^{ + char *errMsg; + const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, normalizedEventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (normalizedEventName, deviceID))"; + if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { + success = YES; + + // Set the database version to the initial version, ie 1. + [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; + } else { + CleverTapLogInternal(self.config.logLevel, @"%@ Create Table SQL error: %s", self, errMsg); + sqlite3_free(errMsg); + } + }); + + return success; +} + - (void)closeDatabase { if (_eventDatabase) { sqlite3_close(_eventDatabase); @@ -567,9 +558,4 @@ - (NSString *)databasePath { return [documentsDirectory stringByAppendingPathComponent:@"CleverTap-Events.db"]; } -- (BOOL)isDatabaseFileExists { - NSString *databasePath = [self databasePath]; - return [[NSFileManager defaultManager] fileExistsAtPath:databasePath]; -} - @end diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 3fadc84a..5bd1bc81 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -30,14 +30,12 @@ - (void)setUp { self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; self.eventDatabase = [CTEventDatabase sharedInstanceWithConfig:self.config]; self.normalizedEventName = [CTUtils getNormalizedName:kEventName]; - [self.eventDatabase createTable]; - } - (void)tearDown { [super tearDown]; - [self.eventDatabase deleteTable]; + [self.eventDatabase deleteAllRows]; } - (void)testGetDatabaseVersion { @@ -164,7 +162,7 @@ - (void)testLastTimestampForEventNameUpdated { } -- (void)testDeleteTableSuccess { +- (void)testDeleteAllRowsSuccess { [self.eventDatabase insertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; @@ -172,7 +170,7 @@ - (void)testDeleteTableSuccess { XCTAssertEqual(eventCount, 1); // Delete table. - BOOL deleteSuccess = [self.eventDatabase deleteTable]; + BOOL deleteSuccess = [self.eventDatabase deleteAllRows]; XCTAssertTrue(deleteSuccess); NSInteger eventCountAfterDelete = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCountAfterDelete, 0); From 807149aee099904f4b9b36b2da3d443f7490d7fb Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Fri, 6 Dec 2024 13:28:38 +0530 Subject: [PATCH 20/41] Added upsert method to insert event if event not exists and update when exists. --- CleverTapSDK/EventDatabase/CTEventDatabase.h | 4 +++ CleverTapSDK/EventDatabase/CTEventDatabase.m | 27 ++++++++++--------- .../EventDatabase/CTEventDatabaseTests.m | 22 ++++++++++++--- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 789afc62..2fcba927 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -25,6 +25,10 @@ normalizedEventName:(NSString *)normalizedEventName - (BOOL)updateEvent:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID; +- (BOOL)upsertEvent:(NSString *)eventName +normalizedEventName:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID; + - (BOOL)eventExists:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 26c7fff4..ce56dd03 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -75,12 +75,6 @@ - (BOOL)insertEvent:(NSString *)eventName CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - - BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; - if (eventExists) { - CleverTapLogInternal(self.config.logLevel, @"%@ Insert SQL - Event name: %@ and DeviceID: %@ already exists.", self, eventName, deviceID); - return NO; - } __block BOOL success = NO; // For new event, set count as 1 @@ -120,12 +114,6 @@ - (BOOL)updateEvent:(NSString *)normalizedEventName CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return NO; } - - BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; - if (!eventExists) { - CleverTapLogInternal(self.config.logLevel, @"%@ Update SQL - Event name: %@ and DeviceID: %@ doesn't exists.", self, normalizedEventName, deviceID); - return NO; - } NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; const char *updateSQL = @@ -155,6 +143,21 @@ - (BOOL)updateEvent:(NSString *)normalizedEventName return success; } +- (BOOL)upsertEvent:(NSString *)eventName +normalizedEventName:(NSString *)normalizedEventName + deviceID:(NSString *)deviceID { + BOOL success = NO; + + BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; + if (!eventExists) { + success = [self insertEvent:eventName normalizedEventName:normalizedEventName deviceID:deviceID]; + } else { + success = [self updateEvent:normalizedEventName forDeviceID:deviceID]; + } + + return success; +} + - (BOOL)eventExists:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID { if (!_eventDatabase) { diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 5bd1bc81..c515847b 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -103,9 +103,25 @@ - (void)testUpdateEventSuccess { XCTAssertTrue(updateSuccess); } -- (void)testUpdateEventFailure { - BOOL updateSuccess = [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; - XCTAssertFalse(updateSuccess); +- (void)testUpsertEventSuccessWhenInsert { + BOOL upsertSuccess = [self.eventDatabase upsertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; + XCTAssertTrue(upsertSuccess); +} + +- (void)testUpsertEventSuccessWhenUpdate { + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; + + BOOL upsertSuccess = [self.eventDatabase upsertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID]; + XCTAssertTrue(upsertSuccess); + + NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCount, 2); } - (void)testGetCountForEventName { From 2ff114b1bddbbdecc15d43c63f8eb5cef3c78923 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Fri, 6 Dec 2024 16:59:31 +0530 Subject: [PATCH 21/41] completed todos regarding connecting the first-time only matching code to the database. updated tests. added normalised names to queries. --- CleverTapSDK/CTLocalDataStore.m | 27 +--- CleverTapSDK/CleverTap.m | 2 +- .../InApps/CTInAppEvaluationManager.h | 4 +- .../InApps/CTInAppEvaluationManager.m | 5 +- .../InApps/Matchers/CTTriggersMatcher.h | 2 + .../InApps/Matchers/CTTriggersMatcher.m | 16 +- .../InApps/CTInAppEvaluationManagerTest.m | 2 +- .../InApps/CTTriggersMatcherTest.m | 137 ++++++++++-------- CleverTapSDKTests/InApps/InAppHelper.h | 2 + CleverTapSDKTests/InApps/InAppHelper.m | 6 +- 10 files changed, 112 insertions(+), 91 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 7dd020e3..69755636 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -48,6 +48,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _deviceInfo = deviceInfo; self.dispatchQueueManager = dispatchQueueManager; self.userEventLogs = [NSMutableSet set]; + self.dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; localProfileUpdateExpiryStore = [NSMutableDictionary new]; _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); @@ -191,24 +192,8 @@ - (void)persistEvent:(NSDictionary *)event { if (!event || !event[CLTAP_EVENT_NAME]) return; [self runOnBackgroundQueue:^{ NSString *eventName = event[CLTAP_EVENT_NAME]; - NSDictionary *storedEvents = [self getStoredEvents]; - if (!storedEvents) storedEvents = @{}; - NSTimeInterval now = [[[NSDate alloc] init] timeIntervalSince1970]; - NSArray *eventData = storedEvents[eventName]; - if (!eventData || eventData.count < 3) { - // This event has been recorded for the very first time - // Set the count to 0, first and last to now - // Count will be incremented soon after this block - eventData = @[@0.0f, @(now), @(now)]; - } - NSMutableArray *eventDataCopy = [eventData mutableCopy]; - double currentCount = ((NSNumber *) eventDataCopy[0]).doubleValue; - currentCount++; - eventDataCopy[0] = @(currentCount); - eventDataCopy[2] = @(now); - NSMutableDictionary *store = [storedEvents mutableCopy]; - store[eventName] = eventDataCopy; - [self setStoredEvents:store]; + // TODO: add normalisation + [self.dbHelper upsertEvent:eventName normalizedEventName:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; }]; } @@ -624,11 +609,8 @@ - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { return NO; } } - if (!self.dbHelper) { - self.dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; - } // TODO: Add normalized name here - NSInteger count = [self.dbHelper getEventCount:eventName deviceID:self.deviceInfo.deviceId]; + NSInteger count = [self.dbHelper getEventCount:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; if (count > 1) { @synchronized (self.userEventLogs) { [self.userEventLogs addObject:eventName]; @@ -675,7 +657,6 @@ - (void)_setProfileValue:(id)value forKey:(NSString *)key fromUpstream:(BOOL)fro NSArray *systemProfileKeys = @[CLTAP_SYS_CARRIER, CLTAP_SYS_CC, CLTAP_SYS_TZ]; if (![systemProfileKeys containsObject:key]) { NSDictionary *profileEvent = @{CLTAP_EVENT_NAME: key}; - // TODO: Call appropriate persist method from the new db/localDataStore class [self persistEvent:profileEvent]; } } diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 064e8b47..20be58d6 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -535,7 +535,7 @@ - (void)initializeInAppSupport { templatesManager:templatesManager fileDownloader:self.fileDownloader]; - CTInAppEvaluationManager *evaluationManager = [[CTInAppEvaluationManager alloc] initWithAccountId:self.config.accountId deviceId:self.deviceInfo.deviceId delegateManager:self.delegateManager impressionManager:impressionManager inAppDisplayManager:displayManager inAppStore:inAppStore inAppTriggerManager:triggerManager]; + CTInAppEvaluationManager *evaluationManager = [[CTInAppEvaluationManager alloc] initWithAccountId:self.config.accountId deviceId:self.deviceInfo.deviceId delegateManager:self.delegateManager impressionManager:impressionManager inAppDisplayManager:displayManager inAppStore:inAppStore inAppTriggerManager:triggerManager localDataStore:self.localDataStore]; self.customTemplatesManager = templatesManager; self.inAppFCManager = inAppFCManager; diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.h b/CleverTapSDK/InApps/CTInAppEvaluationManager.h index 2dbea791..847f6469 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.h +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.h @@ -10,6 +10,7 @@ #import #import "CTBatchSentDelegate.h" #import "CTAttachToBatchHeaderDelegate.h" +#import "CTLocalDataStore.h" @class CTMultiDelegateManager; @class CTImpressionManager; @@ -30,7 +31,8 @@ NS_ASSUME_NONNULL_BEGIN impressionManager:(CTImpressionManager *)impressionManager inAppDisplayManager:(CTInAppDisplayManager *)inAppDisplayManager inAppStore:(CTInAppStore *)inAppStore - inAppTriggerManager:(CTInAppTriggerManager *)inAppTriggerManager; + inAppTriggerManager:(CTInAppTriggerManager *)inAppTriggerManager + localDataStore:(CTLocalDataStore *)dataStore; - (void)evaluateOnEvent:(NSString *)eventName withProps:(NSDictionary *)properties; - (void)evaluateOnChargedEvent:(NSDictionary *)chargeDetails andItems:(NSArray *)items; diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index f2896252..7f4427db 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -52,7 +52,8 @@ - (instancetype)initWithAccountId:(NSString *)accountId impressionManager:(CTImpressionManager *)impressionManager inAppDisplayManager:(CTInAppDisplayManager *)inAppDisplayManager inAppStore:(CTInAppStore *)inAppStore - inAppTriggerManager:(CTInAppTriggerManager *)inAppTriggerManager { + inAppTriggerManager:(CTInAppTriggerManager *)inAppTriggerManager + localDataStore:(CTLocalDataStore *)dataStore { if (self = [super init]) { self.accountId = accountId; self.deviceId = deviceId; @@ -84,7 +85,7 @@ - (instancetype)initWithAccountId:(NSString *)accountId } self.inAppStore = inAppStore; - self.triggersMatcher = [CTTriggersMatcher new]; + self.triggersMatcher = [[CTTriggersMatcher alloc]initWithDataStore:dataStore]; self.limitsMatcher = [CTLimitsMatcher new]; self.triggerManager = inAppTriggerManager; diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.h b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.h index 404c465a..9c08333e 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.h +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.h @@ -8,11 +8,13 @@ #import #import "CTEventAdapter.h" +#import "CTLocalDataStore.h" NS_ASSUME_NONNULL_BEGIN @interface CTTriggersMatcher : NSObject +- (instancetype)initWithDataStore:(CTLocalDataStore *)dataStore; - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)event; @end diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index 785afb21..aff526e9 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -13,8 +13,19 @@ #import "CTTriggerEvaluator.h" #import "CTUtils.h" +@interface CTTriggersMatcher () {} +@property (nonatomic, strong) CTLocalDataStore *dataStore; +@end + @implementation CTTriggersMatcher +- (instancetype)initWithDataStore:(CTLocalDataStore *)dataStore { + if (self = [super init]) { + self.dataStore = dataStore; + } + return self; +} + - (BOOL)matchEventWhenTriggers:(NSArray *)whenTriggers event:(CTEventAdapter *)event { // Events in the array are OR-ed for (NSDictionary *triggerObject in whenTriggers) { @@ -124,9 +135,8 @@ - (BOOL)matchFirstTimeOnly:(CTEventAdapter *)event trigger:(CTTriggerAdapter *)t if (!trigger.firstTimeOnly) { return YES; } - // TODO: Call IsEventFirstTime from the new db/localDataStore class -// NSString *nameToCheck = trigger.profileAttrName ?: trigger.eventName; -// return [CTLocalDataStore IsEventFirstTime:nameToCheck]; + NSString *nameToCheck = trigger.profileAttrName ?: trigger.eventName; + return [self.dataStore isEventLoggedFirstTime:nameToCheck]; return YES; } diff --git a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m index 87718b0e..328d4551 100644 --- a/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m +++ b/CleverTapSDKTests/InApps/CTInAppEvaluationManagerTest.m @@ -778,7 +778,7 @@ - (void)testDelegatesAdded { NSUInteger batchHeaderDelegatesCount = [[delegateManager attachToHeaderDelegates] count]; NSUInteger batchSentDelegatesCount = [[delegateManager batchSentDelegates] count]; - __unused CTInAppEvaluationManager *manager = [[CTInAppEvaluationManager alloc] initWithAccountId:self.helper.accountId deviceId:self.helper.deviceId delegateManager:delegateManager impressionManager:self.helper.impressionManager inAppDisplayManager:self.helper.inAppDisplayManager inAppStore:self.helper.inAppStore inAppTriggerManager:self.helper.inAppTriggerManager]; + __unused CTInAppEvaluationManager *manager = [[CTInAppEvaluationManager alloc] initWithAccountId:self.helper.accountId deviceId:self.helper.deviceId delegateManager:delegateManager impressionManager:self.helper.impressionManager inAppDisplayManager:self.helper.inAppDisplayManager inAppStore:self.helper.inAppStore inAppTriggerManager:self.helper.inAppTriggerManager localDataStore:self.helper.dataStore]; XCTAssertEqual([[delegateManager attachToHeaderDelegates] count], batchHeaderDelegatesCount + 1); XCTAssertEqual([[delegateManager batchSentDelegates] count], batchSentDelegatesCount + 1); diff --git a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m index d3adc751..bef7652e 100644 --- a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m +++ b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m @@ -8,6 +8,7 @@ #import #import +#import #import "CTTriggersMatcher.h" #import "CTEventAdapter.h" #import "CTTriggerEvaluator.h" @@ -15,11 +16,19 @@ #import "CTConstants.h" @interface CTTriggersMatcherTest : XCTestCase - +@property (nonatomic, strong) CTLocalDataStore *dataStore; @end @implementation CTTriggersMatcherTest +- (void)setUp { + [super setUp]; + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" accountRegion:@"testRegion"]; + CTDeviceInfo *deviceInfo = [[CTDeviceInfo alloc] initWithConfig:config andCleverTapID:@"testDeviceInfo"]; + CTDispatchQueueManager *queueManager = [[CTDispatchQueueManager alloc] initWithConfig:config]; + self.dataStore = [[CTLocalDataStore alloc] initWithConfig:config profileValues:[NSMutableDictionary new] andDeviceInfo:deviceInfo dispatchQueueManager:queueManager]; +} + #pragma mark Event - (void)testMatchEventAllOperators { NSArray *whenTriggers = @[ @@ -73,7 +82,7 @@ - (void)testMatchEventAllOperators { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @160, @@ -96,7 +105,7 @@ - (void)testMatchEventWithoutTriggerProps { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -116,7 +125,7 @@ - (void)testMatchEventWithEmptyTriggerProps { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -140,7 +149,7 @@ - (void)testMatchEventWithoutProps { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL matchNoProps = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{}]; XCTAssertFalse(matchNoProps); @@ -233,7 +242,7 @@ - (void)testMatchChargedEvent { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ @"prop1": @150, @@ -277,7 +286,7 @@ - (void)testChargedWithoutItems { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ @"prop1": @150, @@ -337,7 +346,7 @@ - (void)testMatchChargedEventItemArrayEquals { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ @"prop1": @150, @@ -412,7 +421,7 @@ - (void)testMatchChargedEventItemArrayContains { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ @"prop1": @150, @@ -487,7 +496,7 @@ - (void)testMatchEqualsPrimitives { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150, @@ -518,7 +527,7 @@ - (void)testMatchEqualsBoolean { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @(YES), @@ -564,7 +573,7 @@ - (void)testMatchEqualsBooleanString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"true", @@ -593,7 +602,7 @@ - (void)testMatchEqualsBooleanCaseInsensitive { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @(YES), @@ -627,7 +636,7 @@ - (void)testMatchEqualsNumbers { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150, @@ -696,7 +705,7 @@ - (void)testMatchEqualsNumbersCharged { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[@{ @"prop1": @"150", @@ -743,7 +752,7 @@ - (void)testMatchEqualsDouble { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150.950 @@ -775,7 +784,7 @@ - (void)testMatchEqualsExtectedStringWithActualArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"test", @"test2"] @@ -804,7 +813,7 @@ - (void)testMatchEqualsExtectedArrayWithActualString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"test"] @@ -836,7 +845,7 @@ - (void)testMatchEqualsExtectedNumberWithActualArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"test", @150] @@ -859,7 +868,7 @@ - (void)testMatchEqualsExtectedStringWithActualString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"test" @@ -951,7 +960,7 @@ - (void)testMatchEqualsExtectedNumberWithActualString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"test" @@ -973,7 +982,7 @@ - (void)testMatchEqualsExtectedDoubleWithActualDouble { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150.99 @@ -996,7 +1005,7 @@ - (void)testMatchEqualsExtectedDoubleWithActualDoubleString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"150.99" @@ -1019,7 +1028,7 @@ - (void)testMatchEqualsExtectedArrayWithActualArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"test2", @"test3", @"test"] @@ -1042,7 +1051,7 @@ - (void)testMatchEqualsExtectedArrayWithActualArrayNumber { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@3, @1, @2] @@ -1065,7 +1074,7 @@ - (void)testMatchSet { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150 @@ -1086,7 +1095,7 @@ - (void)testMatchSetCharged { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[ @{ @@ -1120,7 +1129,7 @@ - (void)testMatchNotSet { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop2": @150 @@ -1142,7 +1151,7 @@ - (void)testMatchNotSetCharged { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[ @{ @@ -1177,7 +1186,7 @@ - (void)testMatchNotEquals { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @240 @@ -1204,7 +1213,7 @@ - (void)testMatchNotEqualsArrays { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@241] @@ -1233,7 +1242,7 @@ - (void)testMatchLessThan { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150 @@ -1261,7 +1270,7 @@ - (void)testMatchLessThanWithString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"-120" @@ -1290,7 +1299,7 @@ - (void)testMatchLessThanWithArrays { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"-120" @@ -1336,7 +1345,7 @@ - (void)testMatchGreaterThan { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @240 @@ -1364,7 +1373,7 @@ - (void)testMatchGreaterThanWithString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"240" @@ -1393,7 +1402,7 @@ - (void)testMatchGreaterThanWithArrays { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @600 @@ -1439,7 +1448,7 @@ - (void)testMatchBetween { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150 @@ -1492,7 +1501,7 @@ - (void)testMatchBetweenCharged { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{} items:@[ @{ @@ -1529,7 +1538,7 @@ - (void)testMatchBetweenArrayMoreThan2 { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150 @@ -1552,7 +1561,7 @@ - (void)testMatchBetweenEmptyArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @150 @@ -1576,7 +1585,7 @@ - (void)testMatchContainsString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -1603,7 +1612,7 @@ - (void)testMatchContainsStringBool { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"this is true" @@ -1630,7 +1639,7 @@ - (void)testMatchContainsNumber { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"1234567" @@ -1662,7 +1671,7 @@ - (void)testMatchContainsArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -1685,7 +1694,7 @@ - (void)testMatchContainsArrayNumber { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"123456" @@ -1712,7 +1721,7 @@ - (void)testMatchContainsArrayEmpty { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -1735,7 +1744,7 @@ - (void)testMatchContainsStringWithPropertyArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"clevertap",@"test"] @@ -1759,7 +1768,7 @@ - (void)testMatchNotContainsArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -1792,7 +1801,7 @@ - (void)testMatchNotContainsArrayFromTriggerArray { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @[@"clevertap", @"yes"] @@ -1815,7 +1824,7 @@ - (void)testMatchNotContainsString { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ @"prop1": @"clevertap" @@ -1838,7 +1847,7 @@ - (void)testMatchSystemProperties { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ CLTAP_SDK_VERSION: @60000 @@ -1889,7 +1898,7 @@ - (void)testMatchSystemPropertiesCurrentAndLegacy { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{ CLTAP_SDK_VERSION: @60000, CLTAP_APP_VERSION: @"6.0.0", @@ -1927,7 +1936,7 @@ - (void)testMatchNotificationProperties { } ]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"Notification Viewed" eventProperties:@{ CLTAP_PROP_WZRK_ID: @"1701172437_20231128", @@ -1959,7 +1968,7 @@ - (void)testMatchEventWithGeoRadius { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:@"event1" eventProperties:@{} andLocation:location1km]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:event]; XCTAssertTrue(match); @@ -1995,7 +2004,7 @@ - (void)testMatchEventWithGeoRadiusButNotParams { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:@"event1" eventProperties:@{@"prop1": @151} andLocation:location1km]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:event]; XCTAssertFalse(match); @@ -2012,10 +2021,14 @@ - (void)testMatchEventWithFirstTimeOnly { ]; // TODO: Update tests after db/localdatastore is updated with db methods - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; + CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); + id mockIsEventLoggedFirstTime = OCMStub([dataStoreMock isEventLoggedFirstTime:@"event1"]).andReturn(YES); BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers eventName:@"event1" eventProperties:@{}]; + XCTAssertTrue(match); + OCMVerify(mockIsEventLoggedFirstTime); } - (void)testMatchChargedEventWithFirstTimeOnly { @@ -2039,7 +2052,10 @@ - (void)testMatchChargedEventWithFirstTimeOnly { ]; // TODO: Update tests after db/localdatastore is updated with db methods - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; + + CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); + id mockIsEventLoggedFirstTime = OCMStub([dataStoreMock isEventLoggedFirstTime:@"Charged"]).andReturn(YES); BOOL match = [triggerMatcher matchChargedEventWhenTriggers:whenTriggers details:@{ @"prop1": @150, } items:@[ @@ -2053,6 +2069,7 @@ - (void)testMatchChargedEventWithFirstTimeOnly { } ]]; XCTAssertTrue(match); + OCMVerify(mockIsEventLoggedFirstTime); } - (void)testMatchEventFirstTimeOnlyWithGeoRadius { @@ -2074,7 +2091,9 @@ - (void)testMatchEventFirstTimeOnlyWithGeoRadius { CTEventAdapter *event = [[CTEventAdapter alloc] initWithEventName:@"event1" eventProperties:@{} andLocation:location1km]; - CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] init]; + CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; + CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); + id mockIsEventLoggedFirstTime = OCMStub([dataStoreMock isEventLoggedFirstTime:@"event1"]).andReturn(YES); BOOL match = [triggerMatcher matchEventWhenTriggers:whenTriggers event:event]; XCTAssertTrue(match); diff --git a/CleverTapSDKTests/InApps/InAppHelper.h b/CleverTapSDKTests/InApps/InAppHelper.h index 0a782204..a97d44c8 100644 --- a/CleverTapSDKTests/InApps/InAppHelper.h +++ b/CleverTapSDKTests/InApps/InAppHelper.h @@ -16,6 +16,7 @@ @class CTMultiDelegateManager; @class CTInAppTriggerManager; @class CTFileDownloader; +@class CTLocalDataStore; NS_ASSUME_NONNULL_BEGIN @@ -35,6 +36,7 @@ extern NSString *const CLTAP_TEST_CAMPAIGN_ID; @property (nonatomic, strong) CTInAppStore *inAppStore; @property (nonatomic, strong) CTInAppTriggerManager *inAppTriggerManager; @property (nonatomic, strong) CTFileDownloader *fileDownloader; +@property (nonatomic, strong) CTLocalDataStore *dataStore; - (NSString *)accountId; - (NSString *)accountToken; diff --git a/CleverTapSDKTests/InApps/InAppHelper.m b/CleverTapSDKTests/InApps/InAppHelper.m index c523ad61..6d105f66 100644 --- a/CleverTapSDKTests/InApps/InAppHelper.m +++ b/CleverTapSDKTests/InApps/InAppHelper.m @@ -67,6 +67,10 @@ - (instancetype)init { impressionManager:self.impressionManager inAppTriggerManager:self.inAppTriggerManager]; + CTDeviceInfo *deviceInfo = [[CTDeviceInfo alloc] initWithConfig:self.config andCleverTapID:CLTAP_TEST_DEVICE_ID]; + CTDispatchQueueManager *queueManager = [[CTDispatchQueueManager alloc] initWithConfig:self.config]; + self.dataStore = [[CTLocalDataStore alloc] initWithConfig:self.config profileValues:[NSMutableDictionary new] andDeviceInfo:deviceInfo dispatchQueueManager:queueManager]; + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // Initialize when needed, requires CleverTap instance @@ -79,7 +83,7 @@ - (instancetype)init { impressionManager:self.impressionManager inAppDisplayManager:self.inAppDisplayManager inAppStore:self.inAppStore - inAppTriggerManager:self.inAppTriggerManager]; + inAppTriggerManager:self.inAppTriggerManager localDataStore:self.dataStore]; } return self; } From 74c4653dca0add164076dbda408bb9903e8bf8c9 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Sun, 8 Dec 2024 21:29:49 +0530 Subject: [PATCH 22/41] addressed review comments --- CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m index aff526e9..b006a81e 100644 --- a/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m +++ b/CleverTapSDK/InApps/Matchers/CTTriggersMatcher.m @@ -52,7 +52,7 @@ - (BOOL)match:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { return NO; } - if (![self matchFirstTimeOnly:event trigger:trigger]) { + if (![self matchFirstTimeOnlyForTrigger:trigger]) { return NO; } @@ -131,14 +131,12 @@ - (BOOL)matchCharged:(CTTriggerAdapter *)trigger event:(CTEventAdapter *)event { return YES; } -- (BOOL)matchFirstTimeOnly:(CTEventAdapter *)event trigger:(CTTriggerAdapter *)trigger { +- (BOOL)matchFirstTimeOnlyForTrigger:(CTTriggerAdapter *)trigger { if (!trigger.firstTimeOnly) { return YES; } NSString *nameToCheck = trigger.profileAttrName ?: trigger.eventName; return [self.dataStore isEventLoggedFirstTime:nameToCheck]; - - return YES; } @end From ac5b66cdbc8f13eab2df9bc343ee892eb647d109 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 9 Dec 2024 11:47:47 +0530 Subject: [PATCH 23/41] feat(MC-2429): Add new public apis to access database methods --- CleverTapSDK/CTLocalDataStore.h | 10 ++++ CleverTapSDK/CTLocalDataStore.m | 31 +++++++++++ CleverTapSDK/CleverTap.h | 99 ++++++++++++++++++++++++++++++--- CleverTapSDK/CleverTap.m | 46 +++++++++++++++ 4 files changed, 179 insertions(+), 7 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.h b/CleverTapSDK/CTLocalDataStore.h index 5c8ca8b6..334acc95 100644 --- a/CleverTapSDK/CTLocalDataStore.h +++ b/CleverTapSDK/CTLocalDataStore.h @@ -46,4 +46,14 @@ - (BOOL)isEventLoggedFirstTime:(NSString*)eventName; +- (int)readUserEventLogCount:(NSString *)eventName; + +- (CleverTapEventDetail *)readUserEventLog:(NSString *)eventName; + +- (NSTimeInterval)readUserEventLogFirstTs:(NSString *)eventName; + +- (NSTimeInterval)readUserEventLogLastTs:(NSString *)eventName; + +- (NSDictionary *)readUserEventLogs; + @end diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 7dd020e3..e073d446 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -637,6 +637,37 @@ - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { return count == 1; } +#pragma mark - Public APIs for Event log + +- (int)readUserEventLogCount:(NSString *)eventName { + NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; + return (int) [self.dbHelper getEventCount:normalizedEventName deviceID:self.deviceInfo.deviceId]; +} + +- (CleverTapEventDetail *)readUserEventLog:(NSString *)eventName { + NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; + return [self.dbHelper getEventDetail:normalizedEventName deviceID:self.deviceInfo.deviceId]; +} + +- (NSTimeInterval)readUserEventLogFirstTs:(NSString *)eventName { + NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; + return (int) [self.dbHelper getFirstTimestamp:normalizedEventName deviceID:self.deviceInfo.deviceId]; +} + +- (NSTimeInterval)readUserEventLogLastTs:(NSString *)eventName { + NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; + return (int) [self.dbHelper getLastTimestamp:normalizedEventName deviceID:self.deviceInfo.deviceId]; +} + +- (NSDictionary *)readUserEventLogs { + NSArray *allEvents = [self.dbHelper getAllEventsForDeviceID:self.deviceInfo.deviceId]; + NSMutableDictionary *history = [[NSMutableDictionary alloc] init]; + for (CleverTapEventDetail *event in allEvents) { + history[event.eventName] = event; + } + return history; +} + #pragma mark - Private Local Profile Getters and Setters and disk persistence handling diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index 3ea4e882..084cdeff 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -801,7 +801,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (NSTimeInterval)eventGetFirstTime:(NSString *_Nonnull)event; +- (NSTimeInterval)eventGetFirstTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogFirstTs instead"))); /*! @method @@ -813,7 +813,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (NSTimeInterval)eventGetLastTime:(NSString *_Nonnull)event; +- (NSTimeInterval)eventGetLastTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogLastTs instead"))); /*! @method @@ -824,7 +824,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (int)eventGetOccurrences:(NSString *_Nonnull)event; +- (int)eventGetOccurrences:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogCount instead"))); /*! @method @@ -838,7 +838,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; Be sure to call enablePersonalization (typically once at app launch) prior to using this method. */ -- (NSDictionary *_Nullable)userGetEventHistory; +- (NSDictionary *_Nullable)userGetEventHistory __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogHistory instead"))); /*! @method @@ -853,7 +853,72 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (CleverTapEventDetail *_Nullable)eventGetDetail:(NSString *_Nullable)event; +- (CleverTapEventDetail *_Nullable)eventGetDetail:(NSString *_Nullable)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLog instead"))); + +/*! + @method + + @abstract + Get the the count of logged events for a specific event name associated with the current user. + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization prior to invoking this method. + + @param eventName event name + */ +- (int)getUserEventLogCount:(NSString *_Nonnull)eventName; + +/*! + @method + + @abstract + Get the details for the event. + + @discussion + Returns a CleverTapEventDetail object (eventName, normalizedEventName, firstTime, lastTime, count, deviceID) + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization (typically once at app launch) prior to using this method. + + @param eventName event name + */ +- (CleverTapEventDetail *_Nullable)getUserEventLog:(NSString *_Nullable)eventName; + +/*! + @method + + @abstract + Get the time of the first recording of the event. + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization prior to invoking this method. + + @param eventName event name + */ +- (NSTimeInterval)getUserEventLogFirstTs:(NSString *_Nonnull)eventName; + +/*! + @method + + @abstract + Get the time of the last recording of the event. + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization prior to invoking this method. + + @param eventName event name + */ +- (NSTimeInterval)getUserEventLogLastTs:(NSString *_Nonnull)eventName; + +/*! + @method + + @abstract + Get the user's event history. + + @discussion + Returns a dictionary of CleverTapEventDetail objects (eventName, normalizedEventName, firstTime, lastTime, count, deviceID), keyed by eventName. + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization (typically once at app launch) prior to using this method. + + */ +- (NSDictionary *_Nullable)getUserEventLogHistory; #pragma mark Session API @@ -889,7 +954,17 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; Be sure to call enablePersonalization (typically once at app launch) prior to using this method. */ -- (int)userGetTotalVisits; +- (int)userGetTotalVisits __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserAppLaunchCount instead"))); + +/*! + @method + + @abstract + Get the total number of visits by this user. + This operation involves a database query and should be called from a background thread. + Be sure to call enablePersonalization (typically once at app launch) prior to using this method. + */ +- (int)getUserAppLaunchCount; /*! @method @@ -909,7 +984,17 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; Be sure to call enablePersonalization (typically once at app launch) prior to using this method. */ -- (NSTimeInterval)userGetPreviousVisitTime; +- (NSTimeInterval)userGetPreviousVisitTime __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserLastVisitTs instead"))); + +/*! + @method + + @abstract + Get the last prior visit time for this user. + Be sure to call enablePersonalization (typically once at app launch) prior to using this method. + + */ +- (NSTimeInterval)getUserLastVisitTs; /* ------------------------------------------------------------------------------------------------------ * Synchronization diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 064e8b47..1f8a6422 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -195,6 +195,7 @@ @interface CleverTap () { @property (nonatomic, strong, readwrite) CleverTapInstanceConfig *config; @property (nonatomic, assign) NSTimeInterval lastAppLaunchedTime; +@property (nonatomic, assign) NSTimeInterval userLastVisitTs; @property (nonatomic, strong) CTDeviceInfo *deviceInfo; @property (nonatomic, strong) CTLocalDataStore *localDataStore; @property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; @@ -473,6 +474,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig*)config andCleverTapID:( _localDataStore = [[CTLocalDataStore alloc] initWithConfig:_config profileValues:initialProfileValues andDeviceInfo: _deviceInfo dispatchQueueManager:_dispatchQueueManager]; _lastAppLaunchedTime = [self eventGetLastTime:@"App Launched"]; + _userLastVisitTs = [self getUserEventLogLastTs:@"App Launched"]; self.validationResultStack = [[CTValidationResultStack alloc]initWithConfig: _config]; self.userSetLocation = kCLLocationCoordinate2DInvalid; @@ -3105,6 +3107,50 @@ - (CleverTapEventDetail *)eventGetDetail:(NSString *)event { return [self.localDataStore getEventDetail:event]; } +#pragma mark - User Event Log Methods + +- (int)getUserEventLogCount:(NSString *)eventName { + if (!self.config.enablePersonalization) { + return -1; + } + return [self.localDataStore readUserEventLogCount:eventName]; +} + +- (CleverTapEventDetail *)getUserEventLog:(NSString *)eventName { + if (!self.config.enablePersonalization) { + return nil; + } + return [self.localDataStore readUserEventLog:eventName]; +} + +- (NSTimeInterval)getUserEventLogFirstTs:(NSString *)eventName { + if (!self.config.enablePersonalization) { + return -1; + } + return [self.localDataStore readUserEventLogFirstTs:eventName]; +} + +- (NSTimeInterval)getUserEventLogLastTs:(NSString *)eventName { + if (!self.config.enablePersonalization) { + return -1; + } + return [self.localDataStore readUserEventLogFirstTs:eventName]; +} + +- (NSDictionary *)getUserEventLogHistory { + if (!self.config.enablePersonalization) { + return nil; + } + return [self.localDataStore readUserEventLogs]; +} + +- (int)getUserAppLaunchCount { + return [self getUserEventLogCount:@"App Launched"]; +} + +- (NSTimeInterval)getUserLastVisitTs { + return self.userLastVisitTs; +} #pragma mark - Session API From f5729bb71ecacf8ebdf7f1243a13fe768b3f0a8c Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Mon, 9 Dec 2024 12:16:59 +0530 Subject: [PATCH 24/41] added mutex lock flags to the open database command to make it atomic --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index ce56dd03..9967e81e 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -477,7 +477,7 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit - (BOOL)openDatabase { NSString *databasePath = [self databasePath]; - if (sqlite3_open([databasePath UTF8String], &_eventDatabase) == SQLITE_OK) { + if (sqlite3_open_v2([databasePath UTF8String], &_eventDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) == SQLITE_OK) { // Create table, check and update the version if needed [self createTable]; [self checkAndUpdateDatabaseVersion]; From 95bba9a8b42a946a166166dc0b7cdb6043221a03 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Mon, 9 Dec 2024 12:31:51 +0530 Subject: [PATCH 25/41] Updated unit test to call new api. --- CleverTapSDKTests/CTLocalDataStoreTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m index 0def94fd..22b854b8 100644 --- a/CleverTapSDKTests/CTLocalDataStoreTests.m +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -128,7 +128,7 @@ - (void)testPersistEventAndGetEventDetail { NSDictionary *event = @{CLTAP_EVENT_NAME: eventName}; [self.dataStore persistEvent:event]; sleep(1); - CleverTapEventDetail *eventDetails = [self.dataStore getEventDetail:eventName]; + CleverTapEventDetail *eventDetails = [self.dataStore readUserEventLog:eventName]; XCTAssertEqual(eventDetails.count, 1); XCTAssertGreaterThan(eventDetails.firstTime, 0); XCTAssertGreaterThan(eventDetails.lastTime, 0); From 8dccc29aec61f27b20f13020bdfb618de8d262d2 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Dec 2024 13:08:39 +0530 Subject: [PATCH 26/41] Removed Todos. Completed Todos. Updated Tests, Removed sleep. --- CleverTapSDK.xcodeproj/project.pbxproj | 10 ++++- CleverTapSDK/CTLocalDataStore.m | 11 +++-- CleverTapSDKTests/CTLocalDataStore+Tests.h | 15 +++++++ CleverTapSDKTests/CTLocalDataStore+Tests.m | 19 ++++++++ CleverTapSDKTests/CTLocalDataStoreTests.m | 45 +++++++++++++++++-- .../InApps/CTTriggersMatcherTest.m | 2 - 6 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 CleverTapSDKTests/CTLocalDataStore+Tests.h create mode 100644 CleverTapSDKTests/CTLocalDataStore+Tests.m diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index db3405e3..eb53b9a5 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -186,6 +186,7 @@ 49C189A6243B13110003E4D4 /* CleverTapFeatureFlags.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D4C9F22414EE6C0029477E /* CleverTapFeatureFlags.m */; }; 49E2B18324178E7400AD704B /* CleverTapConfigValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 49E2B18124178E7400AD704B /* CleverTapConfigValue.m */; }; 49E2B18824237DCB00AD704B /* CleverTapFeatureFlagsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D4C9F42414EE770029477E /* CleverTapFeatureFlagsPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 4E0721512D0974B6000DE9C6 /* CTLocalDataStore+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E0721502D0974B6000DE9C6 /* CTLocalDataStore+Tests.m */; }; 4E1F155227692A11009387AE /* CleverTap+Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = D02AC2E8276402CF0031C1BE /* CleverTap+Tests.m */; }; 4E1F155B276B662C009387AE /* EventDetail.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E1F155A276B662C009387AE /* EventDetail.m */; }; 4E1F1562277090D6009387AE /* inapp_alert.json in Resources */ = {isa = PBXBuildFile; fileRef = 4E1F1561277090D6009387AE /* inapp_alert.json */; }; @@ -353,8 +354,8 @@ 6B32A0AD2B9DBE31009ADC57 /* CTTemplatePresenterMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AC2B9DBE31009ADC57 /* CTTemplatePresenterMock.m */; }; 6B32A0B02B9DC374009ADC57 /* CTTemplateArgumentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0AF2B9DC374009ADC57 /* CTTemplateArgumentTest.m */; }; 6B32A0B42B9F2E8F009ADC57 /* CTTestTemplateProducer.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */; }; - 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */; }; 6B453EF92CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */; }; + 6B453EFE2CF74BE2003C7A89 /* CTEventAdapterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */; }; 6B4A0F912B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */; }; 6B535FB62AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; }; 6B535FB72AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */; }; @@ -794,6 +795,8 @@ 4987C664251B5E79003E6BE8 /* CTImageInAppViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTImageInAppViewController.m; sourceTree = ""; }; 4987C667251B5F9E003E6BE8 /* CTImageInAppViewControllerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTImageInAppViewControllerPrivate.h; sourceTree = ""; }; 49E2B18124178E7400AD704B /* CleverTapConfigValue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CleverTapConfigValue.m; sourceTree = ""; }; + 4E07214D2D09738F000DE9C6 /* CTLocalDataStore+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTLocalDataStore+Tests.h"; sourceTree = ""; }; + 4E0721502D0974B6000DE9C6 /* CTLocalDataStore+Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "CTLocalDataStore+Tests.m"; sourceTree = ""; }; 4E1F154E27691CA0009387AE /* CleverTapInstanceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CleverTapInstanceTests.m; sourceTree = ""; }; 4E1F155027692042009387AE /* EventTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventTests.m; sourceTree = ""; }; 4E1F1559276B662B009387AE /* EventDetail.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventDetail.h; sourceTree = ""; }; @@ -921,9 +924,9 @@ 6B32A0B12B9F2A75009ADC57 /* CTCustomTemplatesManager+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTCustomTemplatesManager+Tests.h"; sourceTree = ""; }; 6B32A0B22B9F2E8F009ADC57 /* CTTestTemplateProducer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTTestTemplateProducer.h; sourceTree = ""; }; 6B32A0B32B9F2E8F009ADC57 /* CTTestTemplateProducer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTTestTemplateProducer.m; sourceTree = ""; }; - 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventAdapterTest.m; sourceTree = ""; }; 6B453EF72CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTInAppDisplayViewControllerMock.h; sourceTree = ""; }; 6B453EF82CF621E3003C7A89 /* CTInAppDisplayViewControllerMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppDisplayViewControllerMock.m; sourceTree = ""; }; + 6B453EFD2CF74BE2003C7A89 /* CTEventAdapterTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTEventAdapterTest.m; sourceTree = ""; }; 6B4A0F902B45EF6D00A42C6D /* CTInAppTriggerManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTInAppTriggerManagerTest.m; sourceTree = ""; }; 6B535FB42AD56C60002A2663 /* CTMultiDelegateManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTMultiDelegateManager.h; sourceTree = ""; }; 6B535FB52AD56C60002A2663 /* CTMultiDelegateManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTMultiDelegateManager.m; sourceTree = ""; }; @@ -1647,6 +1650,8 @@ 6BD851C82B45CD1800FA5298 /* CTMultiDelegateManager+Tests.h */, 0B5564552C25946C00B87284 /* CTUserInfoMigratorTest.m */, 0B995A492C36AEDC00AF6006 /* CTLocalDataStoreTests.m */, + 4E07214D2D09738F000DE9C6 /* CTLocalDataStore+Tests.h */, + 4E0721502D0974B6000DE9C6 /* CTLocalDataStore+Tests.m */, 4E8739772C921D9000FDFDFD /* CTDomainFactoryTests.m */, 4E87397C2C9223B300FDFDFD /* CTDomainFactory+Tests.h */, ); @@ -2606,6 +2611,7 @@ 4E1F155227692A11009387AE /* CleverTap+Tests.m in Sources */, 6B9E95AC2C27164B0002D557 /* CTFileDownloadTestHelper.m in Sources */, 6A4427CD2AB8C3B10098866F /* CTInAppEvaluationManagerTest.m in Sources */, + 4E0721512D0974B6000DE9C6 /* CTLocalDataStore+Tests.m in Sources */, 6B9E95B12C2864EC0002D557 /* CTFileDownloaderMock.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 0b46253e..cee87254 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -68,6 +68,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( } return self; } + - (void)addObservers { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; @@ -157,7 +158,7 @@ - (void)addDataSyncFlag:(NSMutableDictionary *)event { # pragma mark - Events -- (NSDictionary *)getStoredEvents { +- (NSDictionary *)getStoredEvents __attribute__((deprecated("This method is deprecated in favor of the newer CTEventDatabase methods"))) { NSDictionary *events = [CTPreferences getObjectForKey:[self storageKeyWithSuffix:kWR_KEY_EVENTS]]; if (self.config.isDefaultInstance) { if (!events) { @@ -171,11 +172,11 @@ - (NSDictionary *)getStoredEvents { return events; } -- (void)setStoredEvents:(NSDictionary *)store { +- (void)setStoredEvents:(NSDictionary *)store __attribute__((deprecated("This method is deprecated in favor of the newer CTEventDatabase methods"))) { [CTPreferences putObject:store forKey:[self storageKeyWithSuffix:kWR_KEY_EVENTS]]; } -- (void)clearStoredEvents { +- (void)clearStoredEvents __attribute__((deprecated("This method is deprecated in favor of the newer CTEventDatabase methods"))) { [CTPreferences removeObjectForKey:[self storageKeyWithSuffix:kWR_KEY_EVENTS]]; } @@ -192,7 +193,6 @@ - (void)persistEvent:(NSDictionary *)event { if (!event || !event[CLTAP_EVENT_NAME]) return; [self runOnBackgroundQueue:^{ NSString *eventName = event[CLTAP_EVENT_NAME]; - // TODO: add normalisation [self.dbHelper upsertEvent:eventName normalizedEventName:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; }]; } @@ -604,12 +604,11 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { @synchronized (self.userEventLogs) { - // TODO: add normalisation + // TODO: add normalisation if ([self.userEventLogs containsObject:eventName]) { return NO; } } - // TODO: Add normalized name here NSInteger count = [self.dbHelper getEventCount:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; if (count > 1) { @synchronized (self.userEventLogs) { diff --git a/CleverTapSDKTests/CTLocalDataStore+Tests.h b/CleverTapSDKTests/CTLocalDataStore+Tests.h new file mode 100644 index 00000000..3cc9fbcb --- /dev/null +++ b/CleverTapSDKTests/CTLocalDataStore+Tests.h @@ -0,0 +1,15 @@ +// +// CTLocalDataStore+Tests.h +// CleverTapSDKTests +// +// Created by Akash Malhotra on 11/12/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTLocalDataStore.h" + +@interface CTLocalDataStore (Tests) +- (void)runOnBackgroundQueue:(void (^)(void))taskBlock; +@property (nonatomic, readonly) dispatch_queue_t backgroundQueue; +@end + diff --git a/CleverTapSDKTests/CTLocalDataStore+Tests.m b/CleverTapSDKTests/CTLocalDataStore+Tests.m new file mode 100644 index 00000000..2272c195 --- /dev/null +++ b/CleverTapSDKTests/CTLocalDataStore+Tests.m @@ -0,0 +1,19 @@ +// +// CTLocalDataStore+Tests.m +// CleverTapSDKTests +// +// Created by Akash Malhotra on 11/12/24. +// Copyright © 2024 CleverTap. All rights reserved. +// + +#import "CTLocalDataStore+Tests.h" +#import + +@implementation CTLocalDataStore (Tests) + +- (dispatch_queue_t)backgroundQueue { + Ivar ivar = class_getInstanceVariable([self class], "_backgroundQueue"); + return object_getIvar(self, ivar); +} + +@end diff --git a/CleverTapSDKTests/CTLocalDataStoreTests.m b/CleverTapSDKTests/CTLocalDataStoreTests.m index 22b854b8..247d77e7 100644 --- a/CleverTapSDKTests/CTLocalDataStoreTests.m +++ b/CleverTapSDKTests/CTLocalDataStoreTests.m @@ -11,6 +11,7 @@ #import "CTProfileBuilder.h" #import "CTConstants.h" #import "XCTestCase+XCTestCase_Tests.h" +#import "CTLocalDataStore+Tests.h" @interface CTLocalDataStoreTests : XCTestCase @property (nonatomic, strong) CTLocalDataStore *dataStore; @@ -111,14 +112,16 @@ - (void)testGetUserAttributeChangePropertiesWithIncrementCommand { } - (void)testSetAndGetProfileValueForKey { + + [self waitForInitDataStore]; NSDictionary *profile = @{@"someKey": @"someValue"}; - sleep(1); // Datastore takes a second to initialise in the background thread [self.dataStore setProfileFields:profile]; XCTAssertEqualObjects([self.dataStore getProfileFieldForKey:@"someKey"], @"someValue"); } + - (void)testSetProfileFieldWithKeyAndValue { - sleep(1); // Datastore takes a second to initialise in the background thread + [self waitForInitDataStore]; [self.dataStore setProfileFieldWithKey:@"someKey" andValue:@"someValue"]; XCTAssertEqualObjects([self.dataStore getProfileFieldForKey:@"someKey"], @"someValue"); } @@ -126,12 +129,46 @@ - (void)testSetProfileFieldWithKeyAndValue { - (void)testPersistEventAndGetEventDetail { NSString *eventName = [self randomString]; NSDictionary *event = @{CLTAP_EVENT_NAME: eventName}; - [self.dataStore persistEvent:event]; - sleep(1); + XCTestExpectation *expectation = [self expectationWithDescription:@"Datastore persist event"]; + // WAIT FOR DATA STORE TO FINISH INIT + dispatch_async(self.dataStore.backgroundQueue, ^{ + // Wait for the background queue to complete datastore setup. + [self.dataStore persistEvent:event]; + [self.dataStore runOnBackgroundQueue:^{ + [expectation fulfill]; + }]; + }); + + [self waitForExpectationsWithTimeout:5 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Datastore initialization did not complete in time."); + } + }]; + + CleverTapEventDetail *eventDetails = [self.dataStore readUserEventLog:eventName]; + XCTAssertEqualObjects(eventDetails.eventName, eventName); XCTAssertEqual(eventDetails.count, 1); XCTAssertGreaterThan(eventDetails.firstTime, 0); XCTAssertGreaterThan(eventDetails.lastTime, 0); } +- (void)waitForInitDataStore { + XCTestExpectation *expectation = [self expectationWithDescription:@"Datastore initialization"]; + + // WAIT FOR DATA STORE TO FINISH INIT + dispatch_async(self.dataStore.backgroundQueue, ^{ + // Wait for the background queue to complete datastore setup. + [self.dataStore runOnBackgroundQueue:^{ + [expectation fulfill]; + }]; + }); + + [self waitForExpectationsWithTimeout:5 handler:^(NSError * _Nullable error) { + if (error) { + XCTFail(@"Datastore initialization did not complete in time."); + } + }]; +} + @end diff --git a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m index bef7652e..bbc905ae 100644 --- a/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m +++ b/CleverTapSDKTests/InApps/CTTriggersMatcherTest.m @@ -2020,7 +2020,6 @@ - (void)testMatchEventWithFirstTimeOnly { } ]; - // TODO: Update tests after db/localdatastore is updated with db methods CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); id mockIsEventLoggedFirstTime = OCMStub([dataStoreMock isEventLoggedFirstTime:@"event1"]).andReturn(YES); @@ -2051,7 +2050,6 @@ - (void)testMatchChargedEventWithFirstTimeOnly { } ]; - // TODO: Update tests after db/localdatastore is updated with db methods CTTriggersMatcher *triggerMatcher = [[CTTriggersMatcher alloc] initWithDataStore:self.dataStore]; CTLocalDataStore *dataStoreMock = OCMPartialMock(self.dataStore); From d9c4f3010630fa62c9d10ea5b14f5110b7d73066 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Dec 2024 15:14:31 +0530 Subject: [PATCH 27/41] used normalised names for the in-memory set --- CleverTapSDK/CTLocalDataStore.m | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index cee87254..df3d4903 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -603,16 +603,17 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { } - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { + NSString *normalizedName = [CTUtils getNormalizedName:eventName]; @synchronized (self.userEventLogs) { // TODO: add normalisation - if ([self.userEventLogs containsObject:eventName]) { + if ([self.userEventLogs containsObject:normalizedName]) { return NO; } } - NSInteger count = [self.dbHelper getEventCount:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; + NSInteger count = [self.dbHelper getEventCount:normalizedName deviceID:self.deviceInfo.deviceId]; if (count > 1) { @synchronized (self.userEventLogs) { - [self.userEventLogs addObject:eventName]; + [self.userEventLogs addObject:normalizedName]; } } return count == 1; From 844c8edd0e9f5d5c3ee6c67673c4487510fbf73f Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Dec 2024 16:37:31 +0530 Subject: [PATCH 28/41] removed todo comments --- CleverTapSDK/CTLocalDataStore.m | 1 - 1 file changed, 1 deletion(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index df3d4903..aec780c5 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -605,7 +605,6 @@ - (void)removeProfileFieldsWithKeys:(NSArray *)keys { - (BOOL)isEventLoggedFirstTime:(NSString*)eventName { NSString *normalizedName = [CTUtils getNormalizedName:eventName]; @synchronized (self.userEventLogs) { - // TODO: add normalisation if ([self.userEventLogs containsObject:normalizedName]) { return NO; } From 66c95b04f7828414a0c2762eca6276006217ec09 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Dec 2024 19:23:11 +0530 Subject: [PATCH 29/41] renamed getDatabaseVersion to databaseVersion --- CleverTapSDK/EventDatabase/CTEventDatabase.h | 2 +- CleverTapSDK/EventDatabase/CTEventDatabase.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 2fcba927..97e4f334 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -16,7 +16,7 @@ + (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config; - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config; -- (NSInteger)getDatabaseVersion; +- (NSInteger)databaseVersion; - (BOOL)insertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 9967e81e..0b971ef7 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -44,7 +44,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { return self; } -- (NSInteger)getDatabaseVersion { +- (NSInteger)databaseVersion { if (!_eventDatabase) { CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); return 0; @@ -546,7 +546,7 @@ - (void)setDatabaseVersion:(NSInteger)version { } - (void)checkAndUpdateDatabaseVersion { - NSInteger currentVersion = [self getDatabaseVersion]; + NSInteger currentVersion = [self databaseVersion]; if (currentVersion < CLTAP_DATABASE_VERSION) { // Handle version changes here in future. From 844c08bc2dcbc3749ea99181be10d016fc1014a2 Mon Sep 17 00:00:00 2001 From: Akash Malhotra Date: Wed, 11 Dec 2024 19:25:19 +0530 Subject: [PATCH 30/41] nil checks when fetching eventName --- CleverTapSDK/EventDatabase/CTEventDatabase.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 0b971ef7..28a99f39 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -317,7 +317,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName const char *deviceID = (const char *)sqlite3_column_text(statement, 5); eventDetail = [[CleverTapEventDetail alloc] init]; - eventDetail.eventName = [NSString stringWithUTF8String:eventName]; + eventDetail.eventName = eventName ? [NSString stringWithUTF8String:eventName] : nil; eventDetail.normalizedEventName = [NSString stringWithUTF8String:normalizedEventName]; eventDetail.count = count; eventDetail.firstTime = firstTs; @@ -362,7 +362,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName ed.count = count; ed.firstTime = firstTs; ed.lastTime = lastTs; - ed.eventName = [NSString stringWithUTF8String:eventName]; + ed.eventName = eventName ? [NSString stringWithUTF8String:eventName] : nil; ed.normalizedEventName = [NSString stringWithUTF8String:normalizedEventName]; ed.deviceID = [NSString stringWithUTF8String:deviceID]; From cf27ccefbc0c8b5e337cdde590ae403915eef5eb Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 12 Dec 2024 23:46:01 +0530 Subject: [PATCH 31/41] Addressed review comments. --- CleverTapSDK/CTLocalDataStore.m | 2 +- CleverTapSDK/EventDatabase/CTEventDatabase.h | 6 +- CleverTapSDK/EventDatabase/CTEventDatabase.m | 97 ++++++++++--------- .../EventDatabase/CTEventDatabaseTests.m | 23 +++-- 4 files changed, 67 insertions(+), 61 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index aec780c5..45b753c8 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -48,7 +48,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _deviceInfo = deviceInfo; self.dispatchQueueManager = dispatchQueueManager; self.userEventLogs = [NSMutableSet set]; - self.dbHelper = [CTEventDatabase sharedInstanceWithConfig:self.config]; + self.dbHelper = [CTEventDatabase sharedInstance]; localProfileUpdateExpiryStore = [NSMutableDictionary new]; _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 97e4f334..23ed31db 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -8,13 +8,13 @@ #import #import -#import "CleverTapInstanceConfig.h" #import "CleverTapEventDetail.h" +#import "CTClock.h" @interface CTEventDatabase : NSObject -+ (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config; -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config; ++ (instancetype)sharedInstance; +- (instancetype)initWithClock:(id )clock; - (NSInteger)databaseVersion; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 28a99f39..304acccd 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -8,10 +8,11 @@ #import "CTEventDatabase.h" #import "CTConstants.h" +#import "CTSystemClock.h" @interface CTEventDatabase() -@property (nonatomic, strong) CleverTapInstanceConfig *config; +@property (nonatomic, strong) id clock; @end @@ -20,19 +21,19 @@ @implementation CTEventDatabase { dispatch_queue_t _databaseQueue; } -+ (instancetype)sharedInstanceWithConfig:(CleverTapInstanceConfig *)config { ++ (instancetype)sharedInstance { static CTEventDatabase *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] initWithConfig:config]; + sharedInstance = [[self alloc] initWithClock:[[CTSystemClock alloc] init]]; }); return sharedInstance; } -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { +- (instancetype)initWithClock:(id)clock { if (self = [super init]) { - _config = config; - _databaseQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.eventDatabaseQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_CONCURRENT); + _clock = clock; + _databaseQueue = dispatch_queue_create([@"com.clevertap.eventDatabaseQueue" UTF8String], DISPATCH_QUEUE_CONCURRENT); [self openDatabase]; // Perform cleanup/deletion of rows on instance creation if total row count @@ -46,7 +47,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config { - (NSInteger)databaseVersion { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return 0; } @@ -61,7 +62,7 @@ - (NSInteger)databaseVersion { } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -72,14 +73,14 @@ - (BOOL)insertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } __block BOOL success = NO; // For new event, set count as 1 NSInteger count = 1; - NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + NSInteger currentTs = [[self.clock timeIntervalSince1970] integerValue]; const char *insertSQL = "INSERT INTO CTUserEventLogs (eventName, normalizedEventName, count, firstTs, lastTs, deviceID) VALUES (?, ?, ?, ?, ?, ?)"; dispatch_sync(_databaseQueue, ^{ @@ -96,12 +97,12 @@ - (BOOL)insertEvent:(NSString *)eventName if (result == SQLITE_DONE) { success = YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Insert Table SQL error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"Insert Table SQL error: %s", sqlite3_errmsg(self->_eventDatabase)); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare insert statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"Failed to prepare insert statement: %s", sqlite3_errmsg(self->_eventDatabase)); } }); @@ -111,11 +112,11 @@ - (BOOL)insertEvent:(NSString *)eventName - (BOOL)updateEvent:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } - NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + NSInteger currentTs = [[self.clock timeIntervalSince1970] integerValue]; const char *updateSQL = "UPDATE CTUserEventLogs SET count = count + 1, lastTs = ? WHERE normalizedEventName = ? AND deviceID = ?"; __block BOOL success = NO; @@ -131,12 +132,12 @@ - (BOOL)updateEvent:(NSString *)normalizedEventName if (result == SQLITE_DONE) { success = YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Update Table SQL error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"Update Table SQL error: %s", sqlite3_errmsg(self->_eventDatabase)); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare update statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"Failed to prepare update statement: %s", sqlite3_errmsg(self->_eventDatabase)); } }); @@ -161,7 +162,7 @@ - (BOOL)upsertEvent:(NSString *)eventName - (BOOL)eventExists:(NSString *)normalizedEventName forDeviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } @@ -182,11 +183,11 @@ - (BOOL)eventExists:(NSString *)normalizedEventName exists = YES; } } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL check query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL check query error: %s", sqlite3_errmsg(_eventDatabase)); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -202,7 +203,7 @@ - (void)dealloc { - (NSInteger)getEventCount:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return 0; } @@ -218,12 +219,12 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName if (sqlite3_step(statement) == SQLITE_ROW) { count = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); + CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -233,7 +234,7 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName - (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return 0; } @@ -249,12 +250,12 @@ - (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName if (sqlite3_step(statement) == SQLITE_ROW) { firstTs = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); + CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -264,7 +265,7 @@ - (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName - (NSInteger)getLastTimestamp:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return 0; } @@ -280,12 +281,12 @@ - (NSInteger)getLastTimestamp:(NSString *)normalizedEventName if (sqlite3_step(statement) == SQLITE_ROW) { lastTs = sqlite3_column_int(statement, 0); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); + CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -295,7 +296,7 @@ - (NSInteger)getLastTimestamp:(NSString *)normalizedEventName - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return nil; } @@ -325,11 +326,11 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName eventDetail.deviceID = [NSString stringWithUTF8String:deviceID]; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ No event found with eventName: %@ and deviceID: %@", self, normalizedEventName, deviceID); + CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -338,7 +339,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return nil; } @@ -371,7 +372,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } }); @@ -380,7 +381,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName - (BOOL)deleteAllRows { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } @@ -394,7 +395,7 @@ - (BOOL)deleteAllRows { if (result == SQLITE_OK) { success = YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting all rows from CTUserEventLogs: %s", self, errMsg); + CleverTapLogStaticInternal(@"SQL Error deleting all rows from CTUserEventLogs: %s", errMsg); } }); @@ -404,7 +405,7 @@ - (BOOL)deleteAllRows { - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } @@ -421,7 +422,7 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit int indexResult = sqlite3_exec(_eventDatabase, createIndexSQL, NULL, NULL, &errMsg); if (indexResult != SQLITE_OK) { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to create index on lastTs: %s", self, errMsg); + CleverTapLogStaticInternal(@"Failed to create index on lastTs: %s", errMsg); sqlite3_free(errMsg); sqlite3_exec(_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); // Rollback transaction if index creation fails return; @@ -446,20 +447,20 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit if (result == SQLITE_DONE) { success = YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error deleting rows: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL Error deleting rows: %s", sqlite3_errmsg(_eventDatabase)); } sqlite3_finalize(deleteStatement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } } } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to count rows in CTUserEventLogs", self); + CleverTapLogStaticInternal(@"Failed to count rows in CTUserEventLogs"); } sqlite3_finalize(countStatement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL prepare query error: %s", self, sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); } // Commit or rollback the transaction based on success @@ -483,14 +484,14 @@ - (BOOL)openDatabase { [self checkAndUpdateDatabaseVersion]; return YES; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to open database - CleverTap-Events.db", self); + CleverTapLogStaticInternal(@"Failed to open database - CleverTap-Events.db"); return NO; } } - (BOOL)createTable { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return NO; } @@ -505,7 +506,7 @@ - (BOOL)createTable { // Set the database version to the initial version, ie 1. [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Create Table SQL error: %s", self, errMsg); + CleverTapLogStaticInternal(@"Create Table SQL error: %s", errMsg); sqlite3_free(errMsg); } }); @@ -522,7 +523,7 @@ - (void)closeDatabase { - (void)setDatabaseVersion:(NSInteger)version { if (!_eventDatabase) { - CleverTapLogInternal(self.config.logLevel, @"%@ Event database is not open, cannot execute SQL.", self); + CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); return; } @@ -535,12 +536,12 @@ - (void)setDatabaseVersion:(NSInteger)version { int result = sqlite3_step(statement); if (result != SQLITE_OK) { - CleverTapLogInternal(self.config.logLevel, @"%@ SQL Error: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"SQL Error: %s", sqlite3_errmsg(self->_eventDatabase)); } sqlite3_finalize(statement); } else { - CleverTapLogInternal(self.config.logLevel, @"%@ Failed to prepare update statement: %s", self, sqlite3_errmsg(self->_eventDatabase)); + CleverTapLogStaticInternal(@"Failed to prepare update statement: %s", sqlite3_errmsg(self->_eventDatabase)); } }); } @@ -551,7 +552,7 @@ - (void)checkAndUpdateDatabaseVersion { if (currentVersion < CLTAP_DATABASE_VERSION) { // Handle version changes here in future. [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; - CleverTapLogInternal(self.config.logLevel, @"%@ Schema migration required. Current version: %ld, Target version: %ld", self, (long)currentVersion, (long)CLTAP_DATABASE_VERSION); + CleverTapLogStaticInternal(@"Schema migration required. Current version: %ld, Target version: %ld", (long)currentVersion, (long)CLTAP_DATABASE_VERSION); } } diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index c515847b..84c3bdcb 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -8,17 +8,17 @@ #import #import "CTEventDatabase.h" -#import "CleverTapInstanceConfig.h" #import "CTUtils.h" +#import "CTClockMock.h" static NSString *kEventName = @"Test Event"; static NSString *kDeviceID = @"Test Device"; @interface CTEventDatabaseTests : XCTestCase -@property (nonatomic, strong) CleverTapInstanceConfig *config; @property (nonatomic, strong) CTEventDatabase *eventDatabase; @property (nonatomic, strong) NSString *normalizedEventName; +@property (nonatomic, strong) CTClockMock *mockClock; @end @@ -27,8 +27,9 @@ @implementation CTEventDatabaseTests - (void)setUp { [super setUp]; - self.config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccountId" accountToken:@"testAccountToken"]; - self.eventDatabase = [CTEventDatabase sharedInstanceWithConfig:self.config]; + CTClockMock *mockClock = [[CTClockMock alloc] initWithCurrentDate:[NSDate date]]; + self.mockClock = mockClock; + self.eventDatabase = [[CTEventDatabase alloc] initWithClock:mockClock]; self.normalizedEventName = [CTUtils getNormalizedName:kEventName]; } @@ -39,7 +40,7 @@ - (void)tearDown { } - (void)testGetDatabaseVersion { - NSInteger dbVersion = [self.eventDatabase getDatabaseVersion]; + NSInteger dbVersion = [self.eventDatabase databaseVersion]; XCTAssertEqual(dbVersion, 1); } @@ -143,7 +144,8 @@ - (void)testGetCountForEventNameNotExists { } - (void)testFirstTimestampForEventName { - NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; + NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase insertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; @@ -153,7 +155,8 @@ - (void)testFirstTimestampForEventName { } - (void)testLastTimestampForEventName { - NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; + NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase insertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; @@ -163,7 +166,8 @@ - (void)testLastTimestampForEventName { } - (void)testLastTimestampForEventNameUpdated { - NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; + NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase insertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; @@ -171,7 +175,8 @@ - (void)testLastTimestampForEventNameUpdated { NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(lastTs, currentTs); - NSInteger newCurrentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; + self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; + NSInteger newCurrentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; NSInteger newLastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(newLastTs, newCurrentTs); From 2384163309a11dcec64effa82debd1c332c3b54f Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 17 Dec 2024 14:19:27 +0530 Subject: [PATCH 32/41] - Updated database query to use same Dispatch manager serial queue to avoid deadlock - Fixes race condition in persist event due to different serial queue. - Updated unit tests. --- CleverTapSDK/CTLocalDataStore.m | 9 +- CleverTapSDK/CleverTap.m | 6 +- CleverTapSDK/EventDatabase/CTEventDatabase.h | 32 +- CleverTapSDK/EventDatabase/CTEventDatabase.m | 290 ++++++++++-------- .../EventDatabase/CTEventDatabaseTests.m | 205 ++++++------- 5 files changed, 294 insertions(+), 248 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 45b753c8..6191da66 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -48,7 +48,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( _deviceInfo = deviceInfo; self.dispatchQueueManager = dispatchQueueManager; self.userEventLogs = [NSMutableSet set]; - self.dbHelper = [CTEventDatabase sharedInstance]; + self.dbHelper = [CTEventDatabase sharedInstanceWithDispatchQueueManager:dispatchQueueManager]; localProfileUpdateExpiryStore = [NSMutableDictionary new]; _backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); @@ -191,10 +191,9 @@ - (void)clearStoredEvents __attribute__((deprecated("This method is deprecated i */ - (void)persistEvent:(NSDictionary *)event { if (!event || !event[CLTAP_EVENT_NAME]) return; - [self runOnBackgroundQueue:^{ - NSString *eventName = event[CLTAP_EVENT_NAME]; - [self.dbHelper upsertEvent:eventName normalizedEventName:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; - }]; + + NSString *eventName = event[CLTAP_EVENT_NAME]; + [self.dbHelper upsertEvent:eventName normalizedEventName:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; } /*! diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index c643e05b..dfeff431 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -1959,7 +1959,9 @@ - (void)processEvent:(NSDictionary *)event withType:(CleverTapEventType)eventTyp } if (eventType == CleverTapEventTypeRaised || eventType == CleverTapEventTypeNotificationViewed) { - [self.localDataStore persistEvent:mutableEvent]; + [self.dispatchQueueManager runSerialAsync:^{ + [self.localDataStore persistEvent:mutableEvent]; + }]; } if (eventType == CleverTapEventTypeProfile) { @@ -3134,7 +3136,7 @@ - (NSTimeInterval)getUserEventLogLastTs:(NSString *)eventName { if (!self.config.enablePersonalization) { return -1; } - return [self.localDataStore readUserEventLogFirstTs:eventName]; + return [self.localDataStore readUserEventLogLastTs:eventName]; } - (NSDictionary *)getUserEventLogHistory { diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 23ed31db..f0aab133 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -10,27 +10,32 @@ #import #import "CleverTapEventDetail.h" #import "CTClock.h" +#import "CTDispatchQueueManager.h" @interface CTEventDatabase : NSObject -+ (instancetype)sharedInstance; -- (instancetype)initWithClock:(id )clock; ++ (instancetype)sharedInstanceWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager; +- (instancetype)initWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager + clock:(id)clock; -- (NSInteger)databaseVersion; +- (void)databaseVersionWithCompletion:(void (^)(NSInteger version))completion; -- (BOOL)insertEvent:(NSString *)eventName +- (void)insertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID; + deviceID:(NSString *)deviceID + completion:(void (^)(BOOL success))completion; -- (BOOL)updateEvent:(NSString *)normalizedEventName - forDeviceID:(NSString *)deviceID; +- (void)updateEvent:(NSString *)normalizedEventName + forDeviceID:(NSString *)deviceID + completion:(void (^)(BOOL success))completion; -- (BOOL)upsertEvent:(NSString *)eventName +- (void)upsertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName deviceID:(NSString *)deviceID; -- (BOOL)eventExists:(NSString *)normalizedEventName - forDeviceID:(NSString *)deviceID; +- (void)eventExists:(NSString *)normalizedEventName + forDeviceID:(NSString *)deviceID + completion:(void (^)(BOOL exists))completion; - (NSInteger)getEventCount:(NSString *)normalizedEventName deviceID:(NSString *)deviceID; @@ -46,9 +51,10 @@ normalizedEventName:(NSString *)normalizedEventName - (NSArray *)getAllEventsForDeviceID:(NSString *)deviceID; -- (BOOL)deleteAllRows; +- (void)deleteAllRowsWithCompletion:(void (^)(BOOL success))completion; -- (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit - numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup; +- (void)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit + numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup + completion:(void (^)(BOOL success))completion; @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 304acccd..6ffc7077 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -12,69 +12,74 @@ @interface CTEventDatabase() +@property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; @property (nonatomic, strong) id clock; @end @implementation CTEventDatabase { sqlite3 *_eventDatabase; - dispatch_queue_t _databaseQueue; } -+ (instancetype)sharedInstance { ++ (instancetype)sharedInstanceWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager { static CTEventDatabase *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedInstance = [[self alloc] initWithClock:[[CTSystemClock alloc] init]]; + sharedInstance = [[self alloc] initWithDispatchQueueManager:dispatchQueueManager + clock:[[CTSystemClock alloc] init]]; }); return sharedInstance; } -- (instancetype)initWithClock:(id)clock { +- (instancetype)initWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager + clock:(id)clock { if (self = [super init]) { + _dispatchQueueManager = dispatchQueueManager; _clock = clock; - _databaseQueue = dispatch_queue_create([@"com.clevertap.eventDatabaseQueue" UTF8String], DISPATCH_QUEUE_CONCURRENT); [self openDatabase]; // Perform cleanup/deletion of rows on instance creation if total row count // exceeds mac threshold limit. NSInteger maxRowLimit = CLTAP_EVENT_DB_MAX_ROW_LIMIT; NSInteger numberOfRowsToCleanup = CLTAP_EVENT_DB_ROWS_TO_CLEANUP; - [self deleteLeastRecentlyUsedRows:maxRowLimit numberOfRowsToCleanup:numberOfRowsToCleanup]; + [self deleteLeastRecentlyUsedRows:maxRowLimit numberOfRowsToCleanup:numberOfRowsToCleanup completion:nil]; } return self; } -- (NSInteger)databaseVersion { +- (void)databaseVersionWithCompletion:(void (^)(NSInteger version))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return 0; + return; } const char *querySQL = "PRAGMA user_version;"; __block NSInteger version = 0; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { if (sqlite3_step(statement) == SQLITE_ROW) { version = sqlite3_column_int(statement, 0); } sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - - return version; + + if (completion) { + completion(version); + } + }]; } -- (BOOL)insertEvent:(NSString *)eventName +- (void)insertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID { + deviceID:(NSString *)deviceID + completion:(void (^)(BOOL success))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } __block BOOL success = NO; @@ -83,9 +88,9 @@ - (BOOL)insertEvent:(NSString *)eventName NSInteger currentTs = [[self.clock timeIntervalSince1970] integerValue]; const char *insertSQL = "INSERT INTO CTUserEventLogs (eventName, normalizedEventName, count, firstTs, lastTs, deviceID) VALUES (?, ?, ?, ?, ?, ?)"; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, insertSQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, insertSQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [eventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_int(statement, 3, (int)count); @@ -104,16 +109,19 @@ - (BOOL)insertEvent:(NSString *)eventName } else { CleverTapLogStaticInternal(@"Failed to prepare insert statement: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - - return success; + + if (completion) { + completion(success); + } + }]; } -- (BOOL)updateEvent:(NSString *)normalizedEventName - forDeviceID:(NSString *)deviceID { +- (void)updateEvent:(NSString *)normalizedEventName + forDeviceID:(NSString *)deviceID + completion:(void (^)(BOOL success))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } NSInteger currentTs = [[self.clock timeIntervalSince1970] integerValue]; @@ -121,9 +129,9 @@ - (BOOL)updateEvent:(NSString *)normalizedEventName "UPDATE CTUserEventLogs SET count = count + 1, lastTs = ? WHERE normalizedEventName = ? AND deviceID = ?"; __block BOOL success = NO; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, updateSQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_int(statement, 1, (int)currentTs); sqlite3_bind_text(statement, 2, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 3, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -139,40 +147,47 @@ - (BOOL)updateEvent:(NSString *)normalizedEventName } else { CleverTapLogStaticInternal(@"Failed to prepare update statement: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - - return success; + + if (completion) { + completion(success); + } + }]; } -- (BOOL)upsertEvent:(NSString *)eventName +- (void)upsertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { - BOOL success = NO; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - BOOL eventExists = [self eventExists:normalizedEventName forDeviceID:deviceID]; - if (!eventExists) { - success = [self insertEvent:eventName normalizedEventName:normalizedEventName deviceID:deviceID]; - } else { - success = [self updateEvent:normalizedEventName forDeviceID:deviceID]; - } - - return success; + [self.dispatchQueueManager runSerialAsync:^{ + [self eventExists:normalizedEventName forDeviceID:deviceID completion:^(BOOL exists) { + if (!exists) { + [self insertEvent:eventName normalizedEventName:normalizedEventName deviceID:deviceID completion:nil]; + } else { + [self updateEvent:normalizedEventName forDeviceID:deviceID completion:nil]; + } + dispatch_semaphore_signal(semaphore); + }]; + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } -- (BOOL)eventExists:(NSString *)normalizedEventName - forDeviceID:(NSString *)deviceID { +- (void)eventExists:(NSString *)normalizedEventName + forDeviceID:(NSString *)deviceID + completion:(void (^)(BOOL exists))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } const char *query = "SELECT COUNT(*) FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block BOOL exists = NO; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, query, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, query, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -183,21 +198,23 @@ - (BOOL)eventExists:(NSString *)normalizedEventName exists = YES; } } else { - CleverTapLogStaticInternal(@"SQL check query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL check query error: %s", sqlite3_errmsg(self->_eventDatabase)); } sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - - return exists; + + if (completion) { + completion(exists); + } + }]; } - (void)dealloc { - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ [self closeDatabase]; - }); + }]; } - (NSInteger)getEventCount:(NSString *)normalizedEventName @@ -209,10 +226,11 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger count = 0; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -224,10 +242,12 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return count; } @@ -240,10 +260,11 @@ - (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger firstTs = 0; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -255,10 +276,12 @@ - (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return firstTs; } @@ -271,10 +294,11 @@ - (NSInteger)getLastTimestamp:(NSString *)normalizedEventName const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger lastTs = 0; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -286,10 +310,12 @@ - (NSInteger)getLastTimestamp:(NSString *)normalizedEventName sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); - + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return lastTs; } @@ -302,10 +328,11 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block CleverTapEventDetail *eventDetail = nil; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -330,10 +357,12 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName } sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return eventDetail; } @@ -345,10 +374,11 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE deviceID = ?"; __block NSMutableArray *eventDataArray = [NSMutableArray array]; + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; - if (sqlite3_prepare_v2(_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [deviceID UTF8String], -1, SQLITE_TRANSIENT); while (sqlite3_step(statement) == SQLITE_ROW) { @@ -372,65 +402,70 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName } sqlite3_finalize(statement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return [eventDataArray copy]; } -- (BOOL)deleteAllRows { +- (void)deleteAllRowsWithCompletion:(void (^)(BOOL success))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } const char *querySQL = "DELETE FROM CTUserEventLogs"; __block BOOL success = NO; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ char *errMsg = NULL; - int result = sqlite3_exec(_eventDatabase, querySQL, NULL, NULL, &errMsg); + int result = sqlite3_exec(self->_eventDatabase, querySQL, NULL, NULL, &errMsg); if (result == SQLITE_OK) { success = YES; } else { CleverTapLogStaticInternal(@"SQL Error deleting all rows from CTUserEventLogs: %s", errMsg); } - }); - - return success; + + if (completion) { + completion(success); + } + }]; } -- (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit - numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup { +- (void)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit + numberOfRowsToCleanup:(NSInteger)numberOfRowsToCleanup + completion:(void (^)(BOOL success))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } __block BOOL success = NO; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ // Begin a transaction to ensure atomicity - sqlite3_exec(_eventDatabase, "BEGIN TRANSACTION;", NULL, NULL, NULL); + sqlite3_exec(self->_eventDatabase, "BEGIN TRANSACTION;", NULL, NULL, NULL); // Create an index on the `lastTs` column if it doesn't exist which will improve performance // while deletion when table is large const char *createIndexSQL = "CREATE INDEX IF NOT EXISTS idx_lastTs ON CTUserEventLogs(lastTs);"; char *errMsg = NULL; - int indexResult = sqlite3_exec(_eventDatabase, createIndexSQL, NULL, NULL, &errMsg); + int indexResult = sqlite3_exec(self->_eventDatabase, createIndexSQL, NULL, NULL, &errMsg); if (indexResult != SQLITE_OK) { CleverTapLogStaticInternal(@"Failed to create index on lastTs: %s", errMsg); sqlite3_free(errMsg); - sqlite3_exec(_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); // Rollback transaction if index creation fails + sqlite3_exec(self->_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); // Rollback transaction if index creation fails return; } NSString *countQuerySQL = @"SELECT COUNT(*) FROM CTUserEventLogs;"; sqlite3_stmt *countStatement; - if (sqlite3_prepare_v2(_eventDatabase, [countQuerySQL UTF8String], -1, &countStatement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, [countQuerySQL UTF8String], -1, &countStatement, NULL) == SQLITE_OK) { if (sqlite3_step(countStatement) == SQLITE_ROW) { NSInteger currentRowCount = sqlite3_column_int(countStatement, 0); if (currentRowCount > maxRowLimit) { @@ -440,19 +475,19 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit // Delete the least recently used rows based on lastTs const char *deleteSQL = "DELETE FROM CTUserEventLogs WHERE (normalizedEventName, deviceID) IN (SELECT normalizedEventName, deviceID FROM CTUserEventLogs ORDER BY lastTs ASC LIMIT ?);"; sqlite3_stmt *deleteStatement; - if (sqlite3_prepare_v2(_eventDatabase, deleteSQL, -1, &deleteStatement, NULL) == SQLITE_OK) { + if (sqlite3_prepare_v2(self->_eventDatabase, deleteSQL, -1, &deleteStatement, NULL) == SQLITE_OK) { sqlite3_bind_int(deleteStatement, 1, (int)rowsToDelete); int result = sqlite3_step(deleteStatement); if (result == SQLITE_DONE) { success = YES; } else { - CleverTapLogStaticInternal(@"SQL Error deleting rows: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL Error deleting rows: %s", sqlite3_errmsg(self->_eventDatabase)); } sqlite3_finalize(deleteStatement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } } } else { @@ -460,44 +495,51 @@ - (BOOL)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit } sqlite3_finalize(countStatement); } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(_eventDatabase)); + CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } // Commit or rollback the transaction based on success if (success) { - sqlite3_exec(_eventDatabase, "COMMIT;", NULL, NULL, NULL); + sqlite3_exec(self->_eventDatabase, "COMMIT;", NULL, NULL, NULL); } else { - sqlite3_exec(_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); + sqlite3_exec(self->_eventDatabase, "ROLLBACK;", NULL, NULL, NULL); } - }); - - return success; + + if (completion) { + completion(success); + } + }]; } #pragma mark - Private methods -- (BOOL)openDatabase { +- (void)openDatabase { NSString *databasePath = [self databasePath]; - if (sqlite3_open_v2([databasePath UTF8String], &_eventDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) == SQLITE_OK) { - // Create table, check and update the version if needed - [self createTable]; - [self checkAndUpdateDatabaseVersion]; - return YES; - } else { - CleverTapLogStaticInternal(@"Failed to open database - CleverTap-Events.db"); - return NO; - } + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + [self.dispatchQueueManager runSerialAsync:^{ + if (sqlite3_open_v2([databasePath UTF8String], &self->_eventDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) == SQLITE_OK) { + // Create table, check and update the version if needed + [self createTableWithCompletion:^(BOOL exists) { + [self checkAndUpdateDatabaseVersion]; + }]; + } else { + CleverTapLogStaticInternal(@"Failed to open database - CleverTap-Events.db"); + } + dispatch_semaphore_signal(semaphore); + }]; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } -- (BOOL)createTable { +- (void)createTableWithCompletion:(void (^)(BOOL success))completion { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return NO; + return; } __block BOOL success = NO; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ char *errMsg; const char *createTableSQL = "CREATE TABLE IF NOT EXISTS CTUserEventLogs (eventName TEXT, normalizedEventName TEXT, count INTEGER, firstTs INTEGER, lastTs INTEGER, deviceID TEXT, PRIMARY KEY (normalizedEventName, deviceID))"; if (sqlite3_exec(self->_eventDatabase, createTableSQL, NULL, NULL, &errMsg) == SQLITE_OK) { @@ -509,9 +551,11 @@ - (BOOL)createTable { CleverTapLogStaticInternal(@"Create Table SQL error: %s", errMsg); sqlite3_free(errMsg); } - }); - - return success; + + if (completion) { + completion(success); + } + }]; } - (void)closeDatabase { @@ -529,7 +573,7 @@ - (void)setDatabaseVersion:(NSInteger)version { NSString *updateSQL = [NSString stringWithFormat:@"PRAGMA user_version = %ld;", (long)version]; - dispatch_sync(_databaseQueue, ^{ + [self.dispatchQueueManager runSerialAsync:^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(self->_eventDatabase, [updateSQL UTF8String], -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_int(statement, 1, (int)version); @@ -543,17 +587,17 @@ - (void)setDatabaseVersion:(NSInteger)version { } else { CleverTapLogStaticInternal(@"Failed to prepare update statement: %s", sqlite3_errmsg(self->_eventDatabase)); } - }); + }]; } - (void)checkAndUpdateDatabaseVersion { - NSInteger currentVersion = [self databaseVersion]; - - if (currentVersion < CLTAP_DATABASE_VERSION) { - // Handle version changes here in future. - [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; - CleverTapLogStaticInternal(@"Schema migration required. Current version: %ld, Target version: %ld", (long)currentVersion, (long)CLTAP_DATABASE_VERSION); - } + [self databaseVersionWithCompletion:^(NSInteger currentVersion) { + if (currentVersion < CLTAP_DATABASE_VERSION) { + // Handle version changes here in future. + [self setDatabaseVersion:CLTAP_DATABASE_VERSION]; + CleverTapLogStaticInternal(@"Schema migration required. Current version: %ld, Target version: %ld", (long)currentVersion, (long)CLTAP_DATABASE_VERSION); + } + }]; } - (NSString *)databasePath { diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 84c3bdcb..9d7a4ea0 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -29,108 +29,111 @@ - (void)setUp { CTClockMock *mockClock = [[CTClockMock alloc] initWithCurrentDate:[NSDate date]]; self.mockClock = mockClock; - self.eventDatabase = [[CTEventDatabase alloc] initWithClock:mockClock]; + CleverTapInstanceConfig *config = [[CleverTapInstanceConfig alloc] initWithAccountId:@"testAccount" accountToken:@"testToken" accountRegion:@"testRegion"]; + CTDispatchQueueManager *queueManager = [[CTDispatchQueueManager alloc] initWithConfig:config]; + self.eventDatabase = [[CTEventDatabase alloc] initWithDispatchQueueManager:queueManager clock:mockClock]; self.normalizedEventName = [CTUtils getNormalizedName:kEventName]; } - (void)tearDown { [super tearDown]; - [self.eventDatabase deleteAllRows]; + XCTestExpectation *deleteExpectation = [self expectationWithDescription:@"Delete all rows"]; + [self.eventDatabase deleteAllRowsWithCompletion:^(BOOL success) { + [deleteExpectation fulfill]; + }]; + [self waitForExpectations:@[deleteExpectation] timeout:2.0]; } - (void)testGetDatabaseVersion { - NSInteger dbVersion = [self.eventDatabase databaseVersion]; - XCTAssertEqual(dbVersion, 1); + XCTestExpectation *expectation = [self expectationWithDescription:@"Database version"]; + [self.eventDatabase databaseVersionWithCompletion:^(NSInteger currentVersion) { + XCTAssertEqual(currentVersion, 1); + [expectation fulfill]; + }]; + [self waitForExpectations:@[expectation] timeout:2.0]; } - (void)testInsertEventName { - BOOL insertSuccess = [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - XCTAssertTrue(insertSuccess); -} - -- (void)testInsertEventNameAgain { - BOOL insertSuccess = [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - XCTAssertTrue(insertSuccess); - - // Insert same eventName and deviceID again - BOOL insertSuccessAgain = [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - XCTAssertFalse(insertSuccessAgain); + XCTestExpectation *expectation = [self expectationWithDescription:@"Insert event"]; + [self.eventDatabase insertEvent:kEventName + normalizedEventName:self.normalizedEventName + deviceID:kDeviceID + completion:^(BOOL success) { + XCTAssertTrue(success); + [expectation fulfill]; + }]; + [self waitForExpectations:@[expectation] timeout:2.0]; } - (void)testEventNameExists { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - BOOL eventExists = [self.eventDatabase eventExists:self.normalizedEventName forDeviceID:kDeviceID]; - XCTAssertTrue(eventExists); + XCTestExpectation *expectation = [self expectationWithDescription:@"Event exists"]; + [self.eventDatabase eventExists:self.normalizedEventName forDeviceID:kDeviceID completion:^(BOOL exists) { + XCTAssertTrue(exists); + [expectation fulfill]; + }]; + XCTestExpectation *expectation1 = [self expectationWithDescription:@"Event exists 1"]; NSString *normalizedEventName = [CTUtils getNormalizedName:@"TesT EveNT"]; - eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; - XCTAssertTrue(eventExists); + [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID completion:^(BOOL exists) { + XCTAssertTrue(exists); + [expectation1 fulfill]; + }]; + XCTestExpectation *expectation2 = [self expectationWithDescription:@"Event exists 2"]; normalizedEventName = [CTUtils getNormalizedName:@"TEST EVENT"]; - eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; - XCTAssertTrue(eventExists); + [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID completion:^(BOOL exists) { + XCTAssertTrue(exists); + [expectation2 fulfill]; + }]; + [self waitForExpectations:@[expectation, expectation1, expectation2] timeout:2.0]; } - (void)testEventNameNotExists { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; + XCTestExpectation *expectation = [self expectationWithDescription:@"Event exists"]; NSString *normalizedEventName = [CTUtils getNormalizedName:@"TesT EveNT 1"]; - BOOL eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; - XCTAssertFalse(eventExists); + [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID completion:^(BOOL exists) { + XCTAssertFalse(exists); + [expectation fulfill]; + }]; + XCTestExpectation *expectation1 = [self expectationWithDescription:@"Event exists 1"]; normalizedEventName = [CTUtils getNormalizedName:@"Test.Event"]; - eventExists = [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID]; - XCTAssertFalse(eventExists); + [self.eventDatabase eventExists:normalizedEventName forDeviceID:kDeviceID completion:^(BOOL exists) { + XCTAssertFalse(exists); + [expectation1 fulfill]; + }]; + [self waitForExpectations:@[expectation, expectation1] timeout:2.0]; } - (void)testUpdateEventSuccess { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - BOOL updateSuccess = [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; - XCTAssertTrue(updateSuccess); + XCTestExpectation *expectation = [self expectationWithDescription:@"Update event"]; + [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID completion:^(BOOL success) { + XCTAssertTrue(success); + [expectation fulfill]; + }]; + [self waitForExpectations:@[expectation] timeout:2.0]; } -- (void)testUpsertEventSuccessWhenInsert { - BOOL upsertSuccess = [self.eventDatabase upsertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - XCTAssertTrue(upsertSuccess); -} - -- (void)testUpsertEventSuccessWhenUpdate { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - - BOOL upsertSuccess = [self.eventDatabase upsertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - XCTAssertTrue(upsertSuccess); - +- (void)testUpsertEventWhenUpdate { + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; + XCTAssertEqual(eventCount, 1); + + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; + eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCount, 2); } - (void)testGetCountForEventName { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; - - [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; // count should be 2. NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; @@ -146,9 +149,7 @@ - (void)testGetCountForEventNameNotExists { - (void)testFirstTimestampForEventName { self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger firstTs = [self.eventDatabase getFirstTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(firstTs, currentTs); @@ -157,9 +158,7 @@ - (void)testFirstTimestampForEventName { - (void)testLastTimestampForEventName { self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(lastTs, currentTs); @@ -168,41 +167,38 @@ - (void)testLastTimestampForEventName { - (void)testLastTimestampForEventNameUpdated { self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(lastTs, currentTs); self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; NSInteger newCurrentTs = [self.mockClock.currentDate timeIntervalSince1970]; - [self.eventDatabase updateEvent:self.normalizedEventName forDeviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger newLastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(newLastTs, newCurrentTs); } - (void)testDeleteAllRowsSuccess { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; NSInteger eventCount = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCount, 1); // Delete table. - BOOL deleteSuccess = [self.eventDatabase deleteAllRows]; - XCTAssertTrue(deleteSuccess); + XCTestExpectation *expectation = [self expectationWithDescription:@"Delete all rows"]; + [self.eventDatabase deleteAllRowsWithCompletion:^(BOOL success) { + XCTAssertTrue(success); + [expectation fulfill]; + }]; NSInteger eventCountAfterDelete = [self.eventDatabase getEventCount:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqual(eventCountAfterDelete, 0); - + [self waitForExpectations:@[expectation] timeout:2.0]; } - (void)testEventDetailsForDeviceID { NSInteger currentTs = (NSInteger)[[NSDate date] timeIntervalSince1970]; - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; CleverTapEventDetail *eventDetail = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; XCTAssertEqualObjects(eventDetail.eventName, kEventName); @@ -214,21 +210,16 @@ - (void)testEventDetailsForDeviceID { } - (void)testAllEventsForDeviceID { - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; // Insert to same device id kDeviceID NSString *eventName = @"Test Event 1"; NSString *normalizedName = [CTUtils getNormalizedName:eventName]; - [self.eventDatabase insertEvent:eventName - normalizedEventName:normalizedName - deviceID:kDeviceID]; + + [self.eventDatabase upsertEvent:eventName normalizedEventName:normalizedName deviceID:kDeviceID]; // Insert to different device id - [self.eventDatabase insertEvent:kEventName - normalizedEventName:self.normalizedEventName - deviceID:@"Test Device 1"]; + [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:@"Test Device 1"]; NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqualObjects(allEvents[0].eventName, kEventName); @@ -247,19 +238,21 @@ - (void)testLeastRecentlyUsedRowsDeleted { for (int i = 0; i < totalRowCount; i++) { NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; NSString *normalizedName = [CTUtils getNormalizedName:eventName]; - [self.eventDatabase insertEvent:eventName - normalizedEventName:normalizedName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:eventName normalizedEventName:normalizedName deviceID:kDeviceID]; } NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqual(allEvents.count, totalRowCount); // When deleteLeastRecentlyUsedRows is called using max row limit and numberOfRowsToCleanup // the deleted row count will be `totalRowCount - (maxRow - numberOfRowsToCleanup)` - [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup]; - allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; - int deletedRowCount = totalRowCount - (maxRow - numberOfRowsToCleanup); - XCTAssertEqual(allEvents.count, totalRowCount - deletedRowCount); + XCTestExpectation *expectation = [self expectationWithDescription:@"Delete rows"]; + [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup completion:^(BOOL success) { + NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + int deletedRowCount = totalRowCount - (maxRow - numberOfRowsToCleanup); + XCTAssertEqual(allEvents.count, totalRowCount - deletedRowCount); + [expectation fulfill]; + }]; + [self waitForExpectations:@[expectation] timeout:2.0]; } - (void)testLeastRecentlyUsedRowsNotDeleted { @@ -269,17 +262,19 @@ - (void)testLeastRecentlyUsedRowsNotDeleted { for (int i = 0; i < totalRowCount; i++) { NSString *eventName = [NSString stringWithFormat:@"Test Event %d", i]; NSString *normalizedName = [CTUtils getNormalizedName:eventName]; - [self.eventDatabase insertEvent:eventName - normalizedEventName:normalizedName - deviceID:kDeviceID]; + [self.eventDatabase upsertEvent:eventName normalizedEventName:normalizedName deviceID:kDeviceID]; } NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; XCTAssertEqual(allEvents.count, totalRowCount); // Here any row will not be deleted as it is within limit. - [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup]; - allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; - XCTAssertEqual(allEvents.count, totalRowCount); + XCTestExpectation *expectation = [self expectationWithDescription:@"Delete rows"]; + [self.eventDatabase deleteLeastRecentlyUsedRows:maxRow numberOfRowsToCleanup:numberOfRowsToCleanup completion:^(BOOL success) { + NSArray* allEvents = [self.eventDatabase getAllEventsForDeviceID:kDeviceID]; + XCTAssertEqual(allEvents.count, totalRowCount); + [expectation fulfill]; + }]; + [self waitForExpectations:@[expectation] timeout:2.0]; } - (NSString *)databasePath { From 27df035d820a3e42be0f33c52328fc4cca6a6b49 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 9 Jan 2025 10:22:23 +0530 Subject: [PATCH 33/41] - Addressed review comments. - Added proper return for error and records not found. - Removed get first and last time public API as per TAN. - Minor code improvements. --- CleverTapSDK/CTLocalDataStore.m | 15 +--- CleverTapSDK/CleverTap.h | 28 +------ CleverTapSDK/CleverTap.m | 17 +--- CleverTapSDK/EventDatabase/CTEventDatabase.m | 78 +------------------ .../EventDatabase/CTEventDatabaseTests.m | 12 ++- 5 files changed, 19 insertions(+), 131 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index 6191da66..c13830c8 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -629,16 +629,6 @@ - (CleverTapEventDetail *)readUserEventLog:(NSString *)eventName { return [self.dbHelper getEventDetail:normalizedEventName deviceID:self.deviceInfo.deviceId]; } -- (NSTimeInterval)readUserEventLogFirstTs:(NSString *)eventName { - NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; - return (int) [self.dbHelper getFirstTimestamp:normalizedEventName deviceID:self.deviceInfo.deviceId]; -} - -- (NSTimeInterval)readUserEventLogLastTs:(NSString *)eventName { - NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; - return (int) [self.dbHelper getLastTimestamp:normalizedEventName deviceID:self.deviceInfo.deviceId]; -} - - (NSDictionary *)readUserEventLogs { NSArray *allEvents = [self.dbHelper getAllEventsForDeviceID:self.deviceInfo.deviceId]; NSMutableDictionary *history = [[NSMutableDictionary alloc] init]; @@ -686,7 +676,10 @@ - (void)_setProfileValue:(id)value forKey:(NSString *)key fromUpstream:(BOOL)fro NSArray *systemProfileKeys = @[CLTAP_SYS_CARRIER, CLTAP_SYS_CC, CLTAP_SYS_TZ]; if (![systemProfileKeys containsObject:key]) { NSDictionary *profileEvent = @{CLTAP_EVENT_NAME: key}; - [self persistEvent:profileEvent]; + [self.dispatchQueueManager runSerialAsync:^{ + [self persistEvent:profileEvent]; + }]; + } } @catch (NSException *exception) { diff --git a/CleverTapSDK/CleverTap.h b/CleverTapSDK/CleverTap.h index 084cdeff..d2c60f9b 100644 --- a/CleverTapSDK/CleverTap.h +++ b/CleverTapSDK/CleverTap.h @@ -801,7 +801,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (NSTimeInterval)eventGetFirstTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogFirstTs instead"))); +- (NSTimeInterval)eventGetFirstTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLog instead"))); /*! @method @@ -813,7 +813,7 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; @param event event name */ -- (NSTimeInterval)eventGetLastTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLogLastTs instead"))); +- (NSTimeInterval)eventGetLastTime:(NSString *_Nonnull)event __attribute__((deprecated("Deprecated as of version 7.1.0, use getUserEventLog instead"))); /*! @method @@ -882,30 +882,6 @@ extern NSString * _Nonnull const CleverTapGeofencesDidUpdateNotification; */ - (CleverTapEventDetail *_Nullable)getUserEventLog:(NSString *_Nullable)eventName; -/*! - @method - - @abstract - Get the time of the first recording of the event. - This operation involves a database query and should be called from a background thread. - Be sure to call enablePersonalization prior to invoking this method. - - @param eventName event name - */ -- (NSTimeInterval)getUserEventLogFirstTs:(NSString *_Nonnull)eventName; - -/*! - @method - - @abstract - Get the time of the last recording of the event. - This operation involves a database query and should be called from a background thread. - Be sure to call enablePersonalization prior to invoking this method. - - @param eventName event name - */ -- (NSTimeInterval)getUserEventLogLastTs:(NSString *_Nonnull)eventName; - /*! @method diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index dfeff431..0bce25f7 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -474,7 +474,8 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig*)config andCleverTapID:( _localDataStore = [[CTLocalDataStore alloc] initWithConfig:_config profileValues:initialProfileValues andDeviceInfo: _deviceInfo dispatchQueueManager:_dispatchQueueManager]; _lastAppLaunchedTime = [self eventGetLastTime:@"App Launched"]; - _userLastVisitTs = [self getUserEventLogLastTs:@"App Launched"]; + CleverTapEventDetail *eventDetails = [self getUserEventLog:@"App Launched"]; + _userLastVisitTs = eventDetails ? eventDetails.lastTime : -1; self.validationResultStack = [[CTValidationResultStack alloc]initWithConfig: _config]; self.userSetLocation = kCLLocationCoordinate2DInvalid; @@ -3125,20 +3126,6 @@ - (CleverTapEventDetail *)getUserEventLog:(NSString *)eventName { return [self.localDataStore readUserEventLog:eventName]; } -- (NSTimeInterval)getUserEventLogFirstTs:(NSString *)eventName { - if (!self.config.enablePersonalization) { - return -1; - } - return [self.localDataStore readUserEventLogFirstTs:eventName]; -} - -- (NSTimeInterval)getUserEventLogLastTs:(NSString *)eventName { - if (!self.config.enablePersonalization) { - return -1; - } - return [self.localDataStore readUserEventLogLastTs:eventName]; -} - - (NSDictionary *)getUserEventLogHistory { if (!self.config.enablePersonalization) { return nil; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 6ffc7077..2d901a9c 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -157,8 +157,6 @@ - (void)updateEvent:(NSString *)normalizedEventName - (void)upsertEvent:(NSString *)eventName normalizedEventName:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - [self.dispatchQueueManager runSerialAsync:^{ [self eventExists:normalizedEventName forDeviceID:deviceID completion:^(BOOL exists) { if (!exists) { @@ -166,11 +164,8 @@ - (void)upsertEvent:(NSString *)eventName } else { [self updateEvent:normalizedEventName forDeviceID:deviceID completion:nil]; } - dispatch_semaphore_signal(semaphore); }]; }]; - - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } - (void)eventExists:(NSString *)normalizedEventName @@ -221,11 +216,11 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return 0; + return -1; } const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; - __block NSInteger count = 0; + __block NSInteger count = -1; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.dispatchQueueManager runSerialAsync:^{ @@ -237,6 +232,7 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName if (sqlite3_step(statement) == SQLITE_ROW) { count = sqlite3_column_int(statement, 0); } else { + count = 0; CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); } @@ -251,74 +247,6 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName return count; } -- (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID { - if (!_eventDatabase) { - CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return 0; - } - - const char *querySQL = "SELECT firstTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; - __block NSInteger firstTs = 0; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); - - if (sqlite3_step(statement) == SQLITE_ROW) { - firstTs = sqlite3_column_int(statement, 0); - } else { - CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); - } - - sqlite3_finalize(statement); - } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); - } - dispatch_semaphore_signal(semaphore); - }]; - - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - return firstTs; -} - -- (NSInteger)getLastTimestamp:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID { - if (!_eventDatabase) { - CleverTapLogStaticInternal(@"Event database is not open, cannot execute SQL."); - return 0; - } - - const char *querySQL = "SELECT lastTs FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; - __block NSInteger lastTs = 0; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ - sqlite3_stmt *statement; - if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { - sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); - sqlite3_bind_text(statement, 2, [deviceID UTF8String], -1, SQLITE_TRANSIENT); - - if (sqlite3_step(statement) == SQLITE_ROW) { - lastTs = sqlite3_column_int(statement, 0); - } else { - CleverTapLogStaticInternal(@"No event found with eventName: %@ and deviceID: %@", normalizedEventName, deviceID); - } - - sqlite3_finalize(statement); - } else { - CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); - } - dispatch_semaphore_signal(semaphore); - }]; - - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); - return lastTs; -} - - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName deviceID:(NSString *)deviceID { if (!_eventDatabase) { diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 9d7a4ea0..98ab136c 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -151,7 +151,8 @@ - (void)testFirstTimestampForEventName { NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - NSInteger firstTs = [self.eventDatabase getFirstTimestamp:self.normalizedEventName deviceID:kDeviceID]; + CleverTapEventDetail *event = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; + NSInteger firstTs = event.firstTime; XCTAssertEqual(firstTs, currentTs); } @@ -160,7 +161,8 @@ - (void)testLastTimestampForEventName { NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; + CleverTapEventDetail *event = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; + NSInteger lastTs = event.lastTime; XCTAssertEqual(lastTs, currentTs); } @@ -169,13 +171,15 @@ - (void)testLastTimestampForEventNameUpdated { NSInteger currentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - NSInteger lastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; + CleverTapEventDetail *event = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; + NSInteger lastTs = event.lastTime; XCTAssertEqual(lastTs, currentTs); self.mockClock.currentDate = [self.mockClock.currentDate dateByAddingTimeInterval:5 * 60]; NSInteger newCurrentTs = [self.mockClock.currentDate timeIntervalSince1970]; [self.eventDatabase upsertEvent:kEventName normalizedEventName:self.normalizedEventName deviceID:kDeviceID]; - NSInteger newLastTs = [self.eventDatabase getLastTimestamp:self.normalizedEventName deviceID:kDeviceID]; + CleverTapEventDetail *newEvent = [self.eventDatabase getEventDetail:self.normalizedEventName deviceID:kDeviceID]; + NSInteger newLastTs = newEvent.lastTime; XCTAssertEqual(newLastTs, newCurrentTs); } From 06fbb183e4ada4152dfcc30f6df3b5a3a2983e88 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Thu, 9 Jan 2025 13:08:27 +0530 Subject: [PATCH 34/41] Made init method private and used in tests. --- CleverTapSDK.xcodeproj/project.pbxproj | 2 ++ CleverTapSDK/EventDatabase/CTEventDatabase.h | 2 -- .../EventDatabase/CTEventDatabase+Tests.h | 22 +++++++++++++++++++ .../EventDatabase/CTEventDatabaseTests.m | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 CleverTapSDKTests/EventDatabase/CTEventDatabase+Tests.h diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index eb53b9a5..7561df5c 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -791,6 +791,7 @@ 48F9FD182C3D30B600617770 /* CTFileDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTFileDownloadManager.m; sourceTree = ""; }; 48F9FD192C3D30B600617770 /* CTFileDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTFileDownloader.m; sourceTree = ""; }; 48F9FD1A2C3D30B600617770 /* CTFileDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTFileDownloader.h; sourceTree = ""; }; + 48FC50F02D2FB0E100E57914 /* CTEventDatabase+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTEventDatabase+Tests.h"; sourceTree = ""; }; 4987C663251B5E79003E6BE8 /* CTImageInAppViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTImageInAppViewController.h; sourceTree = ""; }; 4987C664251B5E79003E6BE8 /* CTImageInAppViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTImageInAppViewController.m; sourceTree = ""; }; 4987C667251B5F9E003E6BE8 /* CTImageInAppViewControllerPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTImageInAppViewControllerPrivate.h; sourceTree = ""; }; @@ -1336,6 +1337,7 @@ isa = PBXGroup; children = ( 4847D1712CDA17C2008DC327 /* CTEventDatabaseTests.m */, + 48FC50F02D2FB0E100E57914 /* CTEventDatabase+Tests.h */, ); path = EventDatabase; sourceTree = ""; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index f0aab133..124dffb5 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -15,8 +15,6 @@ @interface CTEventDatabase : NSObject + (instancetype)sharedInstanceWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager; -- (instancetype)initWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager - clock:(id)clock; - (void)databaseVersionWithCompletion:(void (^)(NSInteger version))completion; diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabase+Tests.h b/CleverTapSDKTests/EventDatabase/CTEventDatabase+Tests.h new file mode 100644 index 00000000..50d365fa --- /dev/null +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabase+Tests.h @@ -0,0 +1,22 @@ +// +// CTEventDatabase+Tests.h +// CleverTapSDKTests +// +// Created by Nishant Kumar on 09/01/25. +// Copyright © 2025 CleverTap. All rights reserved. +// + +#ifndef CTEventDatabase_Tests_h +#define CTEventDatabase_Tests_h + +#import "CTEventDatabase.h" + +@interface CTEventDatabase(Tests) + +- (instancetype)initWithDispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager + clock:(id)clock; + +@end + + +#endif /* CTEventDatabase_Tests_h */ diff --git a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m index 98ab136c..020ad2de 100644 --- a/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m +++ b/CleverTapSDKTests/EventDatabase/CTEventDatabaseTests.m @@ -7,7 +7,7 @@ // #import -#import "CTEventDatabase.h" +#import "CTEventDatabase+Tests.h" #import "CTUtils.h" #import "CTClockMock.h" From 26b6d4f6dabbf5f3883c0a438a9bb14938db9176 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 09:02:24 +0530 Subject: [PATCH 35/41] - Addressed lint and formatting review comments. --- CleverTapSDK/CTLocalDataStore.h | 20 +++++++++---------- CleverTapSDK/CTLocalDataStore.m | 9 +++++---- CleverTapSDK/CleverTap.m | 8 ++++---- .../InApps/CTInAppEvaluationManager.m | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/CleverTapSDK/CTLocalDataStore.h b/CleverTapSDK/CTLocalDataStore.h index 334acc95..69c904ef 100644 --- a/CleverTapSDK/CTLocalDataStore.h +++ b/CleverTapSDK/CTLocalDataStore.h @@ -7,8 +7,10 @@ @interface CTLocalDataStore : NSObject - -- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(NSDictionary*)profileValues andDeviceInfo:(CTDeviceInfo*)deviceInfo dispatchQueueManager:(CTDispatchQueueManager*)dispatchQueueManager; +- (instancetype)initWithConfig:(CleverTapInstanceConfig *)config + profileValues:(NSDictionary *)profileValues + andDeviceInfo:(CTDeviceInfo *)deviceInfo + dispatchQueueManager:(CTDispatchQueueManager *)dispatchQueueManager; - (void)persistEvent:(NSDictionary *)event; @@ -16,15 +18,15 @@ - (NSDictionary*)syncWithRemoteData:(NSDictionary *)responseData; -- (NSTimeInterval)getFirstTimeForEvent:(NSString *)event; +- (NSTimeInterval)getFirstTimeForEvent:(NSString *)event __attribute__((deprecated("Deprecated as of version 7.1.0, use readUserEventLog instead"))); -- (NSTimeInterval)getLastTimeForEvent:(NSString *)event; +- (NSTimeInterval)getLastTimeForEvent:(NSString *)event __attribute__((deprecated("Deprecated as of version 7.1.0, use readUserEventLog instead"))); -- (int)getOccurrencesForEvent:(NSString *)event; +- (int)getOccurrencesForEvent:(NSString *)event __attribute__((deprecated("Deprecated as of version 7.1.0, use readUserEventLogCount instead"))); -- (NSDictionary *)getEventHistory; +- (NSDictionary *)getEventHistory __attribute__((deprecated("Deprecated as of version 7.1.0, use readUserEventLogs instead"))); -- (CleverTapEventDetail *)getEventDetail:(NSString *)event; +- (CleverTapEventDetail *)getEventDetail:(NSString *)event __attribute__((deprecated("Deprecated as of version 7.1.0, use readUserEventLog instead"))); - (void)setProfileFields:(NSDictionary *)fields; @@ -50,10 +52,6 @@ - (CleverTapEventDetail *)readUserEventLog:(NSString *)eventName; -- (NSTimeInterval)readUserEventLogFirstTs:(NSString *)eventName; - -- (NSTimeInterval)readUserEventLogLastTs:(NSString *)eventName; - - (NSDictionary *)readUserEventLogs; @end diff --git a/CleverTapSDK/CTLocalDataStore.m b/CleverTapSDK/CTLocalDataStore.m index c13830c8..21c530b3 100644 --- a/CleverTapSDK/CTLocalDataStore.m +++ b/CleverTapSDK/CTLocalDataStore.m @@ -37,6 +37,7 @@ @interface CTLocalDataStore() { @property (nonatomic, strong) CTDispatchQueueManager *dispatchQueueManager; @property (nonatomic, strong) NSMutableSet *userEventLogs; @property (nonatomic, strong) CTEventDatabase *dbHelper; +@property (nonatomic, strong) NSArray *systemProfileKeys; @end @@ -54,6 +55,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:( dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL); lastProfilePersistenceTime = 0; _piiKeys = CLTAP_ENCRYPTION_PII_DATA; + _systemProfileKeys = @[CLTAP_SYS_CARRIER, CLTAP_SYS_CC, CLTAP_SYS_TZ]; [self runOnBackgroundQueue:^{ @synchronized (self->localProfileForSession) { // migrate to new persisted ct-accid-guid-userprofile @@ -193,7 +195,8 @@ - (void)persistEvent:(NSDictionary *)event { if (!event || !event[CLTAP_EVENT_NAME]) return; NSString *eventName = event[CLTAP_EVENT_NAME]; - [self.dbHelper upsertEvent:eventName normalizedEventName:[CTUtils getNormalizedName:eventName] deviceID:self.deviceInfo.deviceId]; + NSString *normalizedEventName = [CTUtils getNormalizedName:eventName]; + [self.dbHelper upsertEvent:eventName normalizedEventName:normalizedEventName deviceID:self.deviceInfo.deviceId]; } /*! @@ -673,13 +676,11 @@ - (void)_setProfileValue:(id)value forKey:(NSString *)key fromUpstream:(BOOL)fro } // PERSIST PROFILE KEY AS EVENT - NSArray *systemProfileKeys = @[CLTAP_SYS_CARRIER, CLTAP_SYS_CC, CLTAP_SYS_TZ]; - if (![systemProfileKeys containsObject:key]) { + if (![_systemProfileKeys containsObject:key]) { NSDictionary *profileEvent = @{CLTAP_EVENT_NAME: key}; [self.dispatchQueueManager runSerialAsync:^{ [self persistEvent:profileEvent]; }]; - } } @catch (NSException *exception) { diff --git a/CleverTapSDK/CleverTap.m b/CleverTapSDK/CleverTap.m index 0bce25f7..30e0cd21 100644 --- a/CleverTapSDK/CleverTap.m +++ b/CleverTapSDK/CleverTap.m @@ -473,8 +473,8 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig*)config andCleverTapID:( _localDataStore = [[CTLocalDataStore alloc] initWithConfig:_config profileValues:initialProfileValues andDeviceInfo: _deviceInfo dispatchQueueManager:_dispatchQueueManager]; - _lastAppLaunchedTime = [self eventGetLastTime:@"App Launched"]; - CleverTapEventDetail *eventDetails = [self getUserEventLog:@"App Launched"]; + _lastAppLaunchedTime = [self eventGetLastTime:CLTAP_APP_LAUNCHED_EVENT]; + CleverTapEventDetail *eventDetails = [self getUserEventLog:CLTAP_APP_LAUNCHED_EVENT]; _userLastVisitTs = eventDetails ? eventDetails.lastTime : -1; self.validationResultStack = [[CTValidationResultStack alloc]initWithConfig: _config]; self.userSetLocation = kCLLocationCoordinate2DInvalid; @@ -3134,7 +3134,7 @@ - (NSDictionary *)getUserEventLogHistory { } - (int)getUserAppLaunchCount { - return [self getUserEventLogCount:@"App Launched"]; + return [self getUserEventLogCount:CLTAP_APP_LAUNCHED_EVENT]; } - (NSTimeInterval)getUserLastVisitTs { @@ -3157,7 +3157,7 @@ - (CleverTapUTMDetail *)sessionGetUTMDetails { } - (int)userGetTotalVisits { - return [self eventGetOccurrences:@"App Launched"]; + return [self eventGetOccurrences:CLTAP_APP_LAUNCHED_EVENT]; } - (int)userGetScreenCount { diff --git a/CleverTapSDK/InApps/CTInAppEvaluationManager.m b/CleverTapSDK/InApps/CTInAppEvaluationManager.m index 7f4427db..c19ef7ba 100644 --- a/CleverTapSDK/InApps/CTInAppEvaluationManager.m +++ b/CleverTapSDK/InApps/CTInAppEvaluationManager.m @@ -85,7 +85,7 @@ - (instancetype)initWithAccountId:(NSString *)accountId } self.inAppStore = inAppStore; - self.triggersMatcher = [[CTTriggersMatcher alloc]initWithDataStore:dataStore]; + self.triggersMatcher = [[CTTriggersMatcher alloc] initWithDataStore:dataStore]; self.limitsMatcher = [CTLimitsMatcher new]; self.triggerManager = inAppTriggerManager; From abc473178dbb0e36bf6c7e52bf1ae3babdc8c7d1 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 14:47:24 +0530 Subject: [PATCH 36/41] - Fixes potential deadlock scenario by adding semaphore only when not running in dispatch queue manager serial queue. --- CleverTapSDK/CTDispatchQueueManager.h | 1 + CleverTapSDK/EventDatabase/CTEventDatabase.h | 6 -- CleverTapSDK/EventDatabase/CTEventDatabase.m | 94 +++++++++++++++----- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/CleverTapSDK/CTDispatchQueueManager.h b/CleverTapSDK/CTDispatchQueueManager.h index 7ba0f81f..5466ff05 100644 --- a/CleverTapSDK/CTDispatchQueueManager.h +++ b/CleverTapSDK/CTDispatchQueueManager.h @@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype _Nonnull)initWithConfig:(CleverTapInstanceConfig*)config; - (void)runSerialAsync:(void (^)(void))taskBlock; - (void)runOnNotificationQueue:(void (^)(void))taskBlock; +- (BOOL)inSerialQueue; @end diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.h b/CleverTapSDK/EventDatabase/CTEventDatabase.h index 124dffb5..c45812c9 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.h +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.h @@ -38,12 +38,6 @@ normalizedEventName:(NSString *)normalizedEventName - (NSInteger)getEventCount:(NSString *)normalizedEventName deviceID:(NSString *)deviceID; -- (NSInteger)getFirstTimestamp:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID; - -- (NSInteger)getLastTimestamp:(NSString *)normalizedEventName - deviceID:(NSString *)deviceID; - - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName deviceID:(NSString *)deviceID; diff --git a/CleverTapSDK/EventDatabase/CTEventDatabase.m b/CleverTapSDK/EventDatabase/CTEventDatabase.m index 2d901a9c..0c6a19de 100644 --- a/CleverTapSDK/EventDatabase/CTEventDatabase.m +++ b/CleverTapSDK/EventDatabase/CTEventDatabase.m @@ -221,9 +221,7 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName const char *querySQL = "SELECT count FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block NSInteger count = -1; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ + void (^taskBlock)(void) = ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); @@ -240,10 +238,24 @@ - (NSInteger)getEventCount:(NSString *)normalizedEventName } else { CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - dispatch_semaphore_signal(semaphore); - }]; + }; + + if ([self.dispatchQueueManager inSerialQueue]) { + // If already on the serial queue, execute directly without semaphore + taskBlock(); + } else { + // Otherwise, use semaphore for synchronous execution + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [self.dispatchQueueManager runSerialAsync:^{ + taskBlock(); + dispatch_semaphore_signal(semaphore); + }]; + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3)) != 0) { + CleverTapLogStaticInternal(@"Timeout occurred while getting event count."); + return -1; + } + } - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return count; } @@ -256,9 +268,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE normalizedEventName = ? AND deviceID = ?"; __block CleverTapEventDetail *eventDetail = nil; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ + void (^taskBlock)(void) = ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [normalizedEventName UTF8String], -1, SQLITE_TRANSIENT); @@ -287,10 +297,24 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName } else { CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - dispatch_semaphore_signal(semaphore); - }]; + }; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if ([self.dispatchQueueManager inSerialQueue]) { + // If already on the serial queue, execute directly without semaphore + taskBlock(); + } else { + // Otherwise, use semaphore for synchronous execution + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [self.dispatchQueueManager runSerialAsync:^{ + taskBlock(); + dispatch_semaphore_signal(semaphore); + }]; + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3)) != 0) { + CleverTapLogStaticInternal(@"Timeout occurred while getting event detail."); + return nil; + } + } + return eventDetail; } @@ -302,9 +326,7 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName const char *querySQL = "SELECT eventName, normalizedEventName, count, firstTs, lastTs, deviceID FROM CTUserEventLogs WHERE deviceID = ?"; __block NSMutableArray *eventDataArray = [NSMutableArray array]; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ + void (^taskBlock)(void) = ^{ sqlite3_stmt *statement; if (sqlite3_prepare_v2(self->_eventDatabase, querySQL, -1, &statement, NULL) == SQLITE_OK) { sqlite3_bind_text(statement, 1, [deviceID UTF8String], -1, SQLITE_TRANSIENT); @@ -332,10 +354,23 @@ - (CleverTapEventDetail *)getEventDetail:(NSString *)normalizedEventName } else { CleverTapLogStaticInternal(@"SQL prepare query error: %s", sqlite3_errmsg(self->_eventDatabase)); } - dispatch_semaphore_signal(semaphore); - }]; + }; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + if ([self.dispatchQueueManager inSerialQueue]) { + // If already on the serial queue, execute directly without semaphore + taskBlock(); + } else { + // Otherwise, use semaphore for synchronous execution + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [self.dispatchQueueManager runSerialAsync:^{ + taskBlock(); + dispatch_semaphore_signal(semaphore); + }]; + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3)) != 0) { + CleverTapLogStaticInternal(@"Timeout occurred while getting all event details."); + return nil; + } + } return [eventDataArray copy]; } @@ -443,9 +478,7 @@ - (void)deleteLeastRecentlyUsedRows:(NSInteger)maxRowLimit - (void)openDatabase { NSString *databasePath = [self databasePath]; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - [self.dispatchQueueManager runSerialAsync:^{ + void (^taskBlock)(void) = ^{ if (sqlite3_open_v2([databasePath UTF8String], &self->_eventDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL) == SQLITE_OK) { // Create table, check and update the version if needed [self createTableWithCompletion:^(BOOL exists) { @@ -454,9 +487,22 @@ - (void)openDatabase { } else { CleverTapLogStaticInternal(@"Failed to open database - CleverTap-Events.db"); } - dispatch_semaphore_signal(semaphore); - }]; - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + }; + + if ([self.dispatchQueueManager inSerialQueue]) { + // If already on the serial queue, execute directly without semaphore + taskBlock(); + } else { + // Otherwise, use semaphore for synchronous execution + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [self.dispatchQueueManager runSerialAsync:^{ + taskBlock(); + dispatch_semaphore_signal(semaphore); + }]; + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3)) != 0) { + CleverTapLogStaticInternal(@"Timeout occurred while opening database."); + } + } } - (void)createTableWithCompletion:(void (^)(BOOL success))completion { From bd55b09c1efe0afda03f49029c8fef90ab3f9c67 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 14:52:01 +0530 Subject: [PATCH 37/41] Updated version to 7.1.0 --- CleverTapSDK/CleverTapBuildInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CleverTapSDK/CleverTapBuildInfo.h b/CleverTapSDK/CleverTapBuildInfo.h index 211764c8..8cf0002a 100644 --- a/CleverTapSDK/CleverTapBuildInfo.h +++ b/CleverTapSDK/CleverTapBuildInfo.h @@ -1 +1 @@ -#define WR_SDK_REVISION @"70003" +#define WR_SDK_REVISION @"71000" From 4f2696a58c7cec7223cbee418c596322b3557f07 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 14:55:57 +0530 Subject: [PATCH 38/41] Updated version in txt file --- sdk-version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-version.txt b/sdk-version.txt index 5febf260..3769235d 100644 --- a/sdk-version.txt +++ b/sdk-version.txt @@ -1 +1 @@ -7.0.3 \ No newline at end of file +7.1.0 \ No newline at end of file From a9a74f40103a5b963495f75ffc4121db3d8454e5 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 15:09:57 +0530 Subject: [PATCH 39/41] Added changelog --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d39dfcae..b4135647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ # Change Log All notable changes to this project will be documented in this file. +### [Version 7.1.0](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.1.0) (January 21, 2024) + +#### Added +- Adds support for triggering InApps based on first-time event filtering in multiple triggers. Now you can create campaign triggers that combine recurring and first-time events. For example: Trigger a campaign when "Charged" occurs (every time) OR "App Launched" occurs (first time only). +- Adds new user-level event log tracking system to store and manage user event history. New APIs include: + - `getUserEventLog(:)`: Get details about a specific event + - `getUserEventLogCount(:)`: Get count of times an event occurred + - `getUserLastVisitTs()`: Get timestamp of user's last app visit + - `getUserAppLaunchCount()`: Get total number of times user has launched the app + - `getUserEventLogHistory()`: Get full event history for current user + +#### API Changes + +- **Deprecated:** The old event tracking APIs tracked events at the device level rather than the user level, making it difficult to maintain accurate user-specific event histories, especially in multi-user scenarios. The following methods have been deprecated in favor of new user-specific event tracking APIs that provide more accurate, user-level analytics. These deprecated methods will be removed in future versions with prior notice: + - `eventGetDetail(:)`: Use `getUserEventLog()` instead for user-specific event details + - `eventGetOccurrences(:)`: Use `getUserEventLogCount()` instead for user-specific event counts + - `eventGetFirstTime(:)`: Use `getUserEventLog()` instead for user-specific first occurrence timestamp + - `eventGetLastTime(:)`: Use `getUserEventLog()` instead for user-specific last occurrence timestamp + - `userGetPreviousVisitTime()`: Use `getUserLastVisitTs()` instead for user-specific last visit timestamp + - `userGetTotalVisits()`: Use `getUserAppLaunchCount()` instead for user-specific app launch count + - `userGetEventHistory()`: Use `getUserEventLogHistory()` instead for user-specific event history + ### [Version 7.0.3](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.0.3) (November 29, 2024) #### Added From f7f710c9af894cc7f5037582e1898cce4b4e62dc Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 19:41:43 +0530 Subject: [PATCH 40/41] Added CTEventDatabase into tvos source file and fixed tvos build issue. --- CleverTap-iOS-SDK.podspec | 2 +- CleverTapSDK.xcodeproj/project.pbxproj | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CleverTap-iOS-SDK.podspec b/CleverTap-iOS-SDK.podspec index 448fcbc2..c385c8f2 100644 --- a/CleverTap-iOS-SDK.podspec +++ b/CleverTap-iOS-SDK.podspec @@ -17,7 +17,7 @@ s.ios.source_files = 'CleverTapSDK/**/*.{h,m}' s.ios.exclude_files = 'CleverTapSDK/include/**/*.h' s.ios.public_header_files = 'CleverTapSDK/CleverTap.h', 'CleverTapSDK/CleverTap+SSLPinning.h','CleverTapSDK/CleverTap+Inbox.h', 'CleverTapSDK/CleverTapInstanceConfig.h', 'CleverTapSDK/CleverTapBuildInfo.h', 'CleverTapSDK/CleverTapEventDetail.h', 'CleverTapSDK/CleverTapInAppNotificationDelegate.h', 'CleverTapSDK/CleverTapSyncDelegate.h', 'CleverTapSDK/CleverTapTrackedViewController.h', 'CleverTapSDK/CleverTapUTMDetail.h', 'CleverTapSDK/CleverTapJSInterface.h', 'CleverTapSDK/CleverTap+DisplayUnit.h', 'CleverTapSDK/CleverTap+FeatureFlags.h', 'CleverTapSDK/CleverTap+ProductConfig.h', 'CleverTapSDK/CleverTapPushNotificationDelegate.h', 'CleverTapSDK/CleverTapURLDelegate.h', 'CleverTapSDK/CleverTap+InAppNotifications.h', 'CleverTapSDK/CleverTap+SCDomain.h', 'CleverTapSDK/CleverTap+PushPermission.h', 'CleverTapSDK/InApps/CTLocalInApp.h', 'CleverTapSDK/CleverTap+CTVar.h', 'CleverTapSDK/ProductExperiences/CTVar.h', 'CleverTapSDK/LeanplumCT.h', 'CleverTapSDK/InApps/CustomTemplates/CTInAppTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTAppFunctionBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplatePresenter.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateProducer.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplateBuilder.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplate.h', 'CleverTapSDK/InApps/CustomTemplates/CTTemplateContext.h', 'CleverTapSDK/InApps/CustomTemplates/CTCustomTemplatesManager.h', 'CleverTapSDK/InApps/CustomTemplates/CTJsonTemplateProducer.h' s.tvos.deployment_target = '9.0' -s.tvos.source_files = 'CleverTapSDK/*.{h,m}', 'CleverTapSDK/FileDownload/*.{h,m}', 'CleverTapSDK/ProductConfig/**/*.{h,m}', 'CleverTapSDK/FeatureFlags/**/*.{h,m}', 'CleverTapSDK/ProductExperiences/*.{h,m}', 'CleverTapSDK/Swizzling/*.{h,m}', 'CleverTapSDK/Session/*.{h,m}' +s.tvos.source_files = 'CleverTapSDK/*.{h,m}', 'CleverTapSDK/FileDownload/*.{h,m}', 'CleverTapSDK/ProductConfig/**/*.{h,m}', 'CleverTapSDK/FeatureFlags/**/*.{h,m}', 'CleverTapSDK/ProductExperiences/*.{h,m}', 'CleverTapSDK/Swizzling/*.{h,m}', 'CleverTapSDK/Session/*.{h,m}', 'CleverTapSDK/EventDatabase/*.{h,m}' s.tvos.exclude_files = 'CleverTapSDK/include/**/*.h', 'CleverTapSDK/CleverTapJSInterface.{h,m}', 'CleverTapSDK/CTInAppNotification.{h,m}', 'CleverTapSDK/CTNotificationButton.{h,m}', 'CleverTapSDK/CTNotificationAction.{h,m}', 'CleverTapSDK/CTPushPrimerManager.{h,m}', 'CleverTapSDK/InApps/*.{h,m}', 'CleverTapSDK/InApps/**/*.{h,m}', 'CleverTapSDK/CTInAppFCManager.{h,m}', 'CleverTapSDK/CTInAppDisplayViewController.{h,m}' s.tvos.public_header_files = 'CleverTapSDK/CleverTap.h', 'CleverTapSDK/CleverTap+SSLPinning.h', 'CleverTapSDK/CleverTapInstanceConfig.h', 'CleverTapSDK/CleverTapBuildInfo.h', 'CleverTapSDK/CleverTapEventDetail.h', 'CleverTapSDK/CleverTapSyncDelegate.h', 'CleverTapSDK/CleverTapTrackedViewController.h', 'CleverTapSDK/CleverTapUTMDetail.h', 'CleverTapSDK/CleverTap+FeatureFlags.h', 'CleverTapSDK/CleverTap+ProductConfig.h', 'CleverTapSDK/CleverTap+CTVar.h', 'CleverTapSDK/ProductExperiences/CTVar.h' end diff --git a/CleverTapSDK.xcodeproj/project.pbxproj b/CleverTapSDK.xcodeproj/project.pbxproj index 7561df5c..1df10405 100644 --- a/CleverTapSDK.xcodeproj/project.pbxproj +++ b/CleverTapSDK.xcodeproj/project.pbxproj @@ -173,6 +173,12 @@ 48A2C4B92BD67DDC006C61BC /* sampleTXTStub.txt in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B72BD67DDB006C61BC /* sampleTXTStub.txt */; }; 48A2C4BA2BD67DDC006C61BC /* samplePDFStub.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 48A2C4B82BD67DDB006C61BC /* samplePDFStub.pdf */; }; 48C0FD6F2BCD522100E01EA9 /* CTFileDownloadManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 48C0FD6E2BCD522100E01EA9 /* CTFileDownloadManagerTests.m */; }; + 48DC69B42D3FDF1F000E7BE4 /* CTEventDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 4847D16C2CCB90E0008DC327 /* CTEventDatabase.h */; }; + 48DC69B52D3FDF23000E7BE4 /* CTEventDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 4847D16E2CCB90F5008DC327 /* CTEventDatabase.m */; }; + 48DC69B62D3FDF35000E7BE4 /* CTNotificationAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BB778C92BED21CE00A41628 /* CTNotificationAction.h */; }; + 48DC69B72D3FDF39000E7BE4 /* CTNotificationAction.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778CA2BED21CE00A41628 /* CTNotificationAction.m */; }; + 48DC69B82D3FDF4D000E7BE4 /* CTCustomTemplateInAppData.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BB778C52BECEC2700A41628 /* CTCustomTemplateInAppData.h */; }; + 48DC69B92D3FDF55000E7BE4 /* CTCustomTemplateInAppData.m in Sources */ = {isa = PBXBuildFile; fileRef = 6BB778C62BECEC2700A41628 /* CTCustomTemplateInAppData.m */; }; 48F9FD092C208F7100617770 /* CTInAppStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 6A4427D72ABCE5EB0098866F /* CTInAppStore.m */; }; 48F9FD1D2C3D30BF00617770 /* CTFileDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 48F9FD182C3D30B600617770 /* CTFileDownloadManager.m */; }; 48F9FD1E2C3D30BF00617770 /* CTFileDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 48F9FD192C3D30B600617770 /* CTFileDownloader.m */; }; @@ -1952,6 +1958,7 @@ 4E25E3D22788889F0008C888 /* CTLoginInfoProvider.h in Headers */, D0BD75AF241769E40006EE55 /* CleverTap+ProductConfig.h in Headers */, D014B8F020E2FAB1001E0780 /* CTPlistInfo.h in Headers */, + 48DC69B42D3FDF1F000E7BE4 /* CTEventDatabase.h in Headers */, 6B535FB72AD56C60002A2663 /* CTMultiDelegateManager.h in Headers */, 4E8B81702AD2BB8A00714BB4 /* CTDispatchQueueManager.h in Headers */, D014B8EC20E2FA9D001E0780 /* CleverTapTrackedViewController.h in Headers */, @@ -1983,6 +1990,7 @@ D014B8E120E2F9E9001E0780 /* CleverTap+SSLPinning.h in Headers */, D014B91C20E2FBD1001E0780 /* CTCertificatePinning.h in Headers */, 4E4E17842B500079009E2F1E /* CTAES.h in Headers */, + 48DC69B62D3FDF35000E7BE4 /* CTNotificationAction.h in Headers */, D014B90420E2FB59001E0780 /* CTValidator.h in Headers */, 4E8B81692AD2ADAE00714BB4 /* CTSwizzleManager.h in Headers */, 4E6383D8296DE9A8001E83E3 /* CTRequestSender.h in Headers */, @@ -1991,6 +1999,7 @@ D014B8F420E2FACA001E0780 /* CTSwizzle.h in Headers */, 4EF0D5462AD84BCA0044C48F /* CTSessionManager.h in Headers */, 6A775C3429BE78C7007790E0 /* CTVariables.h in Headers */, + 48DC69B82D3FDF4D000E7BE4 /* CTCustomTemplateInAppData.h in Headers */, D014B8E620E2FA64001E0780 /* CleverTapInstanceConfig.h in Headers */, 6BEEC2C72AE9B02100BD4EC5 /* CTSystemClock.h in Headers */, 4E41FD99294F46510001FBED /* CTVarCache.h in Headers */, @@ -2501,6 +2510,7 @@ 4E6383DA296DE9A8001E83E3 /* CTRequestSender.m in Sources */, D014B8ED20E2FAA2001E0780 /* CleverTapTrackedViewController.m in Sources */, 4E5A02DF2A4C5FD800DE242A /* LeanplumCT.m in Sources */, + 48DC69B92D3FDF55000E7BE4 /* CTCustomTemplateInAppData.m in Sources */, 6BD334ED2AF2A41F0099E33E /* CTBatchSentDelegateHelper.m in Sources */, D014B8F120E2FAB9001E0780 /* CTPlistInfo.m in Sources */, 4E25E3D12788889F0008C888 /* CTLoginInfoProvider.m in Sources */, @@ -2543,6 +2553,8 @@ D014B8FF20E2FB40001E0780 /* CTLogger.m in Sources */, 6B535FB92AD56C60002A2663 /* CTMultiDelegateManager.m in Sources */, 6BD334F62AF7FC660099E33E /* CTTriggerRadius.m in Sources */, + 48DC69B52D3FDF23000E7BE4 /* CTEventDatabase.m in Sources */, + 48DC69B72D3FDF39000E7BE4 /* CTNotificationAction.m in Sources */, D014B8FD20E2FB18001E0780 /* CTPreferences.m in Sources */, 4E49AE56275D24570074A774 /* CTValidationResultStack.m in Sources */, 6BAFFEAA2C45244100654CAF /* CTFileDownloader.m in Sources */, From 46d72f3392fb28da721e828edc0d5ffe4dad5894 Mon Sep 17 00:00:00 2001 From: Nishant Kumar Date: Tue, 21 Jan 2025 20:42:15 +0530 Subject: [PATCH 41/41] Added EventDatabase files to support SPM --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index c8d9b055..67bf684c 100644 --- a/Package.swift +++ b/Package.swift @@ -58,7 +58,8 @@ let package = Package( .headerSearchPath("ProductExperiences/"), .headerSearchPath("Session/"), .headerSearchPath("Swizzling/"), - .headerSearchPath("FileDownload/") + .headerSearchPath("FileDownload/"), + .headerSearchPath("EventDatabase/") ], linkerSettings: [ .linkedFramework("AVFoundation"),