too many SQL variables: , while compiling: DELETE FROM sample_tbl WHERE rowid IN (?,?,?,...
そもそもこんなエラーには滅多にブチ当たらないだろうし、もしブチ当たったならアプリケーションの設計から見直したほうが良いと思います。はい。
最初このエラーをLogCatで見たときはandroidの問題かなと思いましたが、結論はsqlite3の制限でしたというお話。
例えば、以下のメソッドをアプリケーション側から使うとき。
package android.database.sqlite;
public class SQLiteDatabase extends SQLiteClosable {
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
// (中略)
}
}
第一引数sqlに下記のような"?"プレースフォルダが1000個あるSQL文を、
DELETE FROM sample_tbl WHERE rowid IN (?,?,?,...延々1000個...,?);
第二引数に"?"プレースフォルダにバインドしてもらうパラメータを1000個渡して実行します。JDBCのPreparedStatementを使うときのやり方と同じですね。
するとLogCatには以下のようなログが吐かれて、SQLは失敗してしまいます。
ERROR/hoge.fuga.SampleDao(123): too many SQL variables: , while compiling: DELETE FROM sample_tbl WHERE rowid IN (?,?,?,........
androidのソースを辿っていくと、
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
// (中略)
DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]);
}
↓
package android.database;
public class DatabaseUtils {
public static void bindObjectToProgram(SQLiteProgram prog, int index,
Object value) {
if (value == null) {
prog.bindNull(index);
} else if (value instanceof Double || value instanceof Float) {
prog.bindDouble(index, ((Number)value).doubleValue());
} else if (value instanceof Number) {
prog.bindLong(index, ((Number)value).longValue());
} else if (value instanceof Boolean) {
Boolean bool = (Boolean)value;
if (bool) {
prog.bindLong(index, 1);
} else {
prog.bindLong(index, 0);
}
} else if (value instanceof byte[]){
prog.bindBlob(index, (byte[]) value);
} else {
prog.bindString(index, value.toString());
}
}
}
prog.bindString(int, String)に行ってみます↓
package android.database.sqlite;
public abstract class SQLiteProgram extends SQLiteClosable {
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_string(index, value);
} finally {
releaseReference();
}
}
}
native_bind_string(int, String)にジャンプ↓
package android.database.sqlite;
public abstract class SQLiteProgram extends SQLiteClosable {
protected final native void native_bind_string(int index, String value);
}
frameworks/base/core/jni/android_database_SQLiteProgram.cppまで行き、
static void native_bind_string(JNIEnv* env, jobject object,
jint index, jstring sqlString)
{
int err;
jchar const * sql;
jsize sqlLen;
sqlite3_stmt * statement= GET_STATEMENT(env, object);
sql = env->GetStringChars(sqlString, NULL);
sqlLen = env->GetStringLength(sqlString);
err = sqlite3_bind_text16(statement, index, sql, sqlLen * 2, SQLITE_TRANSIENT);
env->ReleaseStringChars(sqlString, sql);
if (err != SQLITE_OK) {
char buf[32];
sprintf(buf, "handle %p", statement);
throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
return;
}
}
sqlite3_bind_XXX()関数が「too many SQL variables」と言っているようです。
要はsqlite3としては以下のような制限がありますよと。
http://www.sqlite.org/limits.html#max_variable_number
9.Maximum Number Of Host Parameters In A Single SQL Statement
...(中略)...
the maximum value of a host parameter number is SQLITE_MAX_VARIABLE_NUMBER, which defaults to 999.
SQL文のIN句に渡せる値の数とかWHERE句の条件の数の問題ではなく、sqlite3に渡せるバンドパラメータの上限がデフォルト999個までということ。それが証拠にパラメータを渡す方ではなく、SQL文だけを引数に取る以下のメソッドの第一引数に、IN句の条件を文字列として連結して渡すと失敗しません。
package android.database.sqlite;
public class SQLiteDatabase extends SQLiteClosable {
public void execSQL(String sql) throws SQLException {
// (中略)
}
}
パラメータが1000個もあるSQLを投げつけてくるアプリケーションに問題があると思います。