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を投げつけてくるアプリケーションに問題があると思います。
0 件のコメント:
コメントを投稿