My program need to operate database, and now I used 'FMDB' framework and close the sandbox in my xcode project.
but I got a trouble, it's very strange: in myself computer, read/write operate are no problem. (after archive as app), but in the other computer, it can only read, canon't write, Why?
(The database is in this app's source folder).
The problem is not that the database file cannot be found, because I log the database path in other computer, It's right.
My system is "Mac OS 10.15", I'm using SDK that version '10.13' In Xcode.
Read operate: ↓
+ (charData *)getCharacteData:(NSString *)character
{
NSString *databasePath = [[NSBundle mainBundle] pathForResource:#"CN_Characters" ofType:#"DB"];
FMDatabase *db = [FMDatabase databaseWithPath:databasePath];
[db open];
FMResultSet *res = [db executeQuery:[NSString stringWithFormat:#"SELECT * FROM Proverbs WHERE character = '%#';",character]];
charData *charDta = [charData new];
if ([res next]) {
charDta.character = [NSString stringWithString:[res stringForColumn:#"character"]];
charDta.explanation = [NSString stringWithFormat:#"%#",[res stringForColumn:#"explanation"]];
} else {
return NULL;
}
[db close];
return charDta;
}
Write operate: ↓
+ (BOOL)saveCharacterDataWithCharacter:(NSString *)character Explanation:(NSString *)explanation
{
NSString *databasePath = [[NSBundle mainBundle] pathForResource:#"CN_Characters" ofType:#"DB"];
printf("databasePath: %s\n",databasePath.UTF8String);
FMDatabase *db = [FMDatabase databaseWithPath:databasePath];
[db open];
BOOL res = [db executeUpdate:[NSString stringWithFormat:#"UPDATE Proverbs SET explanation = '%#' WHERE character = '%#'",explanation,character]];
[db close];
return res;
}
Is it a permission problem?
When you run your app, you need to copy your file from the bundle into the documents directory.
if([[NSFileManager defaultManager] copyItemAtPath: databasePath
toPath:[documentsDirectory stringByAppendingPathComponent:#"CN_Characters.DB"]
error:&error]){
Otherwise it will be read only.
Related
I´m writing certain values to a file. See Write Operations below.
This works fine when using iPad 6.1 Simulator.
When trying the same thing on my iPad it fails. I think it´s something with sandboxing. I haven´t found out yet which path is best on iOS Devices to write stuff for internal use.
Any ideas?
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError * error = NULL;
NSString *filePath = [NSString stringWithFormat:#"/tmp/%#.txt",fileName];
[string writeToFile:filePath
atomically:YES
encoding: NSUTF8StringEncoding
error:&error];
return YES;
}
You cannot write to /tmp since this is outside of your app sandbox.
However your app also has a temp directory, which can be referenced with the NSTemporaryDirectory() function:
Which works like:
NSString *tempfilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
Here is you method with the correct NSTemporaryDirectory() implementation, also edit some error handling:
#pragma mark Write Operations to Tmp Folder
- (BOOL) psWriteFileWithName: (NSString*) fileName
withString:(NSString*) string {
NSString *fileName = #"artistNumber";
NSError *error = nil;
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
if (![string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error] ) {
NSLog(#"Error writing file: %#", error);
return NO;
}
return YES;
}
I'm having some issue connecting to my sqlite database.. I'm using xcode version 4.4.1
here's my code..
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the database file
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"signature.db"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: databasePath ] == NO)
{
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
status.text = #"Connected..";
} else {
status.text = #"Failed to open/create database..";
}
}
[filemgr release];
Any suggestion..?
Thanks,
Boom
There is no need to check if the database exists; sqlite3_open_v2() (reference) will create the database, if it does not exist, if you pass the flag SQLITE_OPEN_CREATE:
NSString *dbPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPath:#"signature.db"];
if (sqlite3_open_v2([dbPath UTF8String], &database, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE) == SQLITE_OK)
{
status.text = #"Connected..";
// Check the schema and install if it doesn't exist
sqlite3_close(database);
} else {
status.text = #"Failed to open/create database..";
}
You may wish to check if the DB file is in place if you have one in your app's bundle that is pre-built so you can drop it into your documents folder. Of course, as you enhance your app, any schema changes will need to be done in code. Here is some code that I use (that works, but needs to updated a bit)
When opening/creating the DB I use this to specify where the DB file will be. I don't put it with the init because my location is different between iOS and Mac OS X and I want the DB code to work with both:
NSString *dbFilePath;
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolderPath = searchPaths[0];
dbFilePath = [documentFolderPath stringByAppendingPathComponent: #"MyDBFile.db"];
MyDB* appDB = [[EasySpendLogDB alloc] initWithFile:dbFilePath];
if(!appDB) {
// show error
return;
}
This is in my DB access class. It uses some wrapper classes I wrote:
- (id) initWithFile: (NSString*) filePathName {
if(!(self = [super init])) return nil;
BOOL myPathIsDir;
BOOL fileExists = [[NSFileManager defaultManager]
fileExistsAtPath: filePathName
isDirectory: &myPathIsDir];
NSString *backupDbPath = [[NSBundle mainBundle]
pathForResource:#"MyDBFile"
ofType:#"db"];
if (backupDbPath != nil && !fileExists) {
[[NSFileManager defaultManager]
copyItemAtPath:backupDbPath
toPath:filePathName
error:nil];
}
db = [[ABSQLiteDB alloc] init];
if(![db connect:filePathName]) {
return nil;
}
[self checkSchema]; // this is where schema updates are done (see project sample)
return self;
}
You may also want to check out this objective-c wrapper I wrote that has a sample project: https://github.com/AaronBratcher/ABSQLite
I'm trying to open sqlite database from my documents directory:
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbFileName = [docDir stringByAppendingPathComponent:#"DatabaseName.sqlite"];
self.db = [FMDatabase databaseWithPath:dbFileName];
self.db.logsErrors = YES;
self.db = _db;
if ([self.db open]) {
NSLog(#"database opened");
}
NSLog(#"docDir = %#",[NSString stringWithFormat:#"%#%#",docDir,dbFileName]);
NSLog shows strange path docDir = /Users/userName/Documents/Users/userName/Documents/DatabaseName.sqlite instead of /Users/userName/Documents/DatabaseName.sqlite. While opening there are no errors or warnings.
After this I tried get count(*) from my table
NSString *queryString = [NSString stringWithFormat:#"SELECT count(*) FROM histories"];
FMResultSet *result = [self.db executeQuery:queryString];
NSLog(#"count = %i", [result intForColumnIndex:0]);
Database has more when 10k rows but NSLog shows 0. Application is not for iOS, only Command Line. Where can I find the problem?
You have
docDir = /Users/userName/Documents
dbFileName = /Users/userName/Documents/DatabaseName.sqlite
therefore in
NSLog(#"docDir = %#",[NSString stringWithFormat:#"%#%#",docDir,dbFileName]);
the docDir is printed twice (dbFileName already contains docDir).
Remark: The statement self.db = _db; looks suspicious, you might want to remove that.
Added: The FMDB documentation states:
You must always invoke -[FMResultSet next] before attempting to access
the values returned in a query, even if you're only expecting one.
So your code probably should look like this:
FMResultSet *result = [self.db executeQuery:queryString];
if ([result next]) {
NSLog(#"count = %i", [result intForColumnIndex:0]);
}
I can't make a Select on a table in a sqlite database.
I have the following code to copy the .sqlite-file to the user's directory:
// copy the database to the user's directory
- (void)checkAndCreateDB {
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];
// If the database already exists then return without doing anything
if(success) return;
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[NSBundle mainBundle] pathForResource:databaseName ofType:nil];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
[fileManager release];
}
And the select here:
sqlite3 *database;
categories = [[NSMutableArray alloc] init];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = "select * from category";
sqlite3_stmt *compiledStatement;
NSLog(#"get");
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
NSLog(#"test");
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *cId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *cName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
Category *category = [[Category alloc] initWithId:cId name:cName];
[categories addObject:category];
[categories release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
the log shows only: "get". "test" isn't there.
I hope someone can help me.
I believe your database copy code isn't working as you are passing nil to the ofType parameter of [NSBundle pathForResource]. Try passing the correct file extension and add code to detect the success or failure of the copy operation (and make your checkAndCreateDB method return BOOL) and take it from there.
Your database select code looks OK to me, so I'm guess you have an empty database as explained in #Micheal's answer.
sqlite3_open will create a new (empty) database if the one specified doesn't exist. To make sure that this isn't what's happening, try using sqlite3_open_v2 instead and use SQLITE_OPEN_READWRITE as the flags argument. That way you'll get an error if you attempt to open a database that doesn't exist.
There is an example project for using SQLite here you can refer to: https://github.com/AaronBratcher/ABSQLite
It has classes for accessing SQLite in a more traditional database way that I feel makes things easier.
I have a project that includes FMDB to manage SQLite databases. I imported and linked the FMDB wrappers, but problems is that no results are shown when I query the database :
The sqlite database created with Firefox SQLite manager (Ubuntu) and I copy it to Xcode .
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"db.sqlite"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
[db open];
FMResultSet *fResult= [db executeQuery:#"SELECT * FROM mytable"];
while([fResult next])
{
NSLog(#"%#",[fResult stringForColumn:#"title"]);
}
[db close];
If you have copied your database into xCode then you should search for your database in your application's main bundle resource path first, then copy it into the Documents directory if it doesn't exist there yet, only then you can work with it. You might want to debug your FMDatabase object using lastErrorMessage and lastErrorCode messages.
FMDatabase *db = [FMDatabase databaseWithPath:path];
NSLog(#"database instantiated: %#", db];
[db open];
NSLog(#"Database has encountered an error with message: %#. And code: %d", db.lastErrorMessage, db.lastErrorCode];
FMResultSet *fResult= [db executeQuery:#"SELECT * FROM mytable"];
while([fResult next])
{
NSLog(#"%#",[fResult stringForColumn:#"title"]);
}
[db close];
The other problem might of course sound silly, but if your 'mytable' does not contain anything, the while loop expression will always be false. But my best guess - the database is not in the Documents directory.