2011-11-10 22 views
7

Estoy usando FMDB para crear una base de datos SQLite en iPhone. Tengo un initial.sql que es de la formaMúltiples consultas que no se ejecutan en FMDB

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

me carga esta cargando el archivo en un NSString y ejecutarlo

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

[db executeUpdate: str]; 

Esto tiene éxito, pero más tarde me sale un fracaso:

no such table: Def 

Está claro que no se está llamando a la segunda instrucción. ¿Cómo puedo hacer esto para que se llamen todas las consultas?

De acuerdo con la documentación de SQLite: "Las rutinas sqlite3_prepare_v2(), sqlite3_prepare(), sqlite3_prepare16(), sqlite3_prepare16_v2(), sqlite3_exec(), y sqlite3_get_table() aceptar una lista de sentencias SQL (SQL-prop-lista) que es una lista de declaraciones separadas por punto y coma ".

Por lo tanto, esto debería funcionar.

+0

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

Respuesta

8

Fui mordido por este también; Me llevó toda una mañana recorrer FMDatabase y leer la documentación de la API sqlite3 para encontrarla. Todavía no estoy del todo seguro sobre la causa raíz del problema, pero de acuerdo con this bug in PHP, es necesario llamar al sqlite3_exec en lugar de preparar la declaración con sqlite3_prepare_v2 y luego llamar a sqlite3_step.

La documentación no parece sugerir que este comportamiento suceda, de ahí nuestra confusión, y me gustaría que alguien con más experiencia con sqlite presente algunas hipótesis.

Lo resolví desarrollando un método para ejecutar un lote de consultas. Por favor encuentre el código a continuación. Si lo prefiere, puede volver a escribir esto en una categoría en lugar de solo agregarlo a FMDatabase.h, su llamada.

Añadir esto a la interfaz FMDatabase en FMDatabase.h:

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

añadir esto a la implementación FMDatabase en 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; 
} 

Tenga en cuenta que hay muchas características que faltan en executeBatch que lo hacen inadecuado para muchos propósitos. Específicamente, no verifica si la base de datos está bloqueada, no se asegura de que FMDatabase en sí no esté bloqueada, no admite el caché de sentencias.

Si lo necesita, lo anterior es un buen punto de partida para codificarlo usted mismo. ¡Feliz hacking!

+0

De hecho, terminé simplemente asegurándome de que mis consultas por lotes estaban en diferentes líneas y divididas por la n personaje ewline.Luego lo rodeé con BEGIN/COMMIT para convertirlo en una sola transacción. – 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

Este "extra" ha sido reemplazado con un contenedor nativo a 'sqlite3_exec'. Simplemente llame a 'executeStatements'. – Rob

5

FMDB v2.3 ahora tiene una capa nativo para sqlite3_exec llamado 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]; 

También tiene una variante que emplea la devolución de llamada sqlite3_exec, implementado como un bloque:

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

Supongo que la mayoría de la gente no se da cuenta de que si devuelve un valor distinto de cero, se detendrá. Además, creo que es mejor dejar que tu bloque decida si quieres parar o no. Como sugiero aquí https://github.com/ccgus/fmdb/issues/428 – Qiulang

+0

Supongo que es posible que alguien no se dé cuenta, pero está documentado en la ayuda rápida en Xcode para este método. No estoy seguro de lo que quiere decir con "es mejor dejar que su bloque decida si quiere parar o no", sin embargo: ese es el propósito del 'return 0', para decirle que continúe. Si devuelve un valor distinto de cero, se detendrá. – Rob

Cuestiones relacionadas