So I have been working on this project for a short while now. I have no problems with reading data from the DB, and formatting it into UITableViews and what not. But now I am wanting also to write to the DB as well. The problem is I keep getting a "Database is Locked" error from sqlite. After messing around with the original version I had the face-palm moment by realizing my database was in the bundle and therefore not writable. So I relocated the DB to the Apps Documents folder, which is writeable. But now I still get the same "Database is Locked" sql error. I only open and close the DB when necessary. And as far as I can tell, I don't leave it open anywhere. Below is the code where I am wanting to do updates. Any thoughts?
- (BOOL) loanBookTo:(NSString *)newborrower{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"books.sqlite"];
if([[NSFileManager defaultManager] fileExistsAtPath:path]){
NSLog(#"File Exists at: %#", path);
}
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {
NSString *mySQL = #"UPDATE BOOKS SET LOANED = 1, BORROWER = \"<BORROWER>\" where ISBN = \"<ISBN>\"";
mySQL = [mySQL stringByReplacingOccurrencesOfString:#"<ISBN>" withString:self.isbn];
mySQL = [mySQL stringByReplacingOccurrencesOfString:#"<BORROWER>" withString:newborrower];
const char *sql = [mySQL UTF8String];
char* errmsg;
sqlite3_exec(database, sql, NULL, NULL, &errmsg);
// Q. Database is locked. Why?
// A. Because it is in the Bundle and is ReadOnly.
// Need to write a copy to the Doc folder.
// Database is Locked error gets spit out here.
printf(errmsg);
sqlite3_close(database);
}
return NO;
}
Open the database once at the start of your app, then close it in applicationWillTerminate of the AppDelegate.
After every exec you will want to do a reset to clear the connection state.
Take a look at the SQLiteBooks sample app, it is coded this way.
How do you move the DB to the documents folder from the bundle? You need to check that it is there, and if not copy it. I get the feeling that either you have copied it some other way, but retained a read-only attribute, or more likely, you are still referencing the original in the bundle.
For details see
Where would you place your SQLite database file in an iPhone app?
or as joshperry says, the SQLiteBooks sample has all the code you need.
How are you getting to loanBookTo? If the database is open and then you call loanBookTo, it may not throw an error of the open, however the database is holding the state from where you came.
Also, at times, the database retains the locked state upon closing and exiting the application, so you could be 'inheriting' a locked state from your previous failures. Deleting the app from the simulator should give you a clean copy.
Related
I'm having some difficulty with my sqlite prepare statement. I get an error saying my table does not exist, although I've checked in multiple places for it, and it does exist, so I'm confuzzled.
The file is in the correct iPhone Simulator Application folder
The file is added to my project and viewable in the project navigator
It is also in my build phases- Copy Bundle Resources area.
I've cleaned and started running again.
The database exists and running my sql statement gets me just the
results I expected.
- (NSMutableArray *) getMyWorkout{
NSMutableArray *workoutArray = [[NSMutableArray alloc] init];
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"IOSDB.sqlite"];
NSLog(#"Db path is %#",dbPath);
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success) {
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)){
sqlite3_close(db);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(db));
}
const char *sql = "SELECT Id, type, difficulty, duration, description FROM workoutTbl";
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK){
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
} //...
When I run it, I get the file path and the following error
2013-02-01 18:07:08.060 TriShake[9251:c07] -[MyWorkoutList getMyWorkout] Prepare failure 'no such table: workoutTbl' (1)
I've checked out these other questions, but have been unable to find a solution
Sqlite Prepare Failed: no such table<tablename>
Sqlite3 gives "no such table" error on iPhone
I understand sqlite3_open() creates an empty database for you if the database path does not exist, but i know it exists, so frustration ensues. Any help or guidance you could give me would be much appreciated.
In terms of your immediate problem, it's going to be something simple.
You say you've "cleaned and built again", but have you actually removed the old app from your simulator? Either remove the app manually, or, easier, just reset the simulator entirely by choosing "reset content and settings" from the "iOS Simulator" menu. Sometimes Xcode is not good about knowing what files to copy over (particularly in a case like this where your running it on the device may be changing the timestamp of the file in the simulator's bundle!)
Run the app again.
If the app doesn't work as expected, open up the database in the simulator folder from the Mac and check out the database to make sure the table is there and precisely as you expected it to be. So navigate to the app, open the bundle (you may have to choose the "show package contents" option), confirm the existence of the database, but just as importantly, open it up this particular copy of the database in your favorite Mac sqlite3 tool of choice and confirm the existence of the table there.
Let us know what you find. Again, it's got to be something simple such as:
Perhaps the process of rebuilding the app was not reinstalling everything; I've occasionally had problems where Xcode elected to not re-copy something during the install on my simulator;
Perhaps your database in your project was accidentally put in a subdirectory, worse, you might have two copies sitting in different directories;
Perhaps the database in your Xcode project is missing (or has a typo or (esp in the case of the device) has incorrect filename capitalization) in the name of the table or file;
Etc.
For a lot of these errors, you won't notice the problem until you completely reset the simulator itself. There are a million little things it could be, but hopefully completely resetting the simulator and starting over will help you find the issue. It's always something simple when it comes to these sorts of issues.
Some other minor observations:
You probably should not be opening databases from the bundle. Programmatically copy it from the bundle to the Documents folder, and open the database from there. I know it seems unnecessary, but it's important for a myriad of reasons (if db changes during operation of the app, if db accidentally gets created on you, don't let Xcode get confused about things that changed (even if only file timestamps) in the bundle changing behind Xcode's back, etc.)
You should, if you need the database to be there, use sqlite3_open_v2, using either SQLITE_OPEN_READWRITE or SQLITE_OPEN_READONLY for flags (but do not include SQLITE_OPEN_CREATE). It causes headaches to ever give sqlite a chance to create a blank database for you, or otherwise modify it, so never give it an opportunity to so.
I have encounter the same problem as yours. If the IOS can not find the designated database file, defaultly it will create one for you instead of throwing an error. So you must open the database file IOS created for you which is blank so it off course contain the table you expected.
what I deal with it :
1 you have to bundle the resource file named *.sqlite3 into your project
2 Then You have to use [NSBundle mainBundle] pathFordirectory...... function to search your proper database file.
then you can open the database file you expected and can operate it properly
Best regards,
Not enough rep to comment on Jack's post, but that helped me.
In my case, I had mistyped my path for resource extension:
// Wrong
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"sqlite3"];
// Should have been (added db ext)
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"db"];
I would always get past the:
if (sqlite3_open([sqLiteDb UTF8String], &_database) == SQLITE_OK))
because it was automatically creating a db file for me.
I've got a database full of usernames and passwords etc. But i need to able to either download a fresh copy of the database every time the user opens the applciation, or even better, read/write to the database using the internet, which is the most practical solution.
This is currently the code that i am using to read and write to the database locally, this was mainly for the purpose of creating the functions and rules that will be used to read/write to the database online.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
databaseName = #"UserDatabase.sql";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
// Execute the "checkAndCreateDatabase" function
[self checkAndCreateDatabase];
[self readNamesFromDatabase];
// Query the database for all username,pass,email records and construct the three different array's for each.
[registerForm setHidden:YES];
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
Thank you in advance!
Since SQLite version is specifically designed for using in iPhone, so the idea of online SQLite is totally wrong.. since SQLite file is stored in iPhone only...
One of the option of updating your SQLite database is you can program your code to update database when your app starts or on update button. Doing this you can Read/Write your database... and your database will be stored on iPhone...
I'm a (kind of) newbie XCode programmer (well, I would say not so newbie nowadays...) and "my pet issue" is: "I'm having trouble saving local files onto my real iPad, compared to saving them with the simulator".
Well to be honest, I have no problem whatsoever SAVING local files, but retrieving them. Why? Because on the simulator my local files seem to persist between compilation sessions, but on the real device, every time the application gets launched (not only after being uploaded from Xcode, but normally launched), data inside the "Documents" directory seems to disappear... So the final user would not be able to store needed historical data between sessions.
Is it a perception of mine? Is it normal behaviour?
The code I use to save this "persistent" data is this one:
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsDir = [paths objectAtIndex:0];
NSString *finalPath=[NSString stringWithFormat:#"%#/%#", documentsDir, path];
NSLog(#"Course.m: updatePersistentObject (to disk): final file = %#",finalPath);
[NSKeyedArchiver archiveRootObject:newObject toFile:finalPath];
'path' variable being #".HistoricalTestResults";
The code I use to retrieve data (wheather at boot time, or at runtime) is this one:
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSLog(#" historical data: Documents paths = %#", paths);
NSString * docsDir = [paths objectAtIndex:0];
NSLog(#"Course.m: loadHistoricalResultsData: docsDir vale [%#]", docsDir);
NSString *tmpPath=[NSString stringWithFormat:#"%#/.HistoricalTestResults", (NSString *)docsDir];
NSLog(#"Course.m: loadHistoricalResultsData: tmpPath vale [%#]", tmpPath);
NSFileManager *localFileManager = [[NSFileManager alloc] init];
// create directory if it doesn't exist, don't do anything if it exists... (?)
[localFileManager createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:nil];
NSDirectoryEnumerator *dirEnum = [localFileManager enumeratorAtPath:tmpPath];
NSString *file;
while (file = [ dirEnum nextObject])
{
NSLog(#"Historical Data Folder: %#", file);
if ( [[file pathExtension] compare:#"dat"] == NSOrderedSame )
{
NSString *filePath = [NSString stringWithFormat:#"%#/%#", tmpPath, file];
NSLog(#"Course.m: loadHistoricalResultsData: filePath vale [%#]", filePath);
mHistoricalTestList=[[NSKeyedUnarchiver unarchiveObjectWithFile:filePath] retain];
}
}
[localFileManager release];
My exact problem is that while on the simulator, AT BOOT TIME, if I put a trace on the "while" code line, I can see how the enumerator gets some value, and I can iterate among the found files.
On the other hand, when using my iPad, the same breakpoint yields a "nil" pointer when obtaining the enumerator.
As I said, at the beginning of a clean program session, this is normal, so then I need to generate some storable results inside my program memory to store them onto disk.
I do it, and then I write them (both inside the simulator and the iPad). Then I can even re-retrieve this data (from disk) and it seems to still exist inside the Documents folder (both onto the iPad and the simulator).
But then, if I close/kill the program, this data seems to be lost onto the real iPad, and to persist in the simulator.
With this behaviour, my only deduction is "Real iPad programs cannot store persistent data onto their Documents directory". Am I right? (Of course not, because I've seen it work on some other programs).
So I have the feeling I'm doing something wrong, and after wasting TONS of time trying to find it, I'm now asking for advice on stackoverflow...
Every piece of help/insight/hint will be more than welcome.
Something I can think of is that your app is not being installed on the same app sandbox everytime. That means that while in the simulator your path is the same and the documents dir contains the same data, your ipad creates a new directory path for the installation, therefore the data you persisted in the last session won't be accessible.
Another thing that I experienced, was that the simulator was case insensitive when loading up resources, in contrast with the device that is actually case sensitive. In that time I had a strings file with extension .Strings and the iphone was looking for .strings. The simulator would work but the phone showed the keys when LocalizedString() macro was called.
So, tl;dr: verify the paths are the same in the different sessions, and your file names match.
I hope it helps. good luck!
PS: Gaudà rlz.
I'm happy! I was able to solve my own mess (!!?).
I've discovered that, when I originally copied my 2 code snippets, specially the "read part", I didn't copy it literally as it appears on my actual code, I just adapted some variable names and extra stuff that wasn't important here. (Typical situation)
I even did some more things than "removing some unrelated code pieces", (and here comes the important part), as I reordered some of them. And one of the parts I reordered was this one (which didn't work):
NSFileManager *localFileManager = [[NSFileManager alloc] init];
NSString *tmpPath=[NSString stringWithFormat:#"%#/.HistoricalTestResults", (NSString *)docsDir];
NSDirectoryEnumerator *dirEnum = [localFileManager enumeratorAtPath:tmpPath];
// create directory if it doesn't exist, don't do anything if it exists... (?)
[localFileManager createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:nil];
which I changed (when posting here), onto this other part (which works):
NSString *tmpPath=[NSString stringWithFormat:#"%#/.HistoricalTestResults", (NSString *)docsDir];
NSFileManager *localFileManager = [[NSFileManager alloc] init];
// create directory if it doesn't exist, don't do anything if it exists... (?)
[localFileManager createDirectoryAtPath:tmpPath withIntermediateDirectories:YES attributes:nil error:nil];
NSDirectoryEnumerator *dirEnum = [localFileManager enumeratorAtPath:tmpPath];
There's proably some logic behind this, as "why the first part does work on the emulator, but doesn't on the real device". It seems that "createDirectoryAtPath" is somewhat "resetting" some internal stuff, or maybe asking for an enumerator without having created a directory doesn't make sense...
Whatever the case is, I think my experience is worth enough to keep it here posted for someone who might be in my situation in the future!
Greetings again!
I have to add an existent DB into my iphone app
I' ve tried to put it into the project's folder and used this init function
-(id) initDatabase{
databaseName = #"mydatabase.sqlite";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[databasePath retain];
return self;
}
and then i used this function
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK){ //inserting methods
but I have an error: the table 'nametable' does not exists, I am sure that this table exists and I think that these methods create a new db file into the device path.
You can't have any files in the documents folder to start. Everything that comes with the app has to be included in the app bundle. The first time the app runs, the documents folder is always empty.
You need to locate the file inside the app bundle using NSBundle's pathForResource:ofType:.
If the DB is readonly, you can just leave it there. If it is readwrite, you need to copy the file into the documents or library folder the first time the app launches.
Update:
could you make an example about using
'NSBundle's pathForResource:ofType:'
please?I'm sorry but I'm new to iphone
sdk
It's just:
NSString *path=[[NSBundle mainBundle] pathForResource:#"myDatabase" ofType:#"sqlite"];
In the sidebar, check "Targets" => (Your Target) => "Copy Bundle Resources". I think your database file might not have been copied because Xcode has no idea what file type that is.
I created a sqlite3 database, created tables and insert some data. I can retrieve using select query by using terminal application.
But when i add this database to my iPhone application resources and try to access data programatically I get error as "no such table: table name"
Why does this happen?
sqlite3_open() creates an empty database for you if the database path does not exist. So, it is possible that the path you gave it does not lead you to the intended file. With an empty database, you get "no such table" a lot.
The DB file is probably not reachable by the iPhone. Try creating the DB from the iPhone app itself.
NSArray *paths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES
);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [
documentsDirectory stringByAppendingPathComponent:#"yourdbname.sql"
];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &db_handle) != SQLITE_OK) {
// Even though the open failed,
// call close to properly clean up resources.
sqlite3_close(db_handle);
NSAssert1(0,
#"Failed to open database with message '%s'.",
sqlite3_errmsg(db_handle)
);
}
And the perform your query again. Also, it helps if you check for potential errors on every step of your DB access code.
I had the same error with sqlite3X C++ wrapper.
I use this library in my IE-plugin.
The problem was in unavailability .sqlite base.
Current directory for IE(not plugin) is "C:\Documents and Settings\UserName\desktop\".
When I put my base in this location - problem was solved.