SQLite Changes Not Saved - iphone

I'm using SQLite in iOS 4 on an iPhone, but the changes made by my update statements aren't saved. At first thought perhaps quitting the app might be deleting the database somehow, but they're not even persisted during the same session. The code to initialize my database is (using FMDB):
-(SQLiteDal*) init {
pool = [[NSAutoreleasePool alloc] init];
self = [super init];
if(self != nil){
// Setup some globals
NSString *databaseName = [self getDbPath];
NSLog([NSString stringWithFormat:#"db path: %#", databaseName]);
db = (FMDatabase *)[FMDatabase databaseWithPath:databaseName];
if (![db open]) {
NSLog(#"Could not open db.");
[pool release];
return 0;
}
}
//[self checkAndCreateDatabase];
return self;
}
#pragma mark DB Maintenance
-(NSString *)getDbPath {
NSString *databaseName = #"myapp.db";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databaseName = [documentsDir stringByAppendingPathComponent:databaseName];
return databaseName;
}
Both of these methods are called to create the database, then to insert to a table I call:
[db executeQuery:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES (?,?,?,?,?)",
f.Name,
f.description,
f.area,
f.price,
f.id];
The problem is, when I come to read from MyTable using the statement below, I never get anything back:
FMResultSet *rs = [db executeQuery:#"SELECT * FROM MyTable WHERE ID = ?", id];
while ([rs next]) {
//.. this is never called
As far as I can see I'm not missing anything out, and the DB seems to be in a writable location.

When inserting you need to call executeUpdate not executeQuery. Also you should call beginTransaction and then commit, like this:
[_dbPointer beginTransaction];
BOOL isInserted = [_dbPointer executeUpdate:[NSString stringWithFormat:#"INSERT INTO MyTable (Name, Description, Area, Price, ID) VALUES(?,?,?,?,?);", f.Name,
f.description,
f.area,
f.price,
f.id]];
[_dbPointer commit];

Related

Reading from SQLite - FMDB - Beginner

I am trying to read from a database file (performing a simple select all functions).
I am using FMDB.
Here's how i created the DB;
Pro:~ dd$ sqlite3 db.db
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table cus(id integer primary key, firstname varchar(30));
sqlite> inser into cus(firstname)values('f');
Error: near "inser": syntax error
sqlite> insert into cus(firstname)values('f');
sqlite> select * from cus;
1|f
sqlite>
I copied the file (db.db) to my resource folder in xCode. changed the name of the db file to db.db in the app delegate. Code for my program is exactly yhe same as this tutorial.
Here's the code ;
-(NSMutableArray *) getCustomers
{
NSMutableArray *customers = [[NSMutableArray alloc] init];
NSString * path = [(AppDelegate*)[[UIApplication sharedApplication]delegate]databasePath];
NSLog(#"DB path %# ",path);
FMDatabase *db = [FMDatabase databaseWithPath:path];
[db open];
FMResultSet *results = [db executeQuery:#"SELECT * FROM cus"];
NSLog(#"result %# ",results);
while([results next])
{
NSLog(#"result %# ",results);
Customer *customer = [[Customer alloc] init];
customer.customerId = [results intForColumn:#"id"];
customer.firstName = [results stringForColumn:#"firstname"];
[customers addObject:customer];
}
[db close];
return customers;
}
My problem;
Eventhough there is 1 record in the DB, the result of the Select statement is NULL. Why is this and how can i correct it ?
Assuming that the database was created and imported into the project successfully, try the following:
-(NSMutableArray *) getCustomers
{
NSMutableArray *customers = [[NSMutableArray alloc] init];
NSString * path = [(AppDelegate*)[[UIApplication sharedApplication]delegate]databasePath];
NSLog(#"DB path %# ",path);
FMDatabase *db = [FMDatabase databaseWithPath:path];
if(![db open])
{
NSLog(#"Could not open DB, try again");
return nil;
}
FMResultSet *results = nil;
results = [db executeQuery:#"SELECT * FROM cus"];
NSLog(#"result %# ",results);
while([results next])
{
Customer *customer = [[Customer alloc] init];
customer.customerId = [results intForColumn:#"id"];
customer.firstName = [results stringForColumn:#"firstname"];
NSLog(#"Customer object %#", customer);
[customers addObject:customer];
[customer release];
}
[db close];
return customers;
}
I have had this same problem but was managed to resolve this by setting up the path correctly. So, there could be something wrong in the path specification. Do make sure that your database path is perfect. And as everyone suggests, I recommend you to use error statements to narrow down the issue. Wishes!!

how to create a sqlite

how to create a sqlite file when the application starts (didFinishLaunchingWithOptions) the test if it already or not exsist otherwise create the file sqlite
Like this... the sqlPath variable is the path to the pre-made sql database on your ressource
- (void) checkAndCreateSQL
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[documentPath stringByAppendingString:#"/database.sql"]]) {
[[NSFileManager defaultManager] createFileAtPath:[documentPath stringByAppendingString:#"/database.sql"]
contents:[NSData dataWithContentsOfFile:sqlPath]
attributes:nil];
}
}
EDIT 1:
You can create the database on your mac using this command line :
sqlite3 database.sql < DATABASE_CREATION.txt
in the DATABASE_CREATION.txt something like this :
CREATE TABLE IF NOT EXISTS `group` (
`id` integer PRIMARY KEY,
`name` text,
`position` integer
);
Then put directly the database.sql file into your project resource. (like an image)
You'd probably want to use the default Core Data libraries instead of manually creating and handling a single sqlite file. Please check the official Apple Core Data Programming Guide. It will automatically handle the creation and update of the inner database in the app.
sqlite3 *reference2Database() {
if (_database == nil) {
// First, test for existence.
NSError *error;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"my.sqlite"];
if ([fileManager fileExistsAtPath:writableDBPath] == NO) {
// Database file doesnt exists. Copy the database at writable path (documents directory).
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"my.sqlite"];
[fileManager removeItemAtPath:writableDBPath error:nil];
BOOL databaseCopied = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!databaseCopied) {
// Handle the error...
}
}else {
// Open the database. The database was prepared outside the application.
if (sqlite3_open([writableDBPath UTF8String], &_database) != SQLITE_OK) {
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(_database);
_database = nil;
// Additional error handling, as appropriate...
}
}
}
return _database;
}
// Sample usage.
-(void) someDatabaseFunction {
sqlite3 *database = reference2Database();
// Do something with "database"...
}
// Close the database. This should be called when the application terminates.
void closeDatabase() {
if (_database == nil) return;
// Close the database.
if (sqlite3_close(_database) != SQLITE_OK) {
// Handle the error...
}
_database = nil;
}
NOTE: At the top of the file, you should have: static sqlite3 *_database = nil;
I use Matteo Bertozzi's SQLite Wrapper to create my sqlite database with the following code:
-(void)checkDatabase
{
if([[NSFileManager defaultManager] fileExistsAtPath:DBPATH] == NO)
{
sqlite = [[Sqlite alloc] init];
if (![sqlite open:DBPATH]) return;
[sqlite executeNonQuery:#"DROP TABLE yourtable"];
[sqlite executeNonQuery:#"CREATE TABLE yourtable (record1 TEXT NOT NULL,
record2 TEXT NOT NULL,
record3 TEXT NOT NULL,
record4 TEXT NOT NULL);"];
NSArray *results = [sqlite executeQuery:#"SELECT * FROM yourtable;"];
for (NSDictionary *dictionary in results) {
for (NSString *key in [dictionary keyEnumerator])
NSLog(#" - %# %#", key, [dictionary objectForKey:key]);
}
[results release];
[sqlite release];
}
}

problem in inserting the vlaues into database

i was getting familiar with sqlite and try to insert the values into database but giving exception. tf1 and tf2 are object of textfield. here is my code:
#synthesize tf1, tf2, add, disp;
-(id)initApp
{
if(![super init])
return nil;
//DB stored in application bundle
NSString *DBName = #"kbase.sqlite";
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingString:DBName];
db = [FMDatabase databaseWithPath:path];
[db setLogsErrors:TRUE];
[db setTraceExecution:TRUE];
if(![db open])
{
NSLog(#"Could not open Database");
return 0;
}
else
{
NSLog(#"Database opened successfully");
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initApp];
[add addTarget:self action:#selector(insertButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[disp addTarget:self action:#selector(displaytButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
-(IBAction)insertButtonClicked :(id)sender
{
NSLog(#"insert");
NSLog(#"db.....%#",db);
[db executeUpdate:#"insert into info values (?,?)", tf1.text, tf2.text];
[self resignFirstResponder];
}
-(IBAction)displayButtonClicked:(id)sender
{
NSString *str1 = tf2.text;
NSString *returnResult = [[NSString alloc]init];
FMResultSet *rs = [db executeQuery:#"select name from info where rollno = 1"];
while ([rs next])
{
returnResult = [rs stringForColumn:#"rollno"];
[tf1 setText:returnResult];
[tf2 setText:returnResult];
}
}
databse is opening successfully and controll terminate after printing insert
exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSURL executeQuery:]: unrecognized selector sent to instance 0x3d30ef0'
i am not able to configure the problem. plz help.
I think make your method
[db executeUpdate:#"insert into info values (?,?)" withValue1:tf1.text withValue2:tf2.text];
Instead of
[db executeUpdate:#"insert into info values (?,?)", tf1.text, tf2.text];
it seems that db does not exist in this context. If db is a class variable, make sure it is not released somewhere else. Simply put an NSLog(#"%#", db); above the execution to test it.

Problem reading a string from an NSDictionary inside an NSMutableArray stored using NSKeyedArchiver

I'm saving some data using a series of NSDictionaries, stored in an NSMutableArray and archived using NSKeyedArchiver.
I'm basically trying to save the states of several instances the class 'Brick', so I've implemented a getBlueprint method like this (slimmed down version)
-(id)getBlueprint
{
// NOTE: brickColor is a string
NSDictionary *blueprint = [NSDictionary dictionaryWithObjectsAndKeys:
brickColor, #"color",
[NSNumber numberWithInt:rotation], #"rotation",
nil];
return blueprint;
}
And so I have another method that creates a new Brick instance when provided with a blueprint.
-(id)initWithBlueprint:(NSDictionary *)blueprint spriteSheet:(NSString *)ssheet
{
if((self == [super init])){
brickColor = [blueprint objectForKey:#"color"];
[self setColorOffset:brickColor];
while(rotation != [[blueprint objectForKey:#"rotation"] intValue]){
[self setRotation:90];
}
}
return self;
}
Which works when I pass it a 'fresh' blueprint, but not when I read a blueprint from a saved file... sort of. For example, the rotation will work, but changing the color wont. So while I can read the value of brickColor using
NSLog(#"brick color %#", [blueprint objectForKey:#"color"]);
if I try something like
if(brickColor == #"purple"){
colorOffset = CGPointMake(72,36);
NSLog(#"Changed offset for -- %# -- to %#", color, NSStringFromCGPoint(colorOffset));
}
And I know that color is purple, the condition doesn't return true. I thought it might be that somehow NSKeyedUnarchiver changed a string into something else, but the following test returns true.
if([color isKindOfClass:[NSString class]]){
NSLog(#"%# IS A STRING", color);
}else{
NSLog(#"!!!!! COLOR IS A NOT STRING !!!!!");
}
As I said, this isn't a problem if I try to use a freshly created NSDictionary as a blueprint, only when a blueprint is archived and then read back in.
So, as usual, I'm wondering if anyone has any ideas why this might be happening.
incase it's relevant, here's how the data is being stored and recieved.
// Saving
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-(void)buildLevelData{
levelData = [[NSMutableArray alloc] initWithCapacity:100];
for(brickSprite *brick in spriteHolder.children){
[levelData addObject:[brick getBlueprint]];
}
}
-(void)saveLevel
{
[self buildLevelData];
NSData *rawDat = [NSKeyedArchiver archivedDataWithRootObject:levelData];
if([self writeApplicationData:rawDat toFile:saveFileName]){
NSLog(#"Data Saved");
}else{
NSLog(#"ERROR SAVING LEVEL DATA!");
}
[[Director sharedDirector] replaceScene:[MainMenu scene]];
}
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return NO;
}
NSString *appFile = [saveDir stringByAppendingPathComponent:fileName];
return ([data writeToFile:appFile atomically:YES]);
}
// Loading
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
- (void) loadRandomMapFrom:(NSString *)dir
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [paths objectAtIndex:0];
if(!docsDir){
NSLog(#"Cound Not Find Documents Directory When trying To Load Random Map");
return;
}
dir = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#", dir]];
// we'll also set the file name here.
NSArray *existingFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:nil];
// get last file for this test
NSString *filePath = [dir stringByAppendingPathComponent:[existingFiles objectAtIndex:([existingFiles count] - 1)]];
NSMutableArray *levelData = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
[self buildMapWithData:levelData];
}
-(void)buildMapWithData:(NSMutableArray *)lData
{
for(NSDictionary *blueprint in lData){
brickSprite *brick = [[brickSprite alloc] initWithBlueprint:blueprint spriteSheet:#"blocks.png"];
[spriteHolder addChild:brick];
}
}
Sorry about the mess of a question. There's a lot going on that I'm struggling to fully understand myself so it's hard to break it down to the bare minimum.
You should always compare strings with [firstString isEqualToString:secondString], because firstString == secondString only checks for pointer equality, e.g. if both strings are stored at the same location (which they'll never be when comparing dynamically created objects and string constants).

iphone global variable

I want to open my SQLite database in my appDelegate class and reference that database in all my other classes that need the database. I have tried using :
static sqlite3 *database = nil;
But when I try to reference it in my other classes with appDelegate.database, I get a compile error of "error: request for member 'database' in something not a structure or union." How do you reference these types of properies?
You should access any variables stored in the app Delegate through the follow general formula:
YourAppDelegateName *delegate = (YourAppDelegateName *)[[UIApplication sharedApplication]delegate];
//access specific variables in delegate as follows:
sqlite3 *temp = delegate.database;
I suggest you to use the FMDB Objective C wrapper for sqlite. It will really simplify the access to your sqlite database. You can download it from
http://code.google.com/p/flycode/source/browse/trunk/fmdb#fmdb/src
Then,
you can use the following sample code, and use a NSString *db_path variable to access your db from other classes (you use your app delegate to access db_path, then use
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
to acces your db. See the sample code below.
- (NSString *) initialize_db {
NSString *DATABASE_RESOURCE_NAME = #"yourDbName";
NSString *DATABASE_RESOURCE_TYPE = #"db";
NSString *DATABASE_FILE_NAME = #"yourDbName.db";
// copy the database from the bundle if necessary
// look to see if DB is in known location (~/Documents/$DATABASE_FILE_NAME)
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolderPath = [searchPaths objectAtIndex: 0];
NSString *dbFilePath = [documentFolderPath stringByAppendingPathComponent: DATABASE_FILE_NAME];
[dbFilePath retain];
if (! [[NSFileManager defaultManager] fileExistsAtPath: dbFilePath]) {
// didn't find db, need to copy
NSString *backupDbPath = [[NSBundle mainBundle]
pathForResource:DATABASE_RESOURCE_NAME
ofType:DATABASE_RESOURCE_TYPE];
if (backupDbPath == nil) {
// couldn't find backup db to copy, bail
NSLog (#"couldn't init db");
return NULL;
} else {
BOOL copiedBackupDb = [[NSFileManager defaultManager]
copyItemAtPath:backupDbPath
toPath:dbFilePath
error:nil];
if (! copiedBackupDb) {
// copying backup db failed, bail
NSLog (#"couldn't init db");
return NULL;
}
}
}
return dbFilePath;
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
FMResultSet *item_rs;
// copy the database from the bundle if necessary
db_path = [self initialize_db];
if (! db_path) {
// TODO: alert the user!
NSLog (#"couldn't init db");
return;
}
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open]) {
NSLog(#"Could not open the db");
}
FMResultSet *rs = [db executeQuery:#"select * from yourTable"];
if ([db hadError]) {
NSLog(#"Err %d: %#", [db lastErrorCode], [db lastErrorMessage]);
}
while ([rs next]) {
[yourArray addObject:[rs stringForColumn:#"yourColumnName"]];
}
[rs close];
[db close];
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
I needed to create an instance property for the database. My assumption that the static declaration was sufficient was incorrect. BTW, the FMDB/ORM advice is great. I am a huge fan of ORMs. However, this project is my first iphone and it is a small amount of database work and I want to learn. So, I am going to do it old school. Thanks for the advice.
Here are the code changes I made to make my global reference work.. Hope it helps someone:
/* myAppDelegate.h file */
#import <sqlite3.h>
#interface myAppDelegate : NSObject <UIApplicationDelegate> {
... // you may have windows etc here
sqlite3 *database;
}
#property (readwrite) sqlite3 *database;
/* myAppDelegate.m file */
#implementation myAppDelegate
...
#synthesize database;
/* some method in some class that uses the database */
- (void) getSomeData
{
myAppDelegate *appDelegate = (myAppDelegate *) [[ UIApplication sharedApplication ] delegate ];
const char *sql = "SELECT * FROM myTable";
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(appDelegate.database, sql, -1, &selectstmt, NULL) == SQLITE_OK)
{
// get the data here.
}
}