SQLiteDatabase

メシ作成後に掘削してみた記録。
先日、ContentResolver 使う方式で書き換えた Notepad の SQLiteDatabase#query 呼び出す箇所が以下。

    @Override
    public Cursor query(
            Uri url, String[] projection, String selection, String[] selectionArgs,
            String sort) {
        Cursor c = db.query(true, NotesColumns.NOTES_TABLE, 
                new String[] {NotesColumns._ID, NotesColumns.KEY_TITLE,
                NotesColumns.KEY_BODY}, selection, null,
                null, null, null, null);
        c.setNotificationUri(getContext().getContentResolver(), url);
        return c;
    }

SQLiteDatabase の実装を見てみるか、という事で frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java の中身を確認。query メソドの定義は以下。

    public Cursor query(boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit) {
        return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
                groupBy, having, orderBy, limit);
    }

queryWithFactory は以下。

    public Cursor queryWithFactory(CursorFactory cursorFactory,
            boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit) {
        String sql = SQLiteQueryBuilder.buildQueryString(
                distinct, table, columns, selection, groupBy, having, orderBy, limit);

        return rawQueryWithFactory(
                cursorFactory, sql, selectionArgs, findEditTable(table));
    }

ええと、rowQueryWithFactory メソドは以下か。

    public Cursor rawQueryWithFactory(
            CursorFactory cursorFactory, String sql, String[] selectionArgs,
            String editTable) {
        long timeStart = 0;

        if (Config.LOGV) {
            timeStart = System.currentTimeMillis();
        }

        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);

        try {
            return driver.query(
                    cursorFactory != null ? cursorFactory : mFactory,
                    selectionArgs);
        } finally {
            if (Config.LOGV) {
                long duration = System.currentTimeMillis() - timeStart;

                Log.v(SQLiteCursor.TAG,
                      "query (" + duration + " ms): " + driver.toString() + ", args are "
                              + (selectionArgs != null
                              ? TextUtils.join(",", selectionArgs)
                              : "<null>"));
            }
        }
    }

ええと、SQLiteDirectCursorDriver クラスのオブジェクトをナニして query メソドを呼び出しております。SQLiteDirectCursorDriver.java は同じディレクトリにある模様。query メソドが以下か。

    public Cursor query(CursorFactory factory, String[] selectionArgs) {
        // Compile the query
        SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);

        try {
            // Arg binding
            int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
            for (int i = 0; i < numArgs; i++) {
                query.bindString(i + 1, selectionArgs[i]);
            }

            // Create the cursor
            if (factory == null) {
                mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
            } else {
                mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
            }

            mQuery = query;
            query = null;
            return mCursor;
        } finally {
            // Make sure this object is cleaned up if something happens
            if (query != null) query.close();
        }
    }

ここで SQLiteCursor が出てきます。ちなみに query の引数 factory は SQLiteDatabase から延々と渡されているもので rawQueryWithFactory メソドで

            return driver.query(
                    cursorFactory != null ? cursorFactory : mFactory,
                    selectionArgs);

なカンジのナニがあります。cursorFactory は null のはずなので mFactory が渡されるはずなんですが、mFactory 属性はコンストラクタで設定されてます。

    private SQLiteDatabase(String path, CursorFactory factory, int flags) {
        if (path == null) {
            throw new IllegalArgumentException("path should not be null");
        }
        mFlags = flags;
        mPath = path;
        mLogStats = "1".equals(android.os.SystemProperties.get("db.logstats"));
        
        mLeakedException = new IllegalStateException(path +
            " SQLiteDatabase created and never closed");
        mFactory = factory;

あら? コンストラクタが private だな。コメントによれば create と openDatabase を見れ、とある模様。create メソドの定義が以下。

    public static SQLiteDatabase create(CursorFactory factory) {
        // This is a magic string with special meaning for SQLite.
        return openDatabase(":memory:", factory, CREATE_IF_NECESSARY);
    }

openDatabase を呼び出してますな。openDatabase メソドの定義が以下。

    public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
        SQLiteDatabase db = null;
        try {
            // Open the database.
            return new SQLiteDatabase(path, factory, flags);
        } catch (SQLiteDatabaseCorruptException e) {
            // Try to recover from this, if we can.
            // TODO: should we do this for other open failures?
            Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
            new File(path).delete();
            return new SQLiteDatabase(path, factory, flags);
        }
    }

うーん。Notepad を再確認してみたところ、SQLiteDatabase なオブジェクトは

        DatabaseHelper dbHelper = new DatabaseHelper(getContext());
        db = dbHelper.getWritableDatabase();

という方法で取得しておる模様。DatabaseHelper って何だ。って内部定義なソレですた。

    private static class DatabaseHelper extends SQLiteOpenHelper {

って事は SQLiteOpenHelper 見てみれば良いのか。frameworks/base/core/java/android/database/sqlite/SQLiteOpenHelper.java なんですが、特に factory 云々は気にしないことにしよ。

結局

SQLiteCursor に戻ってきました。明日以降ここにフォーカスして頑張ります。