これまではクエリ1つ投げるのに複数のAPIを用いてきたが、これはやはり面倒だ。そこで、一連のAPI呼び出しをまとめてやってくれるsqlite3_exec()の出番となるわけだが、ただ使ってみるだけでは芸がないので、今回は趣向を変えてその実装を見てみよう。以下、本家tarball内のsqlite.cより引用。
SQLITE_API int sqlite3_exec(
sqlite3 *db, /* The database on which the SQL executes */
const char *zSql, /* The SQL to be executed */
sqlite3_callback xCallback, /* Invoke this callback routine */
void *pArg, /* First argument to xCallback() */
char **pzErrMsg /* Write error messages here */
){
int rc = SQLITE_OK; /* Return code */
const char *zLeftover; /* Tail of unprocessed SQL */
sqlite3_stmt *pStmt = 0; /* The current SQL statement */
char **azCols = 0; /* Names of result columns */
int nRetry = 0; /* Number of retry attempts */
int callbackIsInit; /* True if callback data is initialized */
if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT;
if( zSql==0 ) zSql = "";
sqlite3_mutex_enter(db->mutex);
sqlite3Error(db, SQLITE_OK, 0);
while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] ){
int nCol;
char **azVals = 0;
pStmt = 0;
rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover);
assert( rc==SQLITE_OK || pStmt==0 );
if( rc!=SQLITE_OK ){
continue;
}
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = zLeftover;
continue;
}
callbackIsInit = 0;
nCol = sqlite3_column_count(pStmt);
while( 1 ){
int i;
rc = sqlite3_step(pStmt);
/* Invoke the callback function if required */
if( xCallback && (SQLITE_ROW==rc ||
(SQLITE_DONE==rc && !callbackIsInit
&& db->flags&SQLITE_NullCallback)) ){
if( !callbackIsInit ){
azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1);
if( azCols==0 ){
goto exec_out;
}
for(i=0; i<nCol; i++){
azCols[i] = (char *)sqlite3_column_name(pStmt, i);
/* sqlite3VdbeSetColName() installs column names as UTF8
** strings so there is no way for sqlite3_column_name() to fail. */
assert( azCols[i]!=0 );
}
callbackIsInit = 1;
}
if( rc==SQLITE_ROW ){
azVals = &azCols[nCol];
for(i=0; i<nCol; i++){
azVals[i] = (char *)sqlite3_column_text(pStmt, i);
if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
db->mallocFailed = 1;
goto exec_out;
}
}
}
if( xCallback(pArg, nCol, azVals, azCols) ){
rc = SQLITE_ABORT;
sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
sqlite3Error(db, SQLITE_ABORT, 0);
goto exec_out;
}
}
if( rc!=SQLITE_ROW ){
rc = sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
if( rc!=SQLITE_SCHEMA ){
nRetry = 0;
zSql = zLeftover;
while( sqlite3Isspace(zSql[0]) ) zSql++;
}
break;
}
}
sqlite3DbFree(db, azCols);
azCols = 0;
}
exec_out:
if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt);
sqlite3DbFree(db, azCols);
rc = sqlite3ApiExit(db, rc);
if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){
int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db));
*pzErrMsg = sqlite3Malloc(nErrMsg);
if( *pzErrMsg ){
memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg);
}else{
rc = SQLITE_NOMEM;
sqlite3Error(db, SQLITE_NOMEM, 0);
}
}else if( pzErrMsg ){
*pzErrMsg = 0;
}
assert( (rc&db->errMask)==rc );
sqlite3_mutex_leave(db->mutex);
return rc;
}
上から眺めると、まず目に付くのが排他処理。sqlite3_mutex_enter(), sqlite3_mutex_leave()を用いているのが分かる。次に、最も外側のループの中では、引数として与えられたSQL文を逐一実行する。セミコロンで区切った複数のSQL文も、エラーが検出されない限り最後まで実行されるわけだ。
コールバックも指定できる。…と言うか、クエリによってはこれを使わないとどうにもならない。何も返さないクエリは完了時に1回コールバックが呼ばれるだけなので、無理にコールバックを使わなくてもどうにかなるだろう。一方、表を返すクエリの場合、1行毎に取得した表の内容を引数にしてコールバックが呼び出されるので、基本的に所望の処理はコールバックの中で行う。メモリの解放は気にしなくても良い一方で、コールバックの外でも取得した表の内容を参照したければ、コールバックの中でどこかしら別の領域にコピーして取っておく必要がある。また、表を作成したときに指定した型に関わらず、カラムの内容はTEXT型として取得される。
エラーを検出した場合、エラーメッセージを返すことができるが、このエラーメッセージの領域はsqlite3Malloc()で確保したものである。したがって、エラーメッセージを使い終えたらsqlite3_free()で明示的に解放してやらなければならない。
0 件のコメント:
コメントを投稿