SQLite will not prepare query when accessing database in Xcode - objective-c

I'm a beginner iPhone developer trying to take information out of a sqlite database in Xcode 4.3. I have my database (which is named DB_Info.sqlite) in the same directory as my .h and .m files, and I also dragged the database into the folders section on the left bar in Xcode.
Could you please take a quick look at my code and let me know where my mistake is? I have used NSLogs to identify where the problem occurs, at the very last if statement, and it's written in comments. Thank you so much in advance!
#import <sqlite3.h>
#implementation Player
{
sqlite3 *DB_Info;
NSString *databasePath;
NSString *docsDir;
NSArray *dirPaths;
}
-(Player*)createPlayer:(NSString*)playerName
{
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"DB_Info.sqlite"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if (sqlite3_open(dbpath, &DB_Info) == SQLITE_OK) { //works fine
NSString *querySQL = [NSString stringWithFormat: #"SELECT * FROM playerlist WHERE fullName=\"%#\"", playerName];
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(DB_Info, query_stmt, -1, &statement, NULL) == SQLITE_OK) { //PROBLEM: This is where the problem is, and the if statement never goes through
//....rest of code here
} else {
NSLog(#"Error");
}
}

First, rather than just saying "Error", log the SQL error message
NSLog(#"%s SQL error '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(database), sqlite3_errcode(database));`
It will tell you precisely what's going wrong. A common error on people's first time SQL projects is that the table is not found. If so, read on. (If not, feel free to ignore the rest of this.)
Second, you're looking for your database in your Documents folder. Did you explicitly copy it from your bundle to your Documents folder at some point? Or did you create it programmatically? But if you prepared it in advance, it won't be in the Documents folder until you copy it there.
Third, I'd also suggest that you consider using sqlite3_open_v2 instead of sqlite3_open. You are checking to see if that's SQLITE_OK, but that may be giving a false sense of security. The sqlite3_open will create a database if it's not there, which is clearly not your intent. Your app should presumably being copying the db from the bundle or creating the database and the tables for you before you get to this method (and you're not showing that, so I'm not sure if you're doing that). Anyway, the sqlite3_open_v2 function will not create the database if it's not there unless you explicitly request it does so by including SQLITE_OPEN_CREATE. So, something like the following will not create the database if it's not found:
if (sqlite3_open_v2(dbpath, &DB_Info, SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK) {
On the off chance that a blank database has been created for you, I'd suggest you reset your simulator via "Reset Content and Settings" on the simulator's menu, or explicitly delete the app, so any blank databases that might have been created can be removed. If you're running this on a device, delete the app and reinstall it.
Fourth, if the database has been created in advance, have you confirmed that the database has been included in the "Copy Bundle Resources" setting? E.g.:
Fifth, if you're running the app on the simulator, you can always browse the simulator's file structure and make sure your files are where you think they are. You can also run the Mac OS sqlite program to inspect the database that the simulator is using to make sure everything is ok. You can even test your SQL right in the db that the simulator uses, which can be useful for diagnostic purposes. (Or, if you don't like the Mac text based sqlite program, you can also buy/download graphical tools, such as Base.) Before you can do this, you might first want to configure your Mac so you can easily browse the Simulator's files requires that you fire up the Terminal program, and issue the command:
chflags nohidden ~/Library
Then you can browse to "~/Library/Application\ Support/iPhone\ Simulator/5.1/Applications/" and then browse the various apps that you have and make sure you db file is there.

Related

NSKeyedArchiver works on simulator but fails on tvOS device

I'm archiving an object to a file - on simulator it works perfectly, but on a tvOS (Apple TV) device it doesn't (in other words -(BOOL)archiveRootObject:toFile: returns a funny NO):
static NSString * _DocumentsDirectory() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentsDirectory, NSUserDomainMask, YES);
return paths[0];
}
and:
NSString *fullPath = [_DocumentsDirectory() stringByAppendingPathComponent:filename];
[NSKeyedArchiver archiveRootObject:obj toFile:fullPath];
where filename = #"foo.data" and obj conforms to NSCoding.
I tried different ways to put together the path and different directories, but the result is the same.
The only thought I have is that there could be some writing permission I need to set.
Any idea?
After investigating a bit, I found out that the only ways that Apple allows you to store data in tvOS apps are the following:
Therefore, I opted for using the Cache directory (which is purged only if space is needed) for data that I can eventually re-download, and NSUserDefaults for smaller data which I need to always access locally.

Sqlite3 database in iPhone gets locked - how to avoid?

I have a query that performs a search on an Sqlite3 DB. It does nothing but read using a reader.
For each found match it calls a callback to the UI which updates a result view.
While this search is running, I hit a button in the UI which will perform some other action in a new thread. In the end it is supposed to remove the search controller's view and show a new controller.
However, at some point the the triggered action wants to write to the databse. And there it just hangs and eventually I will see an exception that the DB is locked.
Interesting is also, that the search reader does not continue either, it is a deadlock.
Do I have to open the database in some special way to support multithreaded usage? What would the constructor for the connection be?
MonoTouch 5.1+ provides an API to let you select the threading model to be used with SQLite.
SqliteConnection.SetConfig (SQLiteConfig.MultiThread);
This maps to some of the connection options of SQLite library.
UPDATE: If you're using an earlier version of MonoTouch (e.g. between 4.2 and 5.0.x) you can use the binary attached to the bug report #652 (follow the instructions) or copy-paste the patch (p/invoke and enum) inside your own application.
I'm not sure I interpret your description correctly, but the way you describe it, it sounds to me that your "reader" steps through the database row-by-row and every time it finds a result it does a callback to a callback function? Is this correct?
If that is the case, you might repeatedly lock your DB, and your search will be slow.
The right way is to extract all matches into a result set in one single query - once that query is completed the lock will be released and you have a result set from SQL that contains only the matching rows.
You let SQLite create a result set like this by using a query of the type "SELECT * FROM tablename WHERE columnX LIKE '%searchstring%'"
(or similar, depending on your search criteria)
This will create a result set with all matches in the database and then release the database lock. Then you can step through the result and create objects and put into and NSArray that is connected to your UI view.
NSArray retval = [NSMutableArray array];
//Create a query
NSString *query = [NSString stringWithFormat:#"SELECT * FROM %# WHERE %# LIKE %#",
tableName, columnName, searchString];
sqlite3_stmt *statement;
//Database locks here
if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, nil)
== SQLITE_OK) {
//Database should unlock here, the query is finished
while (sqlite3_step(statement) == SQLITE_ROW) {
char *nameChars = (char *) sqlite3_column_text(statement, 0);
NSString *name = [NSString stringWithUTF8String:nameChars];
SomeClass *info = [[SomeClass alloc] initWithName:name];
/* Extract other columns and put in your object */
[retval addObject:info];
[info release];
}
sqlite3_finalize(statement);
} else {
NSLog(#"SQL-statement failed");
}
Doing this way there shouldn't be a problem to write to the DB when it is necessary. Only perform new queries to the DB when it's absolutely necessary, for example when your search criteria changed or the content in the DB has been updated.
Do not run repeated queries to a DB that has not changed, or with unchanged search criteria.

Writing an NSString to a Relative File

I'm getting strange behavior writing NSString and NSData objects to relative file paths. Here's an example:
NSString *string = #"I am a file!";
NSError *error = nil;
NSString *fileName = #"text.txt";
BOOL written = [string writeToFile:fileName atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (written) {
NSLog(#"Successfully written to file.");
} else {
NSLog(#"Error: %#", [error localizedDescription]);
}
When I run this I always get "Successfully written to file.", but the file is never there. Somehow the program thinks it was successful and no error is generated.
What am I doing wrong? (I'm on Mac OS X Lion)
This writes to the current directory. The default current directory when you run something under Xcode 4 is going to be ~/Library/Developer/Xcode/DerivedData/<prodDir>/Build/Products/<configuration>. You can override this using a Scheme. When you run a program from the commandline, then the current directory is whatever the current directory was when you ran the program. If you use Finder to launch the app, then the current directory will often be /.
In principle, it's fine to write the current working directory. It's very common to do this in command-line apps. So regarding #craig's comment about writeToFile: expecting an absolute path, I don't think that's really true. It expects and writes to a path. It doesn't care if it's absolute or relative. This is a Foundation class, and is just as useful in a command-line program as a GUI.
But in a GUI app, you should avoid relative paths. In principle, you could set the current directory and then write the file, but this is usually a bad idea in a large program since it's not thread safe (there is only one cwd for the whole program). And GUI apps tend to have somewhat unpredictable current directories, so it doesn't make for a good user experience.
But to the question of why you didn't get an error, it's because it probably successfully wrote it. You just didn't know where to look.
NSFileManager * fm = [NSFileManager new];
NSString * dirPath = [fm currentDirectoryPath];
NSString * absPath = [dirPath stringByAppendingPathComponent:#"myfile.file"];
[fm release];
keep in mind that currentDirectoryPath reflects your programs working directory until you change it with -changeCurrentDirectoryPath:, the programs working directory can be different depending on how it was launched, and can't be relied upon.
The first parameter to the writeToFile: method (in your example) is a relative path, but you probably want to use an absolute path. Otherwise, the system will place your files relative to the current executable. When you're running inside Xcode, this might not be where you expect them to end up. (As Rob mentioned in his answer, this location is somewhat buried, and can change depending on which version of Xcode you're using.)
If you want to build up a directory path using NSString objects, I would recommend the stringByAppendingPathComponent: method:
...
NSString *directory = #"/Users/Mikael/Desktop";
NSString *filename = #"MyFile.txt";
NSString *fullPath = [directory stringByAppendingPathComponent:filename];
...
Note that this method will take care of making sure your slashes are well-formed.

SQLite wrapper in iOS?

I have an iPhone app that stores information to a SQLite database. It works splendid on my phone as well as my brothers. Stuff get's written to it and it can read from it. However, when running it on other people's phone's, it just crashes. Why?
I am using Matteo Bertozzi's SQLite wrapper (put files in the classes folder and linked to them in my .h like this: #import "Sqlite.h")
I have also imported the libsqlite3.dylib to my project.
Declared my SQLite database like this in my .h as well: Sqlite *database;
Set up my database like this:
if (database == nil) {
// Set up database connection
NSString *myDBTwo = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"flashpics.db"];
database = [[Sqlite alloc] init];
[database open:myDBTwo];
}
And call a query like this (none of my variables are nil) :
[database executeNonQuery:#"INSERT INTO images (id, thumbnail, album, caption) VALUES (?, ?, ?, ?)", photoID, thumbnailLocation, photoAlbumID, photoCaption];
However it crashes on my friend's phone and returns EXC_BAD_ACCESS when trying to read it.. Nothing happens when you try to write to it.
Please help!
Thanks
PS: Here's the link to the wrapper: http://th30z.blogspot.com/2008/11/objective-c-sqlite-wrapper_8445.html
Edit #1: Here's some of the errors I get when loading the app. Maybe this is causing it? Or it's not linking to the correct library?
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.2.1 (8C148)/Symbols/usr/lib/info/dns.so (file not found).
And here's what I get when I do the action that makes it crash.
Program received signal: “EXC_BAD_ACCESS”.
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.2.1 (8C148)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
The problem is that you are trying to write to the resources folder in your app bundle, which is read only on a non-jailbroken system, due to safety/security reasons, you have to create the database in the documents folder, so you change your code to this...
EDIT: SORRY I pasted the WRONG code, here is the fixed version.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *myDBTwo = [[paths objectAtIndex:0]stringByAppendingPathComponent:#"flashpics.db"];
RTS is right, you have to copy it over to the Documents directory (or someplace similar) that is writable.
The reason it worked on your devices? If I had to guess I'd say you're both jailbroken and that notion of a code-signed bundle that cannot be modified isn't really enforced.

MAC OS X: How to determine if filesystem is case sensitive?

I have used the statfs(2) system call to get many characteristics of a Mac OS X filesystem, but it doesn't tell me if the filesystem is case-sensitive or not.
I need this information as the application I am developing will be moving many files around and I want to detect potential loss of data due to files being moved from a case-sensitive filesystem to a case-insensitive filesystem.
Can anyone suggest a way of detecting this?
If you're already using stat(2), then you can easily use pathconf(2) with the _PC_CASE_SENSITIVE selector (result 0 = case-insensitve, 1 = case-sensitive. Note that the man page is out of date, but the _PC_CASE_SENSITIVE and _PC_CASE_PRESERVING are supported. By convention, if a file system doesn't support _PC_CASE_SENSITIVE selector then it is case-sensitive.
I’ve looked around and haven’t found an API for that. There are two possibilities I can think of:
Creating a temporary file and trying to open it with a different case pattern, e.g. creating "a9999" and trying to open "A9999". Considering that neither "a9999" nor "A9999" were available on that particular directory, the filesystem is case-sensitive if and only if opening "A9999" fails.
Running diskutil(8) against the filesystem. It reports case-sensitive, -insensitive file systems differently: Name: Mac OS Extended (Case-sensitive) vs. Name: Mac OS Extended (not journaled).
Since diskutil(8) is able to identify that, it could be the case that this information is available via some API or system call.
Edit: It turns out that NSURL has a set of methods that work on file system properties. In particular, -getResourceValue:forKey:error with the key being NSURLVolumeSupportsCaseSensitiveNamesKey will tell you whether a given filesystem (represented as an NSURL instance) supports case sensitive names.
See the following code for an example of its use.
#include <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *path = [NSString stringWithCString:argv[1] encoding:NSASCIIStringEncoding];
NSURL *filesystem = [NSURL fileURLWithPath:path isDirectory:YES];
NSNumber *caseSensitiveFS;
BOOL hasCaseSensitiveResource;
hasCaseSensitiveResource = [filesystem getResourceValue:&caseSensitiveFS
forKey:NSURLVolumeSupportsCaseSensitiveNamesKey error:NULL];
if (hasCaseSensitiveResource)
{
if ([caseSensitiveFS intValue] == 1)
{
NSLog(#"%s is a case sensitive filesystem", argv[1]);
}
else
{
NSLog(#"%s is a case insensitive filesystem", argv[1]);
}
}
else
{
NSLog(#"can't query %s for case sensitiveness", argv[1]);
}
[pool drain];
return 0;
}
Output example:
./testcase /
/ is a case insensitive filesystem
./testcase /Volumes/Disk\ Image/
/Volumes/Disk Image/ is a case sensitive filesystem
./testcase nonono
can't query nonono for case sensitiveness
Create a temporary file with uppercase letters and check if the file exists using lowercase letters, if the test fails the file system is case-sensitive.
Look here for some code to find the HFS subtype of a device:
http://www.opensource.apple.com/source/libfs/libfs-3/FSFormatName.c
The routine is_hfs will return the hfs subtype. If the subtype is kHFSXSubType or kHFSXJSubType, then it's an HFSX (case sensitive) device.