2012-09-12 13:58:54 +00:00
//
// LocalyticsDatabase . m
2013-04-27 20:24:29 +00:00
// Copyright ( C ) 2013 Char Software Inc . , DBA Localytics
//
2012-09-12 13:58:54 +00:00
// This code is provided under the Localytics Modified BSD License .
// A copy of this license has been distributed in a file called LICENSE
2013-04-27 20:24:29 +00:00
// with this source code .
//
2012-09-12 13:58:54 +00:00
// Please visit www . localytics . com for more information .
# import "LocalyticsDatabase.h"
2013-04-27 20:24:29 +00:00
# import "LocalyticsSession.h"
2012-09-12 13:58:54 +00:00
# define LOCALYTICS_DIR @ ".localytics" // Name for the directory in which Localytics database is stored
# define LOCALYTICS_DB @ "localytics" // File name for the database ( without extension )
# define BUSY_TIMEOUT 30 // Maximum time SQlite will busy - wait for the database to unlock before returning SQLITE_BUSY
@ interface LocalyticsDatabase ( )
2013-04-27 20:24:29 +00:00
- ( int ) schemaVersion ;
- ( void ) createSchema ;
- ( void ) upgradeToSchemaV2 ;
- ( void ) upgradeToSchemaV3 ;
- ( void ) upgradeToSchemaV4 ;
- ( void ) upgradeToSchemaV5 ;
- ( void ) upgradeToSchemaV6 ;
2012-09-12 13:58:54 +00:00
- ( void ) upgradeToSchemaV7 ;
2013-04-27 20:24:29 +00:00
- ( void ) upgradeToSchemaV8 ;
- ( void ) upgradeToSchemaV9 ;
- ( void ) upgradeToSchemaV10 ;
- ( void ) upgradeToSchemaV11 ;
- ( void ) upgradeToSchemaV12 ;
2013-08-11 04:08:25 +00:00
- ( void ) upgradeToSchemaV13 ;
2013-04-27 20:24:29 +00:00
- ( void ) moveDbToCaches ;
- ( NSString * ) randomUUID ;
2012-09-12 13:58:54 +00:00
@ end
@ implementation LocalyticsDatabase
+ ( NSString * ) localyticsDirectoryPath {
2013-04-27 20:24:29 +00:00
NSArray * paths = NSSearchPathForDirectoriesInDomains ( NSCachesDirectory , NSUserDomainMask , YES ) ;
return [ [ paths objectAtIndex : 0 ] stringByAppendingPathComponent : LOCALYTICS_DIR ] ;
2012-09-12 13:58:54 +00:00
}
+ ( NSString * ) localyticsDatabasePath {
2013-04-27 20:24:29 +00:00
NSString * path = [ [ LocalyticsDatabase localyticsDirectoryPath ] stringByAppendingPathComponent : [ NSString stringWithFormat : @ "%@.sqlite" , LOCALYTICS_DB ] ] ;
return path ;
2012-09-12 13:58:54 +00:00
}
- ( LocalyticsDatabase * ) init {
if ( ( self = [ super init ] ) ) {
2013-04-27 20:24:29 +00:00
// Mover any data that a previous library may have left in the documents directory
[ self moveDbToCaches ] ;
// Create directory structure for Localytics .
NSString * directoryPath = [ LocalyticsDatabase localyticsDirectoryPath ] ;
if ( ! [ [ NSFileManager defaultManager ] fileExistsAtPath : directoryPath ] ) {
[ [ NSFileManager defaultManager ] createDirectoryAtPath : directoryPath withIntermediateDirectories : YES attributes : nil error : nil ] ;
}
// Attempt to open database . It will be created if it does not exist , already .
NSString * dbPath = [ LocalyticsDatabase localyticsDatabasePath ] ;
int code = sqlite3_open ( [ dbPath UTF8String ] , & _databaseConnection ) ;
// If we were unable to open the database , it is likely corrupted . Clobber it and move on .
if ( code ! = SQLITE_OK ) {
[ [ NSFileManager defaultManager ] removeItemAtPath : dbPath error : nil ] ;
code = sqlite3_open ( [ dbPath UTF8String ] , & _databaseConnection ) ;
}
// Enable foreign key constraints .
if ( code = = SQLITE_OK ) {
const char * sql = [ @ "PRAGMA foreign_keys = ON;" cStringUsingEncoding : NSUTF8StringEncoding ] ;
code = sqlite3_exec ( _databaseConnection , sql , NULL , NULL , NULL ) ;
}
// Check db connection , creating schema if necessary .
2013-08-11 04:08:25 +00:00
_firstRun = NO ;
2013-04-27 20:24:29 +00:00
if ( code = = SQLITE_OK ) {
sqlite3_busy _timeout ( _databaseConnection , BUSY_TIMEOUT ) ; // Defaults to 0 , otherwise .
if ( [ self schemaVersion ] = = 0 ) {
[ self createSchema ] ;
2013-08-11 04:08:25 +00:00
_firstRun = YES ;
2013-04-27 20:24:29 +00:00
}
}
// Perform any Migrations if necessary
if ( [ self schemaVersion ] < 2 ) {
[ self upgradeToSchemaV2 ] ;
}
if ( [ self schemaVersion ] < 3 ) {
[ self upgradeToSchemaV3 ] ;
}
if ( [ self schemaVersion ] < 4 ) {
[ self upgradeToSchemaV4 ] ;
}
if ( [ self schemaVersion ] < 5 ) {
[ self upgradeToSchemaV5 ] ;
}
if ( [ self schemaVersion ] < 6 ) {
[ self upgradeToSchemaV6 ] ;
}
if ( [ self schemaVersion ] < 7 ) {
[ self upgradeToSchemaV7 ] ;
}
if ( [ self schemaVersion ] < 8 ) {
[ self upgradeToSchemaV8 ] ;
}
if ( [ self schemaVersion ] < 9 ) {
[ self upgradeToSchemaV9 ] ;
}
if ( [ self schemaVersion ] < 10 ) {
[ self upgradeToSchemaV10 ] ;
}
if ( [ self schemaVersion ] < 11 ) {
[ self upgradeToSchemaV11 ] ;
}
if ( [ self schemaVersion ] < 12 ) {
[ self upgradeToSchemaV12 ] ;
}
2013-08-11 04:08:25 +00:00
if ( [ self schemaVersion ] < 13 ) {
[ self upgradeToSchemaV13 ] ;
}
2013-04-27 20:24:29 +00:00
// Perfrorm first run actions
2013-08-11 04:08:25 +00:00
if ( _firstRun )
2013-04-27 20:24:29 +00:00
{
[ self collectFacebookAttributionIfAvailable ] ;
}
}
2012-09-12 13:58:54 +00:00
return self ;
}
2013-04-27 20:24:29 +00:00
# pragma mark - Database
2012-09-12 13:58:54 +00:00
- ( BOOL ) beginTransaction : ( NSString * ) name {
2013-04-27 20:24:29 +00:00
const char * sql = [ [ NSString stringWithFormat : @ "SAVEPOINT %@" , name ] cStringUsingEncoding : NSUTF8StringEncoding ] ;
int code = sqlite3_exec ( _databaseConnection , sql , NULL , NULL , NULL ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) releaseTransaction : ( NSString * ) name {
2013-04-27 20:24:29 +00:00
const char * sql = [ [ NSString stringWithFormat : @ "RELEASE SAVEPOINT %@" , name ] cStringUsingEncoding : NSUTF8StringEncoding ] ;
int code = sqlite3_exec ( _databaseConnection , sql , NULL , NULL , NULL ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) rollbackTransaction : ( NSString * ) name {
2013-04-27 20:24:29 +00:00
const char * sql = [ [ NSString stringWithFormat : @ "ROLLBACK SAVEPOINT %@" , name ] cStringUsingEncoding : NSUTF8StringEncoding ] ;
int code = sqlite3_exec ( _databaseConnection , sql , NULL , NULL , NULL ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( int ) schemaVersion {
2013-04-27 20:24:29 +00:00
int version = 0 ;
const char * sql = "SELECT MAX(schema_version) FROM localytics_info" ;
sqlite3_stmt * selectSchemaVersion ;
if ( sqlite3_prepare _v2 ( _databaseConnection , sql , -1 , & selectSchemaVersion , NULL ) = = SQLITE_OK ) {
if ( sqlite3_step ( selectSchemaVersion ) = = SQLITE_ROW ) {
version = sqlite3_column _int ( selectSchemaVersion , 0 ) ;
}
}
sqlite3_finalize ( selectSchemaVersion ) ;
return version ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) installId {
2013-04-27 20:24:29 +00:00
NSString * installId = nil ;
sqlite3_stmt * selectInstallId ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT install_id FROM localytics_info" , -1 , & selectInstallId , NULL ) ;
int code = sqlite3_step ( selectInstallId ) ;
if ( code = = SQLITE_ROW && sqlite3_column _text ( selectInstallId , 0 ) ) {
installId = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectInstallId , 0 ) ] ;
}
sqlite3_finalize ( selectInstallId ) ;
return installId ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) appKey {
2013-04-27 20:24:29 +00:00
NSString * appKey = nil ;
sqlite3_stmt * selectAppKey ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT app_key FROM localytics_info" , -1 , & selectAppKey , NULL ) ;
int code = sqlite3_step ( selectAppKey ) ;
if ( code = = SQLITE_ROW && sqlite3_column _text ( selectAppKey , 0 ) ) {
appKey = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectAppKey , 0 ) ] ;
}
sqlite3_finalize ( selectAppKey ) ;
return appKey ;
2012-09-12 13:58:54 +00:00
}
// Due to the new iOS storage guidelines it is necessary to move the database out of the documents directory
2013-04-27 20:24:29 +00:00
// and into the / library / caches directory
2012-09-12 13:58:54 +00:00
- ( void ) moveDbToCaches {
2013-04-27 20:24:29 +00:00
NSArray * documentPaths = NSSearchPathForDirectoriesInDomains ( NSDocumentDirectory , NSUserDomainMask , YES ) ;
NSString * localyticsDocumentsDirectory = [ [ documentPaths objectAtIndex : 0 ] stringByAppendingPathComponent : LOCALYTICS_DIR ] ;
NSArray * cachesPaths = NSSearchPathForDirectoriesInDomains ( NSCachesDirectory , NSUserDomainMask , YES ) ;
NSString * localyticsCachesDirectory = [ [ cachesPaths objectAtIndex : 0 ] stringByAppendingPathComponent : LOCALYTICS_DIR ] ;
// If the old directory doesn ' t exist , there is nothing else to do here
if ( [ [ NSFileManager defaultManager ] fileExistsAtPath : localyticsDocumentsDirectory ] = = NO )
{
return ;
}
// Try to move the directory
if ( NO = = [ [ NSFileManager defaultManager ] moveItemAtPath : localyticsDocumentsDirectory
toPath : localyticsCachesDirectory
error : nil ] )
{
// If the move failed try and , delete the old directory
[ [ NSFileManager defaultManager ] removeItemAtPath : localyticsDocumentsDirectory error : nil ] ;
}
2012-09-12 13:58:54 +00:00
}
- ( void ) createSchema {
2013-04-27 20:24:29 +00:00
int code = SQLITE_OK ;
// Execute schema creation within a single transaction .
code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE upload_headers ("
"sequence_number INTEGER PRIMARY KEY, "
"blob_string TEXT)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE events ("
"event_id INTEGER PRIMARY KEY AUTOINCREMENT, " // In case foreign key constraints are reintroduced .
"upload_header INTEGER, "
"blob_string TEXT NOT NULL)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE localytics_info ("
"schema_version INTEGER PRIMARY KEY, "
"last_upload_number INTEGER, "
"last_session_number INTEGER, "
"opt_out BOOLEAN, "
"last_close_event INTEGER, "
"last_flow_event INTEGER, "
"last_session_start REAL, "
"app_key CHAR(64), "
"custom_d0 CHAR(64), "
"custom_d1 CHAR(64), "
"custom_d2 CHAR(64), "
"custom_d3 CHAR(64) "
")" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"INSERT INTO localytics_info (schema_version, last_upload_number, last_session_number, opt_out) "
"VALUES (3, 0, 0, 0)" , NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
}
// V2 adds a unique identifier for each installation
// This identifier has been moved to user preferences so the database an live in the caches directory
// Also adds storage for custom dimensions
- ( void ) upgradeToSchemaV2 {
2013-04-27 20:24:29 +00:00
int code = SQLITE_OK ;
code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD install_id CHAR(40)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d0 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d1 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d2 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d3 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
// Attempt to set schema version and install_id regardless of the result code following the ALTER statements above .
// This is necessary because a previous version of the library performed the migration without setting these values .
// The transaction will succeed even if the individual statements fail with errors ( eg . "duplicate column name" ) .
sqlite3_stmt * updateLocalyticsInfo ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info set install_id = ?, schema_version = 2 " , -1 , & updateLocalyticsInfo , NULL ) ;
sqlite3_bind _text ( updateLocalyticsInfo , 1 , [ [ self randomUUID ] UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
code = sqlite3_step ( updateLocalyticsInfo ) ;
sqlite3_finalize ( updateLocalyticsInfo ) ;
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
}
// V3 adds a field for the last app key and patches a V2 migration issue .
- ( void ) upgradeToSchemaV3 {
2013-04-27 20:24:29 +00:00
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD app_key CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 3" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
}
// V4 adds a field for the customer id .
- ( void ) upgradeToSchemaV4 {
2013-04-27 20:24:29 +00:00
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD customer_id CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 4" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
}
// V5 adds AMP related tables .
- ( void ) upgradeToSchemaV5 {
2013-04-27 20:24:29 +00:00
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
// The AMP DB table was initially created here . in Version 7 it will be dropped and re - added with the correct data types .
2012-09-12 13:58:54 +00:00
// therefore the code that creates it is no longer going to be called here .
2013-04-27 20:24:29 +00:00
2012-09-12 13:58:54 +00:00
// we still want to change the schema version
2013-04-27 20:24:29 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 5" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
}
// V6 adds a field for the queued close event blob string .
- ( void ) upgradeToSchemaV6 {
2013-04-27 20:24:29 +00:00
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD queued_close_event_blob TEXT" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 6" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
- ( void ) upgradeToSchemaV7 {
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
2012-09-12 13:58:54 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DROP TABLE IF EXISTS localytics_amp_rule" , NULL , NULL , NULL ) ;
}
2013-04-27 20:24:29 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE IF NOT EXISTS localytics_amp_rule ("
"rule_id INTEGER PRIMARY KEY AUTOINCREMENT, "
"rule_name TEXT UNIQUE, "
"expiration INTEGER, "
"phone_location TEXT, "
"phone_size_width INTEGER, "
"phone_size_height INTEGER, "
"tablet_location TEXT, "
"tablet_size_width INTEGER, "
"tablet_size_height INTEGER, "
"display_seconds INTEGER, "
"display_session INTEGER, "
"version INTEGER, "
"did_display INTEGER, "
"times_to_display INTEGER, "
"internet_required INTEGER, "
"ab_test TEXT"
")" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE IF NOT EXISTS localytics_amp_ruleevent ("
"rule_id INTEGER, "
"event_name TEXT, "
"FOREIGN KEY(rule_id) REFERENCES localytics_amp_rule(rule_id) ON DELETE CASCADE "
")" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 7" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
- ( void ) upgradeToSchemaV8 {
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD campaign_id INTEGER" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD ttl_expiration INTEGER" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD update_on_ttl_expiration INTEGER" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 8" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
- ( void ) upgradeToSchemaV9 {
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD location TEXT" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 9" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
2012-09-12 13:58:54 +00:00
2013-04-27 20:24:29 +00:00
- ( void ) upgradeToSchemaV10 {
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD conversion_expiration INTEGER" ,
NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
2013-04-27 20:24:29 +00:00
"CREATE TABLE IF NOT EXISTS localytics_identifiers ("
"key TEXT PRIMARY KEY, "
"value TEXT"
")" ,
NULL , NULL , NULL ) ;
2012-09-12 13:58:54 +00:00
}
2013-04-27 20:24:29 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 10" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
2012-09-12 13:58:54 +00:00
2013-04-27 20:24:29 +00:00
- ( void ) upgradeToSchemaV11 {
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD fb_attribution TEXT" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_amp_rule ADD devices TEXT" ,
NULL , NULL , NULL ) ;
}
2012-09-12 13:58:54 +00:00
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
2013-04-27 20:24:29 +00:00
"CREATE TABLE IF NOT EXISTS localytics_amp_conditions ("
"condition_id INTEGER PRIMARY KEY AUTOINCREMENT, "
"rule_id INTEGER, "
"attribute_name TEXT, "
"operator TEXT, "
"FOREIGN KEY(rule_id) REFERENCES localytics_amp_rule(rule_id) ON DELETE CASCADE "
")" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"CREATE TABLE IF NOT EXISTS localytics_amp_conditions_values ("
"condition_id INTEGER, "
"value TEXT, "
"FOREIGN KEY(condition_id) REFERENCES localytics_amp_conditions(condition_id) ON DELETE CASCADE "
")" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 11" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
- ( void ) upgradeToSchemaV12 {
int code = SQLITE_OK ;
code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d4 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d5 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d6 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d7 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d8 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD custom_d9 CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 12" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
2013-08-11 04:08:25 +00:00
- ( void ) upgradeToSchemaV13
{
int code = sqlite3_exec ( _databaseConnection , "BEGIN" , NULL , NULL , NULL ) ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"ALTER TABLE localytics_info ADD app_version CHAR(64)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info set schema_version = 13" ,
NULL , NULL , NULL ) ;
}
// Commit transaction .
if ( code = = SQLITE_OK || code = = SQLITE_DONE ) {
sqlite3_exec ( _databaseConnection , "COMMIT" , NULL , NULL , NULL ) ;
} else {
sqlite3_exec ( _databaseConnection , "ROLLBACK" , NULL , NULL , NULL ) ;
}
}
2013-04-27 20:24:29 +00:00
- ( unsigned long long ) databaseSize {
unsigned long long size = 0 ;
NSDictionary * fileAttributes = [ [ NSFileManager defaultManager ]
attributesOfItemAtPath : [ LocalyticsDatabase localyticsDatabasePath ]
error : nil ] ;
size = [ fileAttributes fileSize ] ;
return size ;
}
- ( int ) eventCount {
int count = 0 ;
const char * sql = "SELECT count(*) FROM events" ;
sqlite3_stmt * selectEventCount ;
if ( sqlite3_prepare _v2 ( _databaseConnection , sql , -1 , & selectEventCount , NULL ) = = SQLITE_OK )
{
if ( sqlite3_step ( selectEventCount ) = = SQLITE_ROW ) {
count = sqlite3_column _int ( selectEventCount , 0 ) ;
}
}
sqlite3_finalize ( selectEventCount ) ;
return count ;
2012-09-12 13:58:54 +00:00
}
- ( NSTimeInterval ) createdTimestamp {
2013-04-27 20:24:29 +00:00
NSTimeInterval timestamp = 0 ;
NSDictionary * fileAttributes = [ [ NSFileManager defaultManager ]
attributesOfItemAtPath : [ LocalyticsDatabase localyticsDatabasePath ]
error : nil ] ;
timestamp = [ [ fileAttributes fileCreationDate ] timeIntervalSince1970 ] ;
return timestamp ;
2012-09-12 13:58:54 +00:00
}
- ( NSTimeInterval ) lastSessionStartTimestamp {
2013-04-27 20:24:29 +00:00
NSTimeInterval lastSessionStart = 0 ;
sqlite3_stmt * selectLastSessionStart ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT last_session_start FROM localytics_info" , -1 , & selectLastSessionStart , NULL ) ;
int code = sqlite3_step ( selectLastSessionStart ) ;
if ( code = = SQLITE_ROW ) {
lastSessionStart = sqlite3_column _double ( selectLastSessionStart , 0 ) ;
}
sqlite3_finalize ( selectLastSessionStart ) ;
return lastSessionStart ;
}
2012-09-12 13:58:54 +00:00
2013-04-27 20:24:29 +00:00
- ( BOOL ) setLastSessionStartTimestamp : ( NSTimeInterval ) timestamp {
sqlite3_stmt * updateLastSessionStart ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET last_session_start = ?" , -1 , & updateLastSessionStart , NULL ) ;
sqlite3_bind _double ( updateLastSessionStart , 1 , timestamp ) ;
int code = sqlite3_step ( updateLastSessionStart ) ;
sqlite3_finalize ( updateLastSessionStart ) ;
return code = = SQLITE_DONE ;
}
- ( BOOL ) isOptedOut {
BOOL optedOut = NO ;
sqlite3_stmt * selectOptOut ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT opt_out FROM localytics_info" , -1 , & selectOptOut , NULL ) ;
int code = sqlite3_step ( selectOptOut ) ;
if ( code = = SQLITE_ROW ) {
optedOut = sqlite3_column _int ( selectOptOut , 0 ) = = 1 ;
}
sqlite3_finalize ( selectOptOut ) ;
return optedOut ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) setOptedOut : ( BOOL ) optOut {
2013-04-27 20:24:29 +00:00
sqlite3_stmt * updateOptedOut ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET opt_out = ?" , -1 , & updateOptedOut , NULL ) ;
sqlite3_bind _int ( updateOptedOut , 1 , optOut ) ;
int code = sqlite3_step ( updateOptedOut ) ;
sqlite3_finalize ( updateOptedOut ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
2013-08-11 04:08:25 +00:00
- ( NSString * ) appVersion {
NSString * appVersion = nil ;
sqlite3_stmt * selectAppVersion ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT app_version FROM localytics_info" , -1 , & selectAppVersion , NULL ) ;
int code = sqlite3_step ( selectAppVersion ) ;
if ( code = = SQLITE_ROW ) {
char * chars = ( char * ) sqlite3_column _text ( selectAppVersion , 0 ) ;
if ( chars ) appVersion = [ NSString stringWithUTF8String : chars ] ;
}
sqlite3_finalize ( selectAppVersion ) ;
return appVersion ;
}
- ( BOOL ) updateAppVersion : ( NSString * ) appVersion {
sqlite3_stmt * updateAppVersion ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info set app_version = ?" , -1 , & updateAppVersion , NULL ) ;
sqlite3_bind _text ( updateAppVersion , 1 , [ appVersion UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( updateAppVersion ) ;
sqlite3_finalize ( updateAppVersion ) ;
return ( code = = SQLITE_DONE ) ;
}
2012-09-12 13:58:54 +00:00
- ( NSString * ) customDimension : ( int ) dimension {
2013-04-27 20:24:29 +00:00
if ( dimension < 0 || dimension > 9 ) {
return nil ;
}
NSString * value = nil ;
NSString * query = [ NSString stringWithFormat : @ "select custom_d%i from localytics_info" , dimension ] ;
sqlite3_stmt * selectCustomDim ;
sqlite3_prepare _v2 ( _databaseConnection , [ query UTF8String ] , -1 , & selectCustomDim , NULL ) ;
int code = sqlite3_step ( selectCustomDim ) ;
if ( code = = SQLITE_ROW && sqlite3_column _text ( selectCustomDim , 0 ) ) {
value = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectCustomDim , 0 ) ] ;
}
sqlite3_finalize ( selectCustomDim ) ;
return value ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) setCustomDimension : ( int ) dimension value : ( NSString * ) value {
2013-04-27 20:24:29 +00:00
if ( dimension < 0 || dimension > 9 ) {
return false ;
}
NSString * query = [ NSString stringWithFormat : @ "update localytics_info SET custom_d%i = %@" ,
dimension ,
( value = = nil ) ? @ "null" : [ NSString stringWithFormat : @ "\" % @ \ "" , value ] ] ;
int code = sqlite3_exec ( _databaseConnection , [ query UTF8String ] , NULL , NULL , NULL ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) incrementLastUploadNumber : ( int * ) uploadNumber {
2013-04-27 20:24:29 +00:00
NSString * t = @ "increment_upload_number" ;
int code = SQLITE_OK ;
code = [ self beginTransaction : t ] ? SQLITE_OK : SQLITE_ERROR ;
if ( code = = SQLITE_OK ) {
// Increment value
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info "
"SET last_upload_number = (last_upload_number + 1)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
// Retrieve new value
sqlite3_stmt * selectUploadNumber ;
sqlite3_prepare _v2 ( _databaseConnection ,
"SELECT last_upload_number FROM localytics_info" ,
-1 , & selectUploadNumber , NULL ) ;
code = sqlite3_step ( selectUploadNumber ) ;
if ( code = = SQLITE_ROW ) {
* uploadNumber = sqlite3_column _int ( selectUploadNumber , 0 ) ;
}
sqlite3_finalize ( selectUploadNumber ) ;
}
if ( code = = SQLITE_ROW ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return code = = SQLITE_ROW ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) incrementLastSessionNumber : ( int * ) sessionNumber {
2013-04-27 20:24:29 +00:00
NSString * t = @ "increment_session_number" ;
int code = [ self beginTransaction : t ] ? SQLITE_OK : SQLITE_ERROR ;
if ( code = = SQLITE_OK ) {
// Increment value
code = sqlite3_exec ( _databaseConnection ,
"UPDATE localytics_info "
"SET last_session_number = (last_session_number + 1)" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
// Retrieve new value
sqlite3_stmt * selectSessionNumber ;
sqlite3_prepare _v2 ( _databaseConnection ,
"SELECT last_session_number FROM localytics_info" ,
-1 , & selectSessionNumber , NULL ) ;
code = sqlite3_step ( selectSessionNumber ) ;
if ( code = = SQLITE_ROW && sessionNumber ! = NULL ) {
* sessionNumber = sqlite3_column _int ( selectSessionNumber , 0 ) ;
}
sqlite3_finalize ( selectSessionNumber ) ;
}
if ( code = = SQLITE_ROW ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return code = = SQLITE_ROW ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) addEventWithBlobString : ( NSString * ) blob {
2013-04-27 20:24:29 +00:00
int code = SQLITE_OK ;
sqlite3_stmt * insertEvent ;
sqlite3_prepare _v2 ( _databaseConnection , "INSERT INTO events (blob_string) VALUES (?)" , -1 , & insertEvent , NULL ) ;
sqlite3_bind _text ( insertEvent , 1 , [ blob UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
code = sqlite3_step ( insertEvent ) ;
sqlite3_finalize ( insertEvent ) ;
return code = = SQLITE_DONE ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) addCloseEventWithBlobString : ( NSString * ) blob {
2013-04-27 20:24:29 +00:00
NSString * t = @ "add_close_event" ;
BOOL success = [ self beginTransaction : t ] ;
// Add close event .
if ( success ) {
success = [ self addEventWithBlobString : blob ] ;
}
// Record row id to localytics_info so that it can be removed if the session resumes .
if ( success ) {
sqlite3_stmt * updateCloseEvent ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET last_close_event = (SELECT event_id FROM events WHERE rowid = ?)" , -1 , & updateCloseEvent , NULL ) ;
sqlite3_int64 lastRow = sqlite3_last _insert _rowid ( _databaseConnection ) ;
sqlite3_bind _int64 ( updateCloseEvent , 1 , lastRow ) ;
int code = sqlite3_step ( updateCloseEvent ) ;
sqlite3_finalize ( updateCloseEvent ) ;
success = code = = SQLITE_DONE ;
}
if ( success ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return success ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) queueCloseEventWithBlobString : ( NSString * ) blob {
2013-04-27 20:24:29 +00:00
NSString * t = @ "queue_close_event" ;
BOOL success = [ self beginTransaction : t ] ;
// Queue close event .
if ( success ) {
sqlite3_stmt * queueCloseEvent ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET queued_close_event_blob = ?" , -1 , & queueCloseEvent , NULL ) ;
sqlite3_bind _text ( queueCloseEvent , 1 , [ blob UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( queueCloseEvent ) ;
sqlite3_finalize ( queueCloseEvent ) ;
success = code = = SQLITE_DONE ;
}
if ( success ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return success ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) dequeueCloseEventBlobString {
2013-04-27 20:24:29 +00:00
NSString * value = nil ;
NSString * query = @ "SELECT queued_close_event_blob FROM localytics_info" ;
sqlite3_stmt * selectStmt ;
sqlite3_prepare _v2 ( _databaseConnection , [ query UTF8String ] , -1 , & selectStmt , NULL ) ;
int code = sqlite3_step ( selectStmt ) ;
if ( code = = SQLITE_ROW && sqlite3_column _text ( selectStmt , 0 ) ) {
value = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectStmt , 0 ) ] ;
}
sqlite3_finalize ( selectStmt ) ;
// Clear the queued close event blob .
[ self queueCloseEventWithBlobString : nil ] ;
return value ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) addFlowEventWithBlobString : ( NSString * ) blob {
2013-04-27 20:24:29 +00:00
NSString * t = @ "add_flow_event" ;
BOOL success = [ self beginTransaction : t ] ;
// Add flow event .
if ( success ) {
success = [ self addEventWithBlobString : blob ] ;
}
// Record row id to localytics_info so that it can be removed if the session resumes .
if ( success ) {
sqlite3_stmt * updateFlowEvent ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET last_flow_event = (SELECT event_id FROM events WHERE rowid = ?)" , -1 , & updateFlowEvent , NULL ) ;
sqlite3_int64 lastRow = sqlite3_last _insert _rowid ( _databaseConnection ) ;
sqlite3_bind _int64 ( updateFlowEvent , 1 , lastRow ) ;
int code = sqlite3_step ( updateFlowEvent ) ;
sqlite3_finalize ( updateFlowEvent ) ;
success = code = = SQLITE_DONE ;
}
if ( success ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return success ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) removeLastCloseAndFlowEvents {
2013-04-27 20:24:29 +00:00
// Attempt to remove the last recorded close event .
// Fail quietly if none was saved or it was previously removed .
int code = sqlite3_exec ( _databaseConnection , "DELETE FROM events WHERE event_id = (SELECT last_close_event FROM localytics_info) OR event_id = (SELECT last_flow_event FROM localytics_info)" , NULL , NULL , NULL ) ;
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) addHeaderWithSequenceNumber : ( int ) number blobString : ( NSString * ) blob rowId : ( sqlite3_int64 * ) insertedRowId {
2013-04-27 20:24:29 +00:00
sqlite3_stmt * insertHeader ;
sqlite3_prepare _v2 ( _databaseConnection , "INSERT INTO upload_headers (sequence_number, blob_string) VALUES (?, ?)" , -1 , & insertHeader , NULL ) ;
sqlite3_bind _int ( insertHeader , 1 , number ) ;
sqlite3_bind _text ( insertHeader , 2 , [ blob UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( insertHeader ) ;
sqlite3_finalize ( insertHeader ) ;
if ( code = = SQLITE_DONE && insertedRowId ! = NULL ) {
* insertedRowId = sqlite3_last _insert _rowid ( _databaseConnection ) ;
}
return code = = SQLITE_DONE ;
2012-09-12 13:58:54 +00:00
}
- ( int ) unstagedEventCount {
2013-04-27 20:24:29 +00:00
int rowCount = 0 ;
sqlite3_stmt * selectEventCount ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT COUNT(*) FROM events WHERE UPLOAD_HEADER IS NULL" , -1 , & selectEventCount , NULL ) ;
int code = sqlite3_step ( selectEventCount ) ;
if ( code = = SQLITE_ROW ) {
rowCount = sqlite3_column _int ( selectEventCount , 0 ) ;
}
sqlite3_finalize ( selectEventCount ) ;
return rowCount ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) stageEventsForUpload : ( sqlite3_int64 ) headerId {
2013-04-27 20:24:29 +00:00
// Associate all outstanding events with the given upload header ID .
NSString * stageEvents = [ NSString stringWithFormat : @ "UPDATE events SET upload_header = ? WHERE upload_header IS NULL" ] ;
sqlite3_stmt * updateEvents ;
sqlite3_prepare _v2 ( _databaseConnection , [ stageEvents UTF8String ] , -1 , & updateEvents , NULL ) ;
sqlite3_bind _int64 ( updateEvents , 1 , headerId ) ;
int code = sqlite3_step ( updateEvents ) ;
sqlite3_finalize ( updateEvents ) ;
BOOL success = ( code = = SQLITE_DONE ) ;
return success ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) updateAppKey : ( NSString * ) appKey {
2013-04-27 20:24:29 +00:00
sqlite3_stmt * updateAppKey ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info set app_key = ?" , -1 , & updateAppKey , NULL ) ;
sqlite3_bind _text ( updateAppKey , 1 , [ appKey UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( updateAppKey ) ;
sqlite3_finalize ( updateAppKey ) ;
BOOL success = ( code = = SQLITE_DONE ) ;
return success ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) uploadBlobString {
2013-04-27 20:24:29 +00:00
// Retrieve the blob strings of each upload header and its child events , in order .
const char * sql = "SELECT * FROM ( "
" SELECT h.blob_string AS 'blob', h.sequence_number as 'seq', 0 FROM upload_headers h"
" UNION ALL "
" SELECT e.blob_string AS 'blob', e.upload_header as 'seq', 1 FROM events e"
") "
"ORDER BY 2, 3" ;
sqlite3_stmt * selectBlobs ;
sqlite3_prepare _v2 ( _databaseConnection , sql , -1 , & selectBlobs , NULL ) ;
NSMutableString * uploadBlobString = [ NSMutableString string ] ;
while ( sqlite3_step ( selectBlobs ) = = SQLITE_ROW ) {
const char * blob = ( const char * ) sqlite3_column _text ( selectBlobs , 0 ) ;
if ( blob ! = NULL ) {
NSString * blobString = [ [ NSString alloc ] initWithCString : blob encoding : NSUTF8StringEncoding ] ;
[ uploadBlobString appendString : blobString ] ;
[ blobString release ] ;
}
}
sqlite3_finalize ( selectBlobs ) ;
return [ [ uploadBlobString copy ] autorelease ] ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) deleteUploadedData {
2013-04-27 20:24:29 +00:00
// Delete all headers and staged events .
NSString * t = @ "delete_upload_data" ;
int code = [ self beginTransaction : t ] ? SQLITE_OK : SQLITE_ERROR ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM events WHERE upload_header IS NOT NULL" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM upload_headers" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) resetAnalyticsData {
2013-04-27 20:24:29 +00:00
// Delete or zero all analytics data .
// Reset : headers , events , session number , upload number , last session start , last close event , and last flow event .
// Unaffected : schema version , opt out status , install ID ( deprecated ) , and app key .
NSString * t = @ "reset_analytics_data" ;
int code = [ self beginTransaction : t ] ? SQLITE_OK : SQLITE_ERROR ;
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM events" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM upload_headers" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM localytics_amp_rule" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM localytics_amp_ruleevent" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM localytics_amp_conditions" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM localytics_amp_conditions_values" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "DELETE FROM localytics_identifiers" , NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
code = sqlite3_exec ( _databaseConnection , "UPDATE localytics_info SET last_session_number = 0, last_upload_number = 0,"
"last_close_event = null, last_flow_event = null, last_session_start = null, "
"custom_d0 = null, custom_d1 = null, custom_d2 = null, custom_d3 = null, "
"queued_close_event_blob = null, fb_attribution = null" ,
NULL , NULL , NULL ) ;
}
if ( code = = SQLITE_OK ) {
[ self releaseTransaction : t ] ;
} else {
[ self rollbackTransaction : t ] ;
}
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( BOOL ) vacuumIfRequired {
2013-04-27 20:24:29 +00:00
int code = SQLITE_OK ;
if ( [ self databaseSize ] > MAX_DATABASE _SIZE * VACUUM_THRESHOLD ) {
code = sqlite3_exec ( _databaseConnection , "VACUUM" , NULL , NULL , NULL ) ;
}
return code = = SQLITE_OK ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) randomUUID {
CFUUIDRef theUUID = CFUUIDCreate ( NULL ) ;
CFStringRef stringUUID = CFUUIDCreateString ( NULL , theUUID ) ;
CFRelease ( theUUID ) ;
return [ ( NSString * ) stringUUID autorelease ] ;
}
2013-04-27 20:24:29 +00:00
- ( BOOL ) setValueForIdentifier : ( NSString * ) identifierName value : ( NSString * ) value
{
sqlite3_stmt * sqlRule ;
if ( [ self valueForIdentifier : identifierName ] ) {
// Update
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_identifiers SET value=? WHERE key=?" , -1 , & sqlRule , NULL ) ;
sqlite3_bind _text ( sqlRule , 1 , [ value UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
sqlite3_bind _text ( sqlRule , 2 , [ identifierName UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
} else {
// Insert
sqlite3_prepare _v2 ( _databaseConnection , "INSERT INTO localytics_identifiers (value, key) VALUES (?, ?)" , -1 , & sqlRule , NULL ) ;
sqlite3_bind _text ( sqlRule , 1 , [ value UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
sqlite3_bind _text ( sqlRule , 2 , [ identifierName UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
}
int code = sqlite3_step ( sqlRule ) ;
sqlite3_finalize ( sqlRule ) ;
return code = = SQLITE_DONE ;
}
- ( NSString * ) valueForIdentifier : ( NSString * ) identifierName
{
NSString * value = nil ;
sqlite3_stmt * selectRuleId ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT value FROM localytics_identifiers WHERE key = ?" , -1 , & selectRuleId , NULL ) ;
sqlite3_bind _text ( selectRuleId , 1 , [ identifierName UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( selectRuleId ) ;
if ( code = = SQLITE_ROW && sqlite3_column _text ( selectRuleId , 0 ) ) {
value = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectRuleId , 0 ) ] ;
}
sqlite3_finalize ( selectRuleId ) ;
return value ;
}
- ( BOOL ) deleteIdentifer : ( NSString * ) identifierName
{
sqlite3_stmt * deleteRule ;
sqlite3_prepare _v2 ( _databaseConnection , "DELETE FROM localytics_identifiers WHERE key = ?" , -1 , & deleteRule , NULL ) ;
sqlite3_bind _text ( deleteRule , 1 , [ identifierName UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( deleteRule ) ;
sqlite3_finalize ( deleteRule ) ;
return ( code = = SQLITE_DONE ) ;;
2012-09-12 13:58:54 +00:00
}
2013-04-27 20:24:29 +00:00
- ( NSDictionary * ) identifiers
2012-09-12 13:58:54 +00:00
{
2013-04-27 20:24:29 +00:00
NSMutableDictionary * identifiers = nil ;
sqlite3_stmt * selectRule ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT key, value FROM localytics_identifiers" , -1 , & selectRule , NULL ) ;
while ( sqlite3_step ( selectRule ) = = SQLITE_ROW )
{
NSString * key = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectRule , 0 ) ] ;
NSString * value = [ NSString stringWithUTF8String : ( char * ) sqlite3_column _text ( selectRule , 1 ) ] ;
if ( key . length > 0 && value . length > 0 )
{
// Defer allocation of the dictionary until we know we need it
// This also allows us to return nil in the event of an empty set
//
if ( ! identifiers )
{
identifiers = [ NSMutableDictionary dictionary ] ;
}
[ identifiers setObject : value forKey : key ] ;
}
}
sqlite3_finalize ( selectRule ) ;
return [ [ identifiers copy ] autorelease ] ;
}
- ( BOOL ) setFacebookAttribution : ( NSString * ) fbAttribution
{
sqlite3_stmt * updateOptedOut ;
sqlite3_prepare _v2 ( _databaseConnection , "UPDATE localytics_info SET fb_attribution = ?" , -1 , & updateOptedOut , NULL ) ;
sqlite3_bind _text ( updateOptedOut , 1 , [ fbAttribution UTF8String ] , -1 , SQLITE_TRANSIENT ) ;
int code = sqlite3_step ( updateOptedOut ) ;
sqlite3_finalize ( updateOptedOut ) ;
return code = = SQLITE_OK ;
}
- ( void ) collectFacebookAttributionIfAvailable
{
NSString * fbAttribution = [ self facebookAttributionFromPasteboard ] ;
if ( fbAttribution )
{
[ self setFacebookAttribution : fbAttribution ] ;
}
}
- ( NSString * ) facebookAttributionFromPasteboard
{
NSString * cookie = nil ;
UIPasteboard * pasteBoard = [ UIPasteboard
pasteboardWithName : @ "fb_app_attribution"
create : NO ] ;
if ( pasteBoard && [ pasteBoard string ] )
{
cookie = [ pasteBoard string ] ;
}
return cookie ;
}
- ( NSString * ) facebookAttributionFromDb
{
NSString * facebookAttribution = nil ;
sqlite3_stmt * selectFbAttribution ;
sqlite3_prepare _v2 ( _databaseConnection , "SELECT fb_attribution FROM localytics_info" , -1 , & selectFbAttribution , NULL ) ;
int code = sqlite3_step ( selectFbAttribution ) ;
if ( code = = SQLITE_ROW ) {
char * chars = ( char * ) sqlite3_column _text ( selectFbAttribution , 0 ) ;
if ( chars ) facebookAttribution = [ NSString stringWithUTF8String : chars ] ;
}
sqlite3_finalize ( selectFbAttribution ) ;
return facebookAttribution ;
2012-09-12 13:58:54 +00:00
}
# pragma mark - Safe NSDictionary value methods
- ( NSInteger ) safeIntegerValueFromDictionary : ( NSDictionary * ) dict forKey : ( NSString * ) key
{
2013-04-27 20:24:29 +00:00
NSInteger integerValue = 0 ;
id value = [ dict objectForKey : key ] ;
if ( [ value isKindOfClass : [ NSNumber class ] ] || [ value isKindOfClass : [ NSString class ] ] ) {
integerValue = [ value integerValue ] ;
} else if ( [ value isKindOfClass : [ NSNull class ] ] ) {
integerValue = 0 ;
}
return integerValue ;
2012-09-12 13:58:54 +00:00
}
- ( NSString * ) safeStringValueFromDictionary : ( NSDictionary * ) dict forKey : ( NSString * ) key
{
2013-04-27 20:24:29 +00:00
NSString * stringValue = nil ;
id value = [ dict objectForKey : key ] ;
if ( [ value isKindOfClass : [ NSString class ] ] ) {
stringValue = value ;
} else if ( [ value isKindOfClass : [ NSNumber class ] ] ) {
stringValue = [ value stringValue ] ;
} else if ( [ value isKindOfClass : [ NSNull class ] ] ) {
stringValue = nil ;
}
return stringValue ;
2012-09-12 13:58:54 +00:00
}
- ( NSDictionary * ) safeDictionaryFromDictionary : ( NSDictionary * ) dict forKey : ( NSString * ) key
{
2013-04-27 20:24:29 +00:00
NSDictionary * dictValue = nil ;
id value = [ dict objectForKey : key ] ;
if ( [ value isKindOfClass : [ NSDictionary class ] ] ) {
dictValue = value ;
}
return dictValue ;
2012-09-12 13:58:54 +00:00
}
- ( NSArray * ) safeListFromDictionary : ( NSDictionary * ) dict forKey : ( NSString * ) key
{
2013-04-27 20:24:29 +00:00
NSArray * arrayValue = nil ;
id value = [ dict objectForKey : key ] ;
if ( [ value isKindOfClass : [ NSArray class ] ] ) {
arrayValue = value ;
}
return arrayValue ;
2012-09-12 13:58:54 +00:00
}
# pragma mark - Lifecycle
- ( id ) copyWithZone : ( NSZone * ) zone {
2013-04-27 20:24:29 +00:00
# pragma unused ( zone )
2012-09-12 13:58:54 +00:00
return self ;
}
- ( id ) retain {
return self ;
}
- ( unsigned ) retainCount {
// maximum value of an unsigned int - prevents additional retains for the class
return UINT_MAX ;
}
- ( oneway void ) release {
// ignore release commands
}
- ( id ) autorelease {
return self ;
}
- ( void ) dealloc {
2013-04-27 20:24:29 +00:00
sqlite3_close ( _databaseConnection ) ;
2012-09-12 13:58:54 +00:00
[ super dealloc ] ;
}
@ end