Как вставить UIImage как большой двоичный объект, используя sqlite3_exec в target-c

Я пытаюсь кэшировать некоторые изображения в sqlite как nsdata, и у меня возникает проблема, когда я пытаюсь вставить массив байтов, используя sqlite3_exec и необработанную строку SQL (как NSString)

NSData *imgData = UIImagePNGRepresentation(img);
NSString* sql = [NSString stringWithFormat:@"INSERT INTO persistedimg (imgx,idvalx) VALUES (%@,'%@')", imgData, idValue];
rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);

Но проблема с вышеизложенным заключается в том, что я добавляю NSData в строку sql напрямую вместо байтов.

Я хотел сделать что-то вроде этого

... [imgData bytes], [imgData length]

Но поскольку я не использую типичный подход, подобный "_bind_blob", я не уверен, как это сделать с необработанной строкой.

Обновить

Я использую оболочку, которую я хотел бы использовать, и просто пишу новый метод для поддержки команд вставки/запроса изображения.

ниже приведен весь мой класс-оболочка

**

#import "SQLiteAccess.h"
#import <sqlite3.h>

@implementation SQLiteAccess

+ (NSString *)pathToDB {
    NSString *dbName = @"test123";
  NSString *originalDBPath = [[NSBundle mainBundle] pathForResource:dbName ofType:@"db"];
  NSString *path = nil;
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *appSupportDir = [paths objectAtIndex:0];
  NSString *dbNameDir = [NSString stringWithFormat:@"%@/test123", appSupportDir];
  NSFileManager *fileManager = [NSFileManager defaultManager];
  BOOL isDir = NO;
  BOOL dirExists = [fileManager fileExistsAtPath:dbNameDir isDirectory:&isDir];
  NSString *dbPath = [NSString stringWithFormat:@"%@/%@.db", dbNameDir, dbName];
  if(dirExists && isDir) {
    BOOL dbExists = [fileManager fileExistsAtPath:dbPath];
    if(!dbExists) {
      NSError *error = nil;
      BOOL success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
      if(!success) {
        NSLog(@"error = %@", error);
      } else {
        path = dbPath;
      }
    } else {
      path = dbPath;
    }
  } else if(!dirExists) {
    NSError *error = nil;
    BOOL success =[fileManager createDirectoryAtPath:dbNameDir attributes:nil];
    if(!success) {
      NSLog(@"failed to create dir");
    }
    success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
    if(!success) {
      NSLog(@"error = %@", error);
    } else {
      path = dbPath;
    }
  }
  return path;
}

+ (NSNumber *)executeSQL:(NSString *)sql withCallback:(void *)callbackFunction context:(id)contextObject {
  NSString *path = [self pathToDB];
  sqlite3 *db = NULL;
  int rc = SQLITE_OK;
  NSInteger lastRowId = 0;
  rc = sqlite3_open([path UTF8String], &db);
  if(SQLITE_OK != rc) {
    NSLog(@"Can't open database: %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    return nil;
  } else {
    char *zErrMsg = NULL;
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);
    if(SQLITE_OK != rc) {
      NSLog(@"Can't run query '%@' error message: %s\n", sql, sqlite3_errmsg(db));
      sqlite3_free(zErrMsg);
    }
    lastRowId = sqlite3_last_insert_rowid(db);
    sqlite3_close(db);
    [pool release];
  }
  NSNumber *lastInsertRowId = nil;
  if(0 != lastRowId) {
    lastInsertRowId = [NSNumber numberWithInteger:lastRowId];
  }
  return lastInsertRowId;
}

static int singleRowCallback(void *queryValuesVP, int columnCount, char **values, char **columnNames) {
  NSMutableDictionary *queryValues = (NSMutableDictionary *)queryValuesVP;
  int i;
  for(i=0; i<columnCount; i++) {
    [queryValues setObject:values[i] ? [NSString stringWithFormat:@"%s",values[i]] : [NSNull null] 
                    forKey:[NSString stringWithFormat:@"%s", columnNames[i]]];
  }
  return 0;
}

+ (NSString *)selectOneValueSQL:(NSString *)sql {
    NSMutableDictionary *queryValues = [NSMutableDictionary dictionary];
    [self executeSQL:sql withCallback:singleRowCallback context:queryValues];
    NSString *value = nil;
    if([queryValues count] == 1) {
        value = [[queryValues objectEnumerator] nextObject];
    }
    return value;
}

+ (NSNumber *)insertWithSQL:(NSString *)sql {
    sql = [NSString stringWithFormat:@"BEGIN TRANSACTION; %@; COMMIT TRANSACTION;", sql];
    return [self executeSQL:sql withCallback:NULL context:NULL];
}

+ (void)updateWithSQL:(NSString *)sql {
  sql = [NSString stringWithFormat:@"BEGIN TRANSACTION; %@; COMMIT TRANSACTION;", sql];
  [self executeSQL:sql withCallback:NULL context:nil];
}

@end

**

Любая помощь с этим решением будет огромной!


person Toran Billups    schedule 17.03.2011    source источник
comment
Я очистил награду, но я думаю, что вы получите лучшие результаты, выполняя две награды в течение двух недель, если это необходимо, лично.   -  person Jeff Atwood    schedule 20.03.2011


Ответы (1)


Я думаю, что большая часть проблемы, с которой вы здесь сталкиваетесь, заключается в том, что вы слишком сильно пытаетесь упростить API-интерфейсы SQLite3. API предназначены не только для выполнения текстовых SQL-запросов; подготовленные операторы и параметры связывания существуют не просто так. Вы не должны пытаться вставить двоичные данные в строку. Это просто вызывает проблемы, особенно если в ваших двоичных данных есть нули.

Чтобы вставить большие двоичные объекты, вам действительно нужно использовать sqlite3_bind_blob с sqlite3_prepare_v2. Когда вы привязываете большой двоичный объект, вам также нужно будет использовать [imgData bytes] в качестве данных большого двоичного объекта.

Возможно, вы ищете помощь в реконструкции вашего API, чтобы упростить подобные вещи для этого конкретного случая использования кэширования изображений?

Изменить

Вот простой пример использования bind для вставки двоичных данных. Предположим, что имеется таблица с именем my_table с двумя столбцами: name типа VARCHAR и data типа BLOB. Обратите внимание, что я не тестировал и даже не пытался компилировать это, поэтому могут быть опечатки или ошибки.

sqlite3 *database;

// Open a connection to the database given its file path.
if (sqlite3_open("/path/to/sqlite/database.sqlite3", &database) != SQLITE_OK) {
    // error handling...
}

// Construct the query and empty prepared statement.
const char *sql = "INSERT INTO `my_table` (`name`, `data`) VALUES (?, ?)";
sqlite3_stmt *statement;

// Prepare the data to bind.
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"something"]);
NSString *nameParam = @"Some name";

// Prepare the statement.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
    // Bind the parameters (note that these use a 1-based index, not 0).
    sqlite3_bind_text(statement, 1, nameParam);
    sqlite3_bind_blob(statement, 2, [imageData bytes], [imageData length], SQLITE_STATIC);
    // SQLITE_STATIC tells SQLite that it doesn't have to worry about freeing the binary data.
}

// Execute the statement.
if (sqlite3_step(statement) != SQLITE_DONE) {
    // error handling...
}

// Clean up and delete the resources used by the prepared statement.
sqlite3_finalize(statement);

// Now let's try to query! Just select the data column.
const char *selectSql = "SELECT `data` FROM `my_table` WHERE `name` = ?";
sqlite3_stmt *selectStatement;

if (sqlite3_prepare_v2(database, selectSql, -1, &selectStatement, NULL) == SQLITE_OK) {
    // Bind the name parameter.
    sqlite3_bind_text(selectStatement, 1, nameParam);
}

// Execute the statement and iterate over all the resulting rows.
while (sqlite3_step(selectStatement) == SQLITE_ROW) {
    // We got a row back. Let's extract that BLOB.
    // Notice the columns have 0-based indices here.
    const void *blobBytes = sqlite3_column_blob(selectStatement, 0);
    int blobBytesLength = sqlite3_column_bytes(selectStatement, 0); // Count the number of bytes in the BLOB.
    NSData *blobData = [NSData dataWithBytes:blobBytes length:blobBytesLength];
    NSLog("Here's that data!\n%@", blobData);
}

// Clean up the select statement
sqlite3_finalize(selectStatement);

// Close the connection to the database.
sqlite3_close(database);
person Marc W    schedule 19.03.2011
comment
Также уже есть несколько хороших оболочек для SQLite, которые вы могли бы использовать вместо создания собственных. Проверьте этот вопрос: stackoverflow.com/questions/640885/ - person Jay Peyer; 19.03.2011
comment
Я полагаю, что ищу способ объединить этот подход (привязать) с тем, что у меня уже есть. Не могли бы вы привести простой пример, показывающий вставку и запрос от начала до конца? Я начал следовать этому руководству, но столкнулся с проблемами, которые авторы src iphonesdkarticles.com/2009/02/ - person Toran Billups; 19.03.2011
comment
Без проблем! Надеюсь, поможет. Дайте мне знать, если у вас возникнут какие-либо проблемы с ним. Это объединение нескольких частей моего старого производственного кода, но я не тестировал его в таком виде. - person Marc W; 19.03.2011