SQLite Step Failed: attempt to write a readonly database , using wrapper - objective-c

I keep getting an error "SQLite Step Failed: attempt to write a readonly database" when using this code to copy a database:
-(void)createEditableCopyOfDatabaseIfNeeded
{
// Testing for existence
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath =
[documentsDirectory stringByAppendingPathComponent:#"Money.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success)
return;
// The writable database does not exist, so copy the default to
// the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"Money.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath
toPath:writableDBPath
error:&error];
if(!success)
{
NSAssert1(0,#"Failed to create writable database file with Message : '%#'.",
[error localizedDescription]);
}
}
I am using the above code in AppDelegate
and this:
NSString *writableDBPath =
[[NSBundle mainBundle] pathForResource:#"Money"
ofType:#"sqlite"];
In ViewController.m
I am using http://th30z.netsons.org/2008/11/objective-c-sqlite-wrapper/ what am I doing wrong?
This is happening again and again... It was working fine before but again the problem started.

The problem is that you cannot write anything to your bundle. To be able to change your database you need to copy it to application's documents or caches folder and work with that copy.

Related

How to copy database file in Application Support directory in Cocoa without losing data?

I have tried this code but it creates BLANK database.sqlite file in document directory.
- (void)createEditableCopyOfDatabaseIfNeeded
{
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"prediction.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"prediction.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
How do I ensure that when I copy database file it does not create an empty database?
Any help would be greatly appreciated.
First your code were not attempts to create a database file.
So... You just want to copy the database file “prediction.sqlite” from source bundle to UserDomain dir?
If this is your purpose,You should pay attention to the code:
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
The correct should be:
if (!success) return;
// The writable database does not exist, so copy the default to the appropriate location.
The directories returned by NSSearchPathForDirectoriesInDomains are not guaranteed to exist; you need to create them yourself, if necessary (see the documentation). NSFileManager's createDirectoryAtPath:withIntermediateDirectories:attributes:error: can help with that.#Anomie
try those code:(before you copy the "prediction.sqlite" file, you need add the file in to your project bundle.):
- (void)createEditableCopyOfDatabaseIfNeeded
{
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"prediction.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (!success){
// create the directories, because the directories returned by NSSearchPathForDirectoriesInDomains are not guaranteed to exist
if (![fileManager createFileAtPath:writableDBPath contents:nil attributes:nil]) {
return;
}
}
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"prediction.sqlite"];
// remove the same file, before you copy the file
[fileManager removeItemAtPath:writableDBPath error:nil];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}

Adding sqlite database: No such file or directory

I wan't to add a sqlite file to preload data for my app. I created this file and copied it into the project directory. I added it to Copy Bundle Resources and copy files. But when I wan't to use this file I get an error saying that there is no such file. I would appreciate if anyone could help me.
The code is listed below:
NSURL *preloadURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"CookBookData" ofType:#"sqlite"]] ;
if (![[NSFileManager defaultManager] copyItemAtURL:preloadURL toURL:storeURL error:&error]) {
NSLog(#"Couldn't preload data, error: %#", error);
}
The error:
2015-07-29 15:42:34.342 CookBook v1[2897:24912] Couldn't preload data, error: Error Domain=NSCocoaErrorDomain Code=4 "The operation couldn’t be completed. (Cocoa error 4.)" UserInfo=0x7fe4a35275d0 {NSSourceFilePathErrorKey=/Users/user/Library/Developer/CoreSimulator/Devices/.../data/Containers/Bundle/Application/.../CookBook v1.app/CookBookData.sqlite, NSUserStringVariant=(
Copy
), NSDestinationFilePath=/Users/user/Library/Developer/CoreSimulator/Devices/.../Documents/CookBookModel 2.sqlite, NSFilePath=/Users/user/Library/Developer/CoreSimulator/Devices/.../data/Containers/Bundle/Application/.../CookBook v1.app/CookBookData.sqlite, NSUnderlyingError=0x7fe4a35145d0 "The operation couldn’t be completed. No such file or directory"}
I tried to change my code:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CookBookModel3.sqlite"];
NSError *error = nil;
NSString *storePath = [storeURL path];
NSLog(#"Error %#", error);
NSString *dbName = #"CookBookData.sqlite";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSString *dbPath = [documentDirectory stringByAppendingString:dbName];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if (!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbName];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (success) {
NSLog(#"Success");
}
else{
NSLog(#"Error %#", [error localizedDescription]);
}
}
else {
NSLog(#"Already exists");
}
if (![[NSFileManager defaultManager] copyItemAtPath:dbPath toPath:storePath error:&error]) {
NSLog(#"Couldn't preload data, error: %#", error);
}
But I get another error:
2015-07-30 12:12:29.118 CookBook[29209:208802] Couldn't preload data, error: Error Domain=NSCocoaErrorDomain Code=516 "The operation couldn’t be completed. (Cocoa error 516.)" UserInfo=0x7fe9526318b0 {NSSourceFilePathErrorKey=/Users/user/Library/Developer/CoreSimulator/Devices/DA5D105A-C9AF-4C91-80B3-DFF2C157CC92/data/Containers/Data/Application/BEED2AB9-BFDA-40AC-A8D0-55DD81F9D985/Library/DocumentationCookBookData.sqlite, NSUserStringVariant=(
Copy
), NSDestinationFilePath=/Users/user/Library/Developer/CoreSimulator/Devices/DA5D105A-C9AF-4C91-80B3-DFF2C157CC92/data/Containers/Data/Application/BEED2AB9-BFDA-40AC-A8D0-55DD81F9D985/Documents/CookBookModel3.sqlite, NSFilePath=/Users/user/Library/Developer/CoreSimulator/Devices/DA5D105A-C9AF-4C91-80B3-DFF2C157CC92/data/Containers/Data/Application/BEED2AB9-BFDA-40AC-A8D0-55DD81F9D985/Library/DocumentationCookBookData.sqlite, NSUnderlyingError=0x7fe952610c30 "The operation couldn’t be completed. File exists"}
2015-07-30 12:12:29.121 CookBook[29209:208802] Managed object context
And another variant:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *path = [documentsDir stringByAppendingPathComponent:#"CookBookData.sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager copyItemAtPath:path toPath:storePath error:&error];
Use the following to get the paths to the file in question:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
NSString *path = [documentsDir stringByAppendingPathComponent:#"CookBookData.sqlite"];
You can then copy the file with the following:
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database.sqlite"];
[fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
I would include more checking and validation than the above - in particular the fileExistsAtPath: method in NSFileManager.
I hope, this gonna help:
NSError *error;
NSString *dbName = #"CookBookData.sqlite";
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSString *dbPath = [documentDirectory stringByAppendingString:dbName];
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbName];
if ([fileManager fileExistsAtPath:defaultDBPath]) { //check if file exists in NSBundle
BOOL success = [fileManager fileExistsAtPath:dbPath]; // check if exists in document directory
if (!success) {
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error]; //copy to document directory
if (success) {
NSLog(#"Database successfully copied to document directory");
}
else{
NSLog(#"Error %#", [error localizedDescription]);
}
}
else {
NSLog(#"Already exists in document directory");
}
}
else{
NSLog(#"Does not exists in NSBundle");
}
Two things you can check for, go to your document directory of application, check if you can go there and other thing is make sure your sqlite file is properly added to project, you can delete and add again. Let me know, if still there is issue

Xcode - Save sqlite3 database in Library folder

I'm developing a app that use a Sqlite3 database. For now the db is inside app package, but I know that I'll have problems to update the data when I test in real world, with a real iPhone.
1 - How can I copy, at bundle phase, my project database to Library folder?
2 - How can I access this copied database? For now my code is:
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"myDatabase.sqlite"];
BOOL success = [fileMgr fileExistsAtPath:dbPath];
What I'll have to change?
3 - How can I preserve this database (with the user's data) if I update the app?
Thanks in advance! ;)
You have to copy the database from the Resources folder to Documents folder
sqlite3 *database;
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"Lists.db"];
success = [fileManager fileExistsAtPath:writableDBPath];
// The writable database does not exist, so copy the default to the appropriate location.
if(!success)
{
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Lists.db"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
TFLog(#"Failed moving database... %#",[error localizedDescription]);
return;
}
}
When you update your app database in your Documents folder will be their you just need to check the condition
success = [fileManager fileExistsAtPath:writableDBPath];
if it does not exits new database will be copied their.
Just keep in mind database structure is same as before.

Can't find the database file

I can't figure why db.sqlite can't be found. I added it to the resource dir of the project. It is there.
Here is some code how I check for existing.
-(void)getDatabaseLocation
{
NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [dirPaths objectAtIndex:0];
// Build the path to the database file
NSString *databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"db.sqlite"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: databasePath ] == NO)
{
NSLog(#"NO"); // It's say no when this method is called.
}
else
{ NSLog(#"YES");
}
}
//EDIT
Now I check that the path in docsDir doesn't exist.
Of course it isn't there. You must first copy the resource to the documents directory, like this:
if (![filemgr fileExistsAtPath:databasePath])
{
[filemgr copyItemAtPath:[NSBundle.mainBundle pathForResource:#"db" ofType:#"sqlite"] toPath:databasePath error:nil];
}
EDIT: If you only need to do lookups on the database (no editing), then you can simply do this:
databasePath = [NSBundle.mainBundle pathForResource:#"db" ofType:#"sqlite"];

SQLite Table does not exist

EDIT: SOLVED! answer below first code box
When i try to connect with a sqlite database (with a table TblTest). In debug mode in my app, the error is:
* Assertion failure in -[QueryClass getTestData:],
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cant build SQL to read item [no such table: TblTest]'
In release mode (simulator), it does not give an error but it will not read the database.
The code behaves like it can connect to the db, but can NOT read the TblTest?
I already reset the simulator and used clean and build.
Sorry for the long code:
#implementation QueryClass
#synthesize databaseName;
#synthesize databaseLite;
-(id)initWithDatabase:(NSString *)databaseNameP{
if(self = [super init]){
[self createEditableCopyOfDatabaseIfNeeded];
self.databaseName = databaseNameP;
self.databaseLite = [self getNewDBConnection];
}
return self;
}
- (void)createEditableCopyOfDatabaseIfNeeded {
NSLog(#"Creating editable copy of database");
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:self.databaseName];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:self.databaseName];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message ‘%#’.", [error localizedDescription]);
}
}
- (sqlite3 *) getNewDBConnection{
sqlite3 *newDBconnection;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:self.databaseName];
// Open the database. The database was prepared outside the application.
if (sqlite3_open([path UTF8String], &newDBconnection) == SQLITE_OK) {
NSLog(#"Database Successfully Opened ");
} else {
NSLog(#"Error in opening database ");
}
return newDBconnection;
}
-(NSMutableArray *)getTestData:(NSInteger) testId{
(NSMutableArray *testArray = [[NSMutableArray alloc]init];
int ret;
sqlite3_stmt *selStmt = nil;
const char *sql = "SELECT testId, name, FROM TblTest WHERE testId = ?";
if (!selStmt)
{ // build update statement
if (sqlite3_prepare_v2(self.databaseLite, sql, -1, &selStmt, NULL)!=SQLITE_OK)
{
selStmt = nil;
}
}
sqlite3_bind_int(selStmt, 1, testId);
if(!selStmt){
NSAssert1(0, #"Cant build SQL to read item [%s]", sqlite3_errmsg(self.databaseLite));
}
while ((ret=sqlite3_step(selStmt))==SQLITE_ROW)
{ // get the fields from the record set and assign to item
Test *test = [[Test alloc]init];
NSNumber *testId = [NSNumber numberWithInt: (int) sqlite3_column_int(selStmt, 0)];
NSString *name = [NSString stringWithUTF8String:(char *) sqlite3_column_text(selStmt, 1)];
test.testId =testId;
test.name =name;
[testArray addObject:pill];
[test release];
}
sqlite3_reset(selStmt);
return [testArray autorelease];
}
//in my function i do:
qc = [[QueryClass alloc]initWithDatabase:#"databaseLite.sql"];
[qc getTestData:0];
I rewrote my code using this tutorial and it works like a charm. :)
Thanks
-(id)initWithDatabase:(NSString *)databaseNameP{
if(self = [super init]){
[self createEditableCopyOfDatabaseIfNeeded];
[self initializeDatabase];
}
return self;
}
// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"databaseLite4.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"databaseLite4.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
// Open the database connection and retrieve minimal information for all objects.
- (void)initializeDatabase {
// The database is stored in the application bundle.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
self.path = [documentsDirectory stringByAppendingPathComponent:#"databaseLite4.sqlite"];
}
When you solve your own problems, you should provide your solution as an answer rather than updating your question. It is possible (and recommended in this situation) to answer your own question and accept it as your answer. You will not gain any reputation for accepting your own answer, but the question will be correctly listed as solved.
Below is the answer I extracted from your question. Feel free to accept it.
I rewrote my code using this tutorial and it works like a charm. :)
Here's my solution:
-(id)initWithDatabase:(NSString *)databaseNameP{
if(self = [super init]){
[self createEditableCopyOfDatabaseIfNeeded];
[self initializeDatabase];
}
return self;
}
// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"databaseLite4.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"databaseLite4.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
// Open the database connection and retrieve minimal information for all objects.
- (void)initializeDatabase {
// The database is stored in the application bundle.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
self.path = [documentsDirectory stringByAppendingPathComponent:#"databaseLite4.sqlite"];
}