2010/06/28

[objective-c][iPad][sqlite]INSERT文の発行その2

前回は、バインド変数を使わずに、INSERT文を実行する方法について書きました。

今日は、長い試行のすえできた、バインド変数を使ったトランザクション処理について書いてみたいと思います。

// TestTable.h

#import <Foundation/Foundation.h>
#import <sqlite3.h>

@interface TestTable : NSObject {
sqlite3 *db;
NSString *dbPath;
}

@property sqlite3 *db;
@property (retain,nonatomic) NSString *dbPath;

-(id)init;
-(BOOL)openDataBase;
-(BOOL)createTable;
-(BOOL)insertData;
-(void)closeDataBase;
-(void)errDataBase;
@end

// TestTable.m
#import "TestTable.h"

@implementation TestTable

@synthesize db;
@synthesize dbPath;

-(id)init{
self = [super init];

//dbが存在しているかどうかの確認
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
dbPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"test.db"];

//ファイルが存在しない場合
if (![[NSFileManager defaultManager] fileExistsAtPath:dbPath]) {
//ファイルを作成する
BOOL result = [[NSFileManager defaultManager] createFileAtPath:dbPath contents:nil attributes:nil];
//ファイル作成が失敗した場合
if (!result) {
}
//BOOL sql_exec_ok = NO;
//sqliteをオープンする
if([self openDataBase]){
//テーブルの作成
[self createTable];

//データを登録
[self insertData];

//テーブルのクローズ
[self closeDataBase];
}
}
return self;
}

//sqliteをオープンする
-(BOOL)openDataBase{
int ret;
ret = sqlite3_open([dbPath UTF8String],&db);
//正常終了
if(ret == SQLITE_OK){
return YES;
//異常終了
}else {
//エラーが発生してしまったので、クローズを行う
sqlite3_close(db);
return NO;
}
}

//tableの作成
-(BOOL)createTable{
//sqlの設定
char *sql = "CREATE TABLE test(column_no1,column_no2)";
//各関数の戻り値
int ret;
//sql文を実行するための変数
sqlite3_stmt *sqlstmt;

ret = sqlite3_prepare_v2(db,sql,-1,&sqlstmt,NULL);
//実行準備おk
if(ret == SQLITE_OK){
//sqlの実行を行う
ret = sqlite3_step(sqlstmt);
//sql文の解放
sqlite3_finalize(sqlstmt);

//sqlの実行が正常終了した場合
if(ret == SQLITE_DONE){
return YES;
}
}

//エラーメセッドをコール
[self errDataBase];
//dbクローズ
[self closeDataBase];
return NO;

}

-(BOOL)insertData{

NSArray *tmpArray = [[NSArray alloc] initWithObjects:@"test_01"
, @"test_02"
, @"test_03"
, @"test_04"
, nil
];

BOOL q_Ret = NO;

//各関数の戻り値
int ret;

//トランザクションの開始
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);

//insert文の作成
sqlite3_stmt* sqlstmt;
char *sql = "insert into test(column_no1,column_no2)values(?,?)";
ret = sqlite3_prepare_v2(db, sql, -1, &sqlstmt, NULL);
//構文解析の結果問題なし(バインド前)
if (ret == SQLITE_OK) {
for (NSUInteger tmp=1; tmp<3; tmp++) {
//sqlをリセット
sqlite3_reset(sqlstmt);
//バインド変数をクリアー
sqlite3_clear_bindings(sqlstmt);
sqlite3_bind_text(sqlstmt,1,[[tmpArray objectAtIndex:(tmp-1)] UTF8String],-1,SQLITE_TRANSIENT);
sqlite3_bind_text(sqlstmt,2,[[tmpArray objectAtIndex:tmp] UTF8String],-1,SQLITE_TRANSIENT);
//sql文を実行
ret = sqlite3_step(sqlstmt);

//一回でもエラーが発生した場合はクローズさせて終了
if(ret != SQLITE_DONE){
//sql文の解放
sqlite3_finalize(sqlstmt);
//異常終了(ROLLBACKして処理を終了)
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
//エラーメソッドをコール
[self errDataBase];
//dbをクローズ
[self closeDataBase];
return q_Ret;
}
}
//正常終了(COMMITをして処理を終了)
sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL);
q_Ret = YES;
NSLog(@"OKです");
//構文解析の結果問題あり(バインド前)
}else {
//エラーメソッドをコール
[self errDataBase];
//異常終了(ROLLBACKして処理を終了)
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
}

//sql文の解放
sqlite3_finalize(sqlstmt);

//クローズ
[self closeDataBase];
return q_Ret;
}

//sqliteをクローズする
-(void)closeDataBase{
sqlite3_close(db);
}

//sqliteのエラー処理
-(void)errDataBase{

UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:[[NSString alloc] initWithFormat:@"error:%d",sqlite3_errcode(db)]
message:[[NSString alloc] initWithUTF8String:sqlite3_errmsg(db)]
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil
];
[alertView show];
[alertView release];

}

@end

バインドの方法は、SQLiteで“おこづかいちょう”の部分を読みながら、NSArrayに設定したNSStringをchar型に変換させて、それを、sqlite3_bind_text関数を使ってバインドさせてます。

同関数の第四引数には、SQLITE_TRANSIENTという定数があるが、これは、Binding Values To Prepared Statementsを読んで、SQLITE_STATICかSQLITE_TRANSIENTという選択肢があり、SQLITE_STATICは性的な場合のみ使えと書いてあったので、今回、SQLITE_TRANSIENTを採用しました。

ここからちょっと気になったのだが、SQLiteで“おこづかいちょう”に記載されていたコードには、sql文を解放させるsqlite3_reset関数が使われていたが、バインド変数をリセットするsqlite3_clear_bindingsが使われていなかった。

確かに、sqlite3_clear_bindings関数を使わずに実行をした結果、きちんと登録されているが、Reset A Prepared Statement Objectを読んでみると、

Any SQL statement variables that had values bound to them using the sqlite3_bind_*() API retain their values. Use sqlite3_clear_bindings() to reset the bindings.

via:Reset A Prepared Statement Object

と書いてあるので、やはり、sqlite3_clear_bindings使わないといけないんじゃーないのかなーっと疑問に思いつつ。

やっとこれでバインド変数を使った動的SQL文を作成することができました。

ただ、やはり、前回から指摘しているが、各メソッドに、カラム名及び、テーブル名を直指定してしまっている。

これを、NSStringなどの変数に設定して、動的に、テーブルを作成したり、ドロップしたりすることはできないのだろうか?
そうすれば、もっとプログラム全体が汎用的になるはず。

実は、その方法も発見したのですが、今回は、バインド変数を使った登録までなので、次回に譲りたい。

0 コメント:

コメントを投稿