May 15

This is an incomplete experiment creating a class to manage an sqlite3 database on the iPhone.

The place that needs the most work is the mcaDB::queryDB() method. The plan is to eventually have this method populate an array or list that I can access after the query is finished. There are methods in PHP that do similar things.

What got me started was Chapter 11: Basic Data Persistence in Beginning iPhone Development.

You can find documentation and the API reference for SQLite here: http://www.sqlite.org.

/*
 *  mcaDB.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
/*
 *  Instructions for linking to sqlite3 library
 *
 *  In Xcode:
 *
 *      Click on folder: Groups & File / Frameworks
 *      Select menu: Project / Add to Project ...
 *      Navigate to:  /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator[version].sdk/usr/lib/
 *      Select file: libsqlite3.dylib
 *      Uncheck: Copy items into destination groups folder if needed
 *      Change Reference Type to: Relative to Current SDK
 */
 
#include "/usr/include/sqlite3.h"
 
#include "mcaUtils.h"
 
class mcaDB {
 
protected:
 
	sqlite3	*m_database;
 
	bool m_open;
 
public:
 
	void errorDB();
 
	bool openDB( const char *szDB );
 
	void closeDB( bool bForce );
 
	bool execDB( const char *szSQL );
 
	bool queryDB( const char *szSQL );
 
};
/*
 *  mcaDB.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaDB.h"
 
void mcaDB::errorDB()
{
	mcaUtils_logString( @"ERROR", sqlite3_errmsg( m_database ) );
}
 
bool mcaDB::openDB( const char *szDB ) {
 
	NSLog( @"mcaDB::openDB( ... )" );
 
	mcaUtils_logString( @"DATABASE", szDB );
 
	if( sqlite3_open( szDB, &m_database ) != SQLITE_OK ) {
 
		errorDB();
 
		closeDB( true );
 
		return false;
	}
 
	m_open = true;
 
	return true;
}
 
void mcaDB::closeDB( bool bForce ) {
 
	NSLog( @"mcaDB::closeDB( ... )" );
 
	if( m_open || bForce ) {
 
		sqlite3_close( m_database );
 
		m_open = false;
	}
}
 
bool mcaDB::execDB( const char *szQuery ) {
 
	NSLog( @"mcaDB::execDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szQuery );
 
	if( ! m_open ) return false;	
 
	if( sqlite3_exec( m_database, szQuery, NULL, NULL, NULL ) != SQLITE_OK ) {
 
		errorDB();
 
		return false;
	}
 
	return true;
}
 
bool mcaDB::queryDB( const char *szSQL ) {
 
	NSLog( @"mcaDB::queryDB( ... )" );
 
	mcaUtils_logString( @"QUERY", szSQL );
 
	if( ! m_open ) return false;
 
	sqlite3_stmt	*stmt;
 
	if( sqlite3_prepare_v2( m_database, szSQL, -1, &stmt, nil ) == SQLITE_OK) {
 
		while( sqlite3_step( stmt ) == SQLITE_ROW ) {
 
			NSLog( @" ... looping ... " );
 
			int iCols = sqlite3_column_count( stmt );
 
			for( int i = 0; i < iCols; i++ ) {
 
				const char *colName = sqlite3_column_name( stmt, i );
 
				mcaUtils_logString( @"COLUMN", colName );
 
				long colType = sqlite3_column_type( stmt, i );
 
				switch( colType ) {
 
					case SQLITE_INTEGER:
 
						int fldInt = sqlite3_column_int( stmt, i );
						mcaUtils_logInteger( @"INTEGER", fldInt );
 
						break;
					case SQLITE_FLOAT:
 
						NSLog( @"FLOAT" );
 
						float fldFloat = sqlite3_column_double( stmt, i );
						break;
					case SQLITE_TEXT:
 
						const unsigned char * fldStr = sqlite3_column_text( stmt, i );
						mcaUtils_logString( @"TEXT", (const char *) fldStr );
 
						break;
					case SQLITE_BLOB:
 
						int fldSize = sqlite3_column_bytes( stmt, i );
						mcaUtils_logInteger( @"BLOB (size)", fldSize );
						break;
					case SQLITE_NULL:
						break;
					default:
						break;
				}
			}
 
		}
 
		sqlite3_finalize(stmt);
	}
 
	return true;
}

Utility Class

This is just something I whipped up to make it easy to log info in the class above while I debug. This will probably change.

/*
 *  mcaUtils.h
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
void mcaUtils_logString( NSString *szLabel, const char * str );
void mcaUtils_logInteger( NSString * szLabel, int i );
/*
 *  mcaUtils.mm
 *  mcaDB1
 *
 *  Created by Mitchell Allen on 5/15/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */
 
#include "mcaUtils.h"
 
void mcaUtils_logString( NSString * szLabel, const char * str )
{
	NSString *szTemp = [[NSString alloc] initWithUTF8String:str];
 
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %@", szLabel, szTemp ];
 
	NSLog( szMessage );
 
	[szTemp release];
	[szMessage release];
}
 
void mcaUtils_logInteger( NSString * szLabel, int i )
{
	NSString *szMessage = [[NSString alloc] initWithFormat: @"%@ = %d", szLabel, i ];
 
	NSLog( szMessage );
 
	[szMessage release];
}

Sample Usage

This could go into say a viewController class file.

Define the name of your database.

#define TEST_DB_FILE	@"/test.db1"

Build a complete path to your database file.

- (NSString *) dataFilePath;
{
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
 
	NSString *documentsDirectory = [paths objectAtIndex:0];
 
	return [documentsDirectory stringByAppendingString:TEST_DB_FILE];
}

Pass the full path of your database to an object – in say your view controllers viewDidLoad method.

    m_table.initDB( [[self dataFilePath] UTF8String] );

This is how you might define the method above. Yes, in a production environment the final method should always close the database before returning.

void testTable::initDB( const char *szDB ) {
 
	NSLog( @"START: testTable::initDB( ... )" );
 
	if( ! m_db.openDB( szDB ) ) {
 
		NSLog( @" ... ERROR opening database" );
 
		return;
	}
 
	if( ! m_db.execDB(  "DROP TABLE TEST_TABLE;" ) ) {
 
		// Since this is just a test database - we drop the table every time to start fresh.
 
		NSLog( @" ... Couldn't drop table ... ignoring " );
	}
 
	if( ! m_db.execDB(  "CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);" ) ) {
 
		NSLog( @" ... ERROR creating table" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.execDB(  "INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');" ) ) {
 
		NSLog( @" ... ERROR inserting row" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT COUNT(*) AS myCount FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
	if( ! m_db.queryDB(  "SELECT * FROM TEST_TABLE;" ) ) {
 
		NSLog( @" ... ERROR querying DB" );
 
		return;
	}
 
        m_db.closeDB( false );
 
	NSLog( @"END: testTable::initDB( ... )" );
}

Log Output

mcaDB1[3138:20b] START: testTable::initDB( ... )
mcaDB1[3138:20b] mcaDB::openDB( ... )
mcaDB1[3138:20b] DATABASE = /Users/apple/Library/Application Support/iPhone Simulator/User/Applications/.../Documents/test.db1
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = DROP TABLE TEST_TABLE;
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = CREATE TABLE IF NOT EXISTS TEST_TABLE (ROW INTEGER PRIMARY KEY, PLANET TEXT);
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Mercury');
mcaDB1[3138:20b] mcaDB::execDB( ... )
mcaDB1[3138:20b] QUERY = INSERT OR REPLACE INTO TEST_TABLE (PLANET) VALUES ('Venus');
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT COUNT(*) AS myCount FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = myCount
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] mcaDB::queryDB( ... )
mcaDB1[3138:20b] QUERY = SELECT * FROM TEST_TABLE;
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 1
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Mercury
mcaDB1[3138:20b]  ... looping ...
mcaDB1[3138:20b] COLUMN = ROW
mcaDB1[3138:20b] INTEGER = 2
mcaDB1[3138:20b] COLUMN = PLANET
mcaDB1[3138:20b] TEXT = Venus
mcaDB1[3138:20b] mcaDB::closeDB( ... )
mcaDB1[3138:20b] END: testTable::initDB( ... )

Tags: ,