2011-11-10 6 views
7

Sto utilizzando FMDB per creare un database SQLite su iPhone. Ho un initial.sql che ha la formaPiù query non in esecuzione in FMDB

CREATE TABLE Abc ... ; 
CREATE TABLE Def ... ; 

che carico questa caricando il file in un NSString ed eseguirlo

NSString * str = // string from file initial.sql 

[db executeUpdate: str]; 

Questo succede ma in seguito ho un fallimento:

no such table: Def 

E 'chiaro che la seconda istruzione non viene chiamato. Come posso fare in modo che tutte le query vengano chiamate?

Secondo la documentazione SQLite: "Le routine sqlite3_prepare_v2(), sqlite3_prepare(), sqlite3_prepare16(), sqlite3_prepare16_v2(), sqlite3_exec(), e sqlite3_get_table() accettare una lista di un'istruzione SQL (SQL-stmt-list) che è un elenco di dichiarazioni separate da punto e virgola. "

Quindi, tutto dovrebbe funzionare.

+0

Vedere https://github.com/ccgus/fmdb/issues/59 – luqmaan

risposta

8

Sono stato morso anche da questo; ho impiegato un'intera mattinata a esaminare FMDatabase e leggere la documentazione dell'API sqlite3 per trovarlo. Non sono ancora del tutto sicuro della causa principale del problema, ma in base a this bug in PHP, è necessario chiamare sqlite3_exec anziché preparare l'istruzione con sqlite3_prepare_v2 e quindi chiamare sqlite3_step.

La documentazione non sembra suggerire che questo comportamento potrebbe accadere, quindi la nostra confusione, e mi piacerebbe che qualcuno con più esperienza con sqlite presentasse alcune ipotesi.

Ho risolto questo problema sviluppando un metodo per eseguire un batch di query. Si prega di trovare il codice qui sotto. Se preferisci, puoi riscriverlo in una categoria invece di aggiungerlo a FMDatabase.h, la tua chiamata.

Aggiungere questo all'interfaccia FMDatabase in FMDatabase.h:

- (BOOL)executeBatch:(NSString*)sql error:(NSError**)error; 

Aggiungere questo all'attuazione FMDatabase in FMDatabase.m:

- (BOOL)executeBatch:(NSString *)sql error:(NSError**)error 
{ 
    char* errorOutput; 
    int responseCode = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &errorOutput); 

    if (errorOutput != nil) 
    { 
     *error = [NSError errorWithDomain:[NSString stringWithUTF8String:errorOutput] 
            code:responseCode 
           userInfo:nil]; 
     return false; 
    } 

    return true; 
} 

Si prega di notare che ci sono molte caratteristiche mancanti da executeBatch che lo rendono inadatto per molti scopi. In particolare, non controlla se il database è bloccato, non si assicura che FMDatabase non sia bloccato, non supporta la memorizzazione delle istruzioni.

Se necessario, quanto sopra è un buon punto di partenza per codificarlo autonomamente. Happy hacking!

+0

In realtà ho finito per assicurarmi che le mie query a batch fossero su linee diverse e suddivisione per il n carattere ewline.Quindi l'ho circondato con BEGIN/COMMIT per rendere tutto una singola transazione. – George

2
Split Batch Statement 
Add in .h file: 
#import "FMSQLStatementSplitter.h" 
#import "FMDatabaseQueue.h" 

FMSQLStatementSplitter can split batch sql statement into several separated statements, then [FMDatabase executeUpdate:] or other methods can be used to execute each separated statement: 

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:databasePath]; 
NSString *batchStatement = @"insert into ftest values ('hello;');" 
          @"insert into ftest values ('hi;');" 
          @"insert into ftest values ('not h!\\\\');" 
          @"insert into ftest values ('definitely not h!')"; 
NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement]; 
[queue inDatabase:^(FMDatabase *adb) { 
    for (FMSplittedStatement *sqlittedStatement in statements) 
    { 
     [adb executeUpdate:sqlittedStatement.statementString]; 
    } 
}]; 
+0

Questo "extra" è stato sostituito con un wrapper nativo per 'sqlite3_exec'. Basta chiamare 'executeStatements'. – Rob

5

FMDB v2.3 ora ha un involucro nativo per sqlite3_exec chiamato executeStatements:

BOOL success; 

NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);" 
       "create table bulktest2 (id integer primary key autoincrement, y text);" 
       "create table bulktest3 (id integer primary key autoincrement, z text);" 
       "insert into bulktest1 (x) values ('XXX');" 
       "insert into bulktest2 (y) values ('YYY');" 
       "insert into bulktest3 (z) values ('ZZZ');"; 

success = [db executeStatements:sql]; 

Essa ha anche una variante che impiega il sqlite3_exec callback, implementato come un blocco:

sql = @"select count(*) as count from bulktest1;" 
     "select count(*) as count from bulktest2;" 
     "select count(*) as count from bulktest3;"; 

success = [db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) { 
    NSInteger count = [dictionary[@"count"] integerValue]; 
    NSLog(@"Count = %d", count); 
    return 0; // if you return 0, it continues execution; return non-zero, it stops execution 
}]; 
+0

Immagino che molte persone non si rendano conto che se si restituisce un valore diverso da zero, si fermerà. Inoltre, penso che sia meglio lasciare che il tuo blocco decida se vuoi fermarti o meno. Come suggerisco qui https://github.com/ccgus/fmdb/issues/428 – Qiulang

+0

immagino sia possibile che qualcuno non lo noti, ma è documentato nella guida rapida in Xcode per questo metodo. Non sono sicuro di cosa intendi con "è meglio lasciare decidere al tuo blocco se vuoi fermarlo o no", però: questo è lo scopo del 'return 0', dirgli di continuare. Se si restituisce un valore diverso da zero, si fermerà. – Rob