The ATTACH DATABASE command is useful for transferring rows between sqlite database files and allows you to join rows from tables across databases
e.g.
$ sqlite3 BookLoansDB.sqlite
sqlite> ATTACH DATABASE '/Users/.../Documents/BooksDB.sqlite' AS books_db;
sqlite> select B.BookName, B.Pages, BL.LentTo from main.tblBookLoan BL inner join books_db.tblBook B on B.BookID = BL.BookID;
The Client|512|Jenny
The Pelican Brief|432|Mike
How can I do the same from objective-c on the iPhone. I've had no success with this kind of code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
const char *booksDBPath = [[documentDirectory stringByAppendingPathComponent:#"BooksDB.sqlite"] UTF8String];
const char *bookLoansDBPath = [[documentDirectory stringByAppendingPathComponent:#"BookLoansDB.sqlite"] UTF8String];
sqlite3 *bookLoansDB;
int result = sqlite3_open(bookLoansDBPath, &bookLoansDB);
sqlite3_stmt *attachStmt;
NSString *attachSQL = [NSString stringWithFormat: #"ATTACH DATABASE \'%s\' AS books_db", bookLoansDBPath];
result = sqlite3_prepare_v2(bookLoansDB, [attachSQL UTF8String] , -1, &attachStmt, nil);
char *errorMessage;
result = sqlite3_exec(bookLoansDB, [attachSQL UTF8String], NULL, NULL, &errorMessage);
sqlite3_stmt *selectStmt;
NSString *selectSQL = #"select * from main.tblBookLoan BL inner join books_db.tblBook B on B.BookID = BL.BookID";
result = sqlite3_prepare_v2(bookLoansDB, [selectSQL UTF8String] , -1, &selectStmt, nil);
// result == 1
result = sqlite3_step(selectStmt) ;
// result == 21
if (result == SQLITE_ROW)
{
//do something
}
Can it even be done?
I've got the example working (had my database names mixed up in the "attach database" SQL). So yes it can be done. Thanks for pointing me in the right direction Deepmist
As such examples are rather hard to find, I've pasted the working version below.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
const char *booksDBPath = [[documentDirectory stringByAppendingPathComponent:#"BooksDB.sqlite"] UTF8String];
const char *bookLoansDBPath = [[documentDirectory stringByAppendingPathComponent:#"BookLoansDB.sqlite"] UTF8String];
sqlite3 *bookLoansDB;
if (sqlite3_open(bookLoansDBPath, &bookLoansDB) == SQLITE_OK) {
NSString *attachSQL = [NSString stringWithFormat: #"ATTACH DATABASE \'%s\' AS books_db", booksDBPath];
char *errorMessage;
if (sqlite3_exec(bookLoansDB, [attachSQL UTF8String], NULL, NULL, &errorMessage) == SQLITE_OK) {
sqlite3_stmt *selectStmt;
NSString *selectSQL = #"select * from main.tblBookLoan BL inner join books_db.tblBook B on B.BookID = BL.BookID";
if (sqlite3_prepare_v2(bookLoansDB, [selectSQL UTF8String] , -1, &selectStmt, nil) == SQLITE_OK) {
int n=0;
while (sqlite3_step(selectStmt) == SQLITE_ROW) {
//do something
}
}
else {
NSLog(#"Error while creating select statement: '%s'", sqlite3_errmsg(bookLoansDB));
}
}
else {
NSLog(#"Error while attaching databases: '%s'", errorMessage);
}
}
else {
NSLog(#"Failed to open database at %# with error %s", booksDBPath, sqlite3_errmsg(bookLoansDB));
sqlite3_close(bookLoansDB);
}
You can attach databases in sqlite on the iPhone. It's hard to say what's going on with your code but it should help if it looks a little more like this:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
const char *booksDBPath = [[documentDirectory stringByAppendingPathComponent:#"BooksDB.sqlite"] UTF8String];
const char *bookLoansDBPath = [[documentDirectory stringByAppendingPathComponent:#"BookLoansDB.sqlite"] UTF8String];
sqlite3 *bookLoansDB;
if (sqlite3_open(bookLoansDBPath, &bookLoansDB) == SQLITE_OK) {
NSString *attachSQL = [NSString stringWithFormat: #"ATTACH DATABASE \'%s\' AS books_db", bookLoansDBPath];
char *errorMessage;
if (sqlite3_exec(bookLoansDB, [attachSQL UTF8String], NULL, NULL, &errorMessage) == SQLITE_OK && errorMessage == nil) {
sqlite3_stmt *selectStmt;
NSString *selectSQL = #"select * from main.tblBookLoan BL inner join books_db.tblBook B on B.BookID = BL.BookID";
if (sqlite3_prepare_v2(bookLoansDB, [selectSQL UTF8String] , -1, &selectStmt, nil) == SQLITE_OK) {
while (sqlite3_step(selectStmt) == SQLITE_ROW) {
//process row
}
}
else {
NSLog(#"Error while creating select statement: '%s'", sqlite3_errmsg(bookLoansDB));
}
}
else {
NSLog(#"Error while attaching databases: '%s'", errorMessage);
}
}
else {
NSLog(#"Failed to open database at %# with error %s", booksDBPath, sqlite3_errmsg(bookLoansDB));
sqlite3_close(bookLoansDB);
}
I haven't tested this code, just modified yours, so it might require fixes.
Related
I'm trying to create a SQLite Database but run into a problem that seems to derive from the table CONTACTS never being created (I get the error message 'Error while inserting 'no such table: CONTACTS'
').
From the code below, can anyone see what is wrong and how it can be corrected? This example is taken straight from this guide, which is why I'm surprised it doesn't work:
http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_6_iPhone_Application
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
//Buid the path for database file
_databasePath = [[NSString alloc]
initWithString:[docsDir stringByAppendingPathComponent:#"contacts.db"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if([filemgr fileExistsAtPath: _databasePath ] == NO)
{
const char *db_path = [_databasePath UTF8String];
if(sqlite3_open(db_path, &_contactDB) == SQLITE_OK)
{
char *errMsg;
const char *sql_stmt =
"CREATE TABLE IF NOT EXISTS CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT)";
if (sqlite3_exec(_contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
{
_text.text = #"Failed to create table";
}
sqlite3_close(_contactDB);
} else {
_text.text = #"Failed to open/create database";
}
}
}
- (IBAction)saveName:(id)sender {
NSString *currentName = self.text.text;
NSString *nameSaved = [[NSString alloc] initWithFormat:currentName];
sqlite3_stmt *statement;
const char *dbpath = [_databasePath UTF8String];
if (sqlite3_open(dbpath, &_contactDB) == SQLITE_OK)
{
NSLog(#"pass");
NSString *insertSQL = [NSString stringWithFormat:
#"INSERT INTO CONTACTS (name) VALUES (\"%#\")",
nameSaved];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(_contactDB, insert_stmt,
-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSLog(#"pass");
} else {
NSLog( #"Error while inserting '%s'", sqlite3_errmsg(_contactDB));
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
}
}
There is nothing wrong with that code - it runs fine here on Mac OS X and in the iOS simulator. Does the error come later when you try and insert data into the table? Are you closing the file before trying to insert data into it?
Run it in the simulator, step through the code, and then find the file in the simulator's Documents directory. Use Terminal to see what's in the file:
sqlite3 path-to-file .dump
The exact path will differ on your machine but in my iOS simulator I get:
sqlite3 "/Users/my-login-name/Library/Application Support/iPhone Simulator/6.1/Applications/DAD0BB47-2136-4367-8DAF-AA578D0C00A5/Documents/contacts.db" .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT);
COMMIT;
You can see that the table exists with the correct columns.
Use objectAtIndex:
sqlite3* _contactDB;
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
//Buid the path for database file
NSString * _databasePath = [[NSString alloc]
initWithString:[docsDir stringByAppendingPathComponent:#"contacts.db"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if([filemgr fileExistsAtPath: _databasePath ] == NO)
{
const char *db_path = [_databasePath UTF8String];
if(sqlite3_open(db_path, &_contactDB) == SQLITE_OK)
{
char *errMsg;
const char *sql_stmt =
"CREATE TABLE IF NOT EXISTS CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT)";
if (sqlite3_exec(_contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
{
NSLog(#"Failed to create table") ;
}
sqlite3_close(_contactDB);
} else {
NSLog( #"Failed to open/create database");
}
}
I have database, but when i tried to remove data from the database nothing happens, what should i do to make sure it works? Because when i pressed delete the dta is still in the database
This is the code:
/file path to database
-(NSString*)filePath {
NSArray*paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0]stringByAppendingPathComponent:#"bp.sql"];
}
//open database
-(void)openDB {
if(sqlite3_open([[self filePath]UTF8String], &db) !=SQLITE_OK) {
sqlite3_close(db);
NSAssert(0, #"Databese failed to open");
}
else {
NSLog(#"database opemed");
}
}
- (IBAction)del:(id)sender {
NSString*sql = [NSString stringWithFormat:#"DELETE key, theDate, customer, code1, code2 FROM summary WHERE key=\"%#\"",customerName];
const char* query_stmt = [sql UTF8String];
sqlite3_stmt*statement;
sqlite3_prepare_v2(db, query_stmt, -1, & statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSAssert(0, #"database object delete failed");
} else {
NSLog(#"No error");
}
sqlite3_finalize(statement);
sqlite3_close(db)
You can't delete specific column values using the DELETE query. It's for removing the entire row.
The problem is with the following query:
NSString*sql = [NSString stringWithFormat:#"DELETE key, theDate, customer, code1, code2 FROM summary WHERE key=\"%#\"",customerName];
Change it to:
NSString*sql = [NSString stringWithFormat:#"DELETE FROM summary WHERE key=\"%#\"",customerName];
If you need to remove particular column value of a row use the UPDATE query.
Please check the sqlite documentation for the details
All the functions that you wrote like checking filepath, opendb should occur in the same function(maybe inside your del function).
This is how I will do it:
-(void)updateStatus:(NSString *)queryString {
NSString *docsDir;
NSArray *dirPaths;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
strDatabasePath = [NSString stringWithString:[docsDir stringByAppendingPathComponent:#"bp.sql"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: strDatabasePath] == YES)
{
const char *dbpath = [strDatabasePath UTF8String];
if (sqlite3_open(dbpath, &sqlDatabase) == SQLITE_OK)
{
const char* beginString = "BEGIN;";
sqlite3_stmt *compiledstatement;
sqlite3_prepare_v2(sqlDatabase, beginString, -1, &compiledstatement, NULL);
if (sqlite3_step(compiledstatement) == SQLITE_DONE) {}
else DLog(#"Failed!");
sqlite3_finalize(compiledstatement);
DLog(#"QUERY : %#",queryString);
const char *selectStatement = [queryString UTF8String];
sqlite3_prepare_v2(sqlDatabase, selectStatement, -1, &compiledstatement, NULL);
//sqlite3_bind_text(compiledstatement,1,[statusString UTF8String],-1,SQLITE_TRANSIENT);
if (sqlite3_step(compiledstatement) == SQLITE_DONE) {}
else DLog(#"Failed!");
sqlite3_finalize(compiledstatement);
const char* endString="END;";
sqlite3_prepare_v2(sqlDatabase, endString, -1, &compiledstatement, NULL);
if (sqlite3_step(compiledstatement) == SQLITE_DONE) {}
else DLog(#"Failed!");
sqlite3_finalize(compiledstatement);
sqlite3_close(sqlDatabase);
}
else DLog(#"Failed to open table");
}
}
NSString *queryString;
queryString = [NSString stringWithFormat:#"DELETE key, theDate, customer, code1, code2 FROM summary WHERE key=\"%#\"",customerName];
[self updateStatus:queryString];
Hope this helps...
-(BOOL)DeleteWishList:(int)rowno
{
NSString *queryString=[NSString stringWithFormat:#"delete from wishtable where _id=%d",rowno];
[self openDB];
char *err;
if (sqlite3_exec(dBObject, [queryString UTF8String], NULL,NULL, &err)!= SQLITE_OK)
{
sqlite3_close(dBObject);
return NO;
}
else
{
return YES;
}
}
I am working on iPhone app and calling following code on my button click, its updating my Database table only on first click. When I click it second time it gives me success message but database table is not getting updated.
Code is :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Games.sqlite"];
NSMutableArray *arrayRandomLevel = [[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3",#"4",#"5",nil];
for (int i = 0; i< 5; i++)
{
NSUInteger randomIndex = arc4random() % 5;
[arrayRandomLevel exchangeObjectAtIndex:i withObjectAtIndex:randomIndex];
//firstObject +=1;
}
NSLog(#"arrayRandomLevel = %#",arrayRandomLevel);
for (int level = 0; level < 5; level++)
{
NSString *strLevel = [#"" stringByAppendingFormat:#"%d",level+1];
int finalLevel = [[arrayRandomLevel objectAtIndex:level] intValue];
NSLog(#"Static level = %#, Changed level = %d",strLevel,finalLevel);
sqlite3 *database;
if(sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
NSString *cmd = [NSString stringWithFormat:#"UPDATE zquestionsdata SET zdifficultylevel = %d WHERE zanswertype = '%#' AND zquestiontype = '%#';",finalLevel,strLevel,#"Geni"];
const char * sql = [cmd UTF8String];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sql, -1, &compiledStatement, NULL) == SQLITE_OK)
{
sqlite3_step(compiledStatement); // Here is the added step.
NSLog(#"update SUCCESS - executed command %#",cmd);
}
else {
NSLog(#"update FAILED - failed to execute command %#",cmd);
}
sqlite3_finalize(compiledStatement);
}
else {
NSLog(#"pdateContact FAILED - failed to open database");
}
sqlite3_close(database);
}
Please help me on this or provide any other solution to update table each time.
Thanks in advance.
The Updating process will happen at
if(sqlite3_step(statement) == SQLITE_DONE)
There the values assigned will go and store in the database the statement look like the above.
code from Prepare statement is given below
if(sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL) == SQLITE_OK)
{
if(sqlite3_step(statement) == SQLITE_DONE)
{
//assign new values here
}
}
I have the following function in my iPhone project which works great...unless the query returns nothing and then the app crashes. It is being a pain to debug with none of the breakpoints being activated at all!
I know this works as I pass in static stuff that is in the DB and it returns a value.
-(NSString *)getSomeText:(NSString *)toPass {
sqlite3 *database;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"sf.sqlite"];
int strLength = 0;
strLength = [toPass length];
if (strLength <3)
return #"Unknown";
NSString *MIDstr;
NSMutableString * toPass Copy = [NSMutableString stringWithString:toPass];
MIDstr = [toPassCopy substringWithRange:NSMakeRange(0, 3)];
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
// Setup the SQL Statement and compile it for faster access
NSString *BaseSQL = [NSString stringWithFormat:#"select * from MIDS where MID = '%#'",MIDstr];
NSLog(BaseSQL);
const char *sqlStatement = [BaseSQL UTF8String];
//NSLog(BaseSQL);
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *aName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *returnString = [NSString stringWithFormat:#"%#",aName];
return returnString;
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
}
A. if sqlite3_step does not return any rows, you crash because you have declared that you are returning a NSString, but when there are no rows you return nothing.
The caller will try to read a NSString from the stack and thus end up dereferencing garbage.
To quickly fix the problem, write:
sqlite3_close(database);
return nil;
}
and make sure the caller handles nil results.
B/ If you do have data, your code never gets to call sqlite3_finalize and sqlite3_close because you return early:
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
[..]
return returnString;
while (sqlite3_step(sqlstatement) == SQLITE_ROW )
{
//Your code goes here
}
sqlite3_finalize(sqlstatement);
sqlite3_close(databaseRefObj);
close the database and finalize your statement after the while loop this helped me out,
i've been trying to figure out wh. sqy my object allocation keeps rigth up every time i call this function, Instruments reports no leaks but I get a heck of a lot of object coming from
sqlite3_exec --> sqlite3Prepare --> sqlite3Parser --> yy_reduce --> malloc & also a whole bunch from
& from
sqlite3Step --> sqlite3VdbeExec --> sqlite3BtreeInsert --> malloc
I tried solving it by following the suggestions posted here: http://www.iphonedevsdk.com/forum/iphone-sdk-development/7092-sqlite3-database-gobbling-up-memory.html but haven't been able to fix it
ANY HELP is appreciated, my code is below
+(void)getDesignationsInLibrary:(NSString *)library
{
NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
NSString *dbName = #"s8.sqlite";
NSArray *documentPaths = \
NSSearchPathForDirectoriesInDomains \
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = \
[documentPaths objectAtIndex:0];
NSString *databasePath = \
[documentsDir stringByAppendingPathComponent:dbName];
[[DT sharedDT].designationsInLibrary removeAllObjects];
NSString *sqlString;
for(int i=0;i<[[DT sharedDT].typesInLibrary count];i++)
{
if(sqlite3_open([databasePath UTF8String], &db)==SQLITE_OK)
{
if (sqlite3_exec(db, "PRAGMA CACHE_SIZE=50;", NULL, NULL, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error: failed to set cache size with message '%s'.", sqlite3_errmsg(db));
}
NSMutableString *lib=[NSMutableString stringWithString:library];
[lib appendString:#"-"];
[lib appendString:[[DT sharedDT].typesInLibrary objectAtIndex:i]];
if([DT sharedDT].sortedBy==#"AISC Default")
{
sqlString = [NSString stringWithFormat:#"select DESIGNATION from \"%#\";",lib];
}
else
{
sqlString = [NSString stringWithFormat:#"select DESIGNATION from \"%#\" order by cast(%# as numeric) %#;",lib, [DT sharedDT].sortedBy, [DT sharedDT].sortAscDesc];
}
const char *sql = [sqlString cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(db,sql,-1,&selectstmt, NULL)==SQLITE_OK)
{
while(sqlite3_step(selectstmt)==SQLITE_ROW)
{
[[DT sharedDT].designationsInLibrary addObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,0)]];
}
sqlite3_finalize(selectstmt);
selectstmt=nil;
}
}
}
sqlite3_close(db);
[localPool release];
}
It seems, that you're opening db on every loop cycle, but close only once, before function exit
So try to change:
}
sqlite3_close(db);
[localPool release];
}
to
sqlite3_close(db);
}
[localPool release];
}
Or even better change:
for(int i=0;i [[DT sharedDT].typesInLibrary count];i++)
{
if(sqlite3_open([databasePath UTF8String], &db)==SQLITE_OK)
{
if (sqlite3_exec(db, "PRAGMA CACHE_SIZE=50;", NULL, NULL, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error: failed to set cache size with message '%s'.", sqlite3_errmsg(db));
}
to:
if(sqlite3_open([databasePath UTF8String], &db)==SQLITE_OK)
{
if (sqlite3_exec(db, "PRAGMA CACHE_SIZE=50;", NULL, NULL, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error: failed to set cache size with message '%s'.", sqlite3_errmsg(db));
}
for(int i=0;i [[DT sharedDT].typesInLibrary count];i++)
{
...
because you're always open the same database
Try invoking sqlite3_exec with:
pragma cache_size=1
Sqlite seems to gobble up memory for caching.