2011/01/25

too many SQL variables: , while compiling: ・・・

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 件のコメント :

コメントを投稿