スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。



MIDP -> Android その8 SqliteでのBLOBの扱い

昨日検証していて問題になったことがありました。
ContentValuesを使用したupdate処理ではSQL関数が使用できない。
これはContentValuesのputでSQL関数を使用しても文字列として扱われるためです。

調べてみると,その回避策としては以下のことがあがっていました。

1.フィールドにDEFAULT値を設定して回避する。
2.生のSQL文で実行する。(execSQL()を使う)

sqliteの仕様でDEFAULTにはSQL関数が使用できないので,
今回のようなjuilanday関数を使用したデフォルト値は設定できません。
(sqliteのバージョンアップによって使用できるようになるかもしれません)

ということで残りは2.の生のSQL文を書くという方法です。
ではBLOBデータはどうやって展開すればいいのか?

とりあえずAndroidのsqliteクラスはどうやって扱っているか調べてみました。

結局nativeメソッドで処理されていてJavaコードからはわかりませんでした。
androidのコード解析はnative側もみないとだめですね〜><

とりあえず,展開している部分のコードポインタをあげときます。

android/database/sqlite/SQLiteConnection.java

	private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
        final int count = bindArgs != null ? bindArgs.length : 0;
        if (count != statement.mNumParameters) {
            throw new SQLiteBindOrColumnIndexOutOfRangeException(
                    "Expected " + statement.mNumParameters + " bind arguments but "
                    + bindArgs.length + " were provided.");
        }
        if (count == 0) {
            return;
        }

        final int statementPtr = statement.mStatementPtr;
        for (int i = 0; i < count; i++) {
            final Object arg = bindArgs[i];
            switch (DatabaseUtils.getTypeOfObject(arg)) {
                case Cursor.FIELD_TYPE_NULL:
                    nativeBindNull(mConnectionPtr, statementPtr, i + 1);
                    break;
                case Cursor.FIELD_TYPE_INTEGER:
                    nativeBindLong(mConnectionPtr, statementPtr, i + 1,
                            ((Number)arg).longValue());
                    break;
                case Cursor.FIELD_TYPE_FLOAT:
                    nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
                            ((Number)arg).doubleValue());
                    break;
                case Cursor.FIELD_TYPE_BLOB:
                    nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
                    break;
                case Cursor.FIELD_TYPE_STRING:
                default:
                    if (arg instanceof Boolean) {
                        // Provide compatibility with legacy applications which may pass
                        // Boolean values in bind args.
                        nativeBindLong(mConnectionPtr, statementPtr, i + 1,
                                ((Boolean)arg).booleanValue() ? 1 : 0);
                    } else {
                        nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
                    }
                    break;
            }
        }
    }

    private static native void nativeBindBlob(int connectionPtr, int statementPtr,
            int index, byte[] value);

というころで生のSQL文でBLOBデータを設定する方法を試してみました。
BLOBの設定方法にもいくつかあるようですが,
今回は「x''」という形式を使用してみました。

package com.example.sqliteblobtest;

import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;

/**
 * BLOBデータの扱いをテストするためのクラス
 */
public class SqliteBlobTest extends Activity implements Runnable
{
	static final private String DB_NAME = "test.db";
	
	static final private String BLOB_DATA_STRING = "BLOBテスト用に使用する文字列";
	
	private BlobTestOpenHelper openHelper;
	private Thread thread;
	private byte[] BLOB_DATA;

	@Override public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_sqlite_blob_test);

		try
		{
			BLOB_DATA = BLOB_DATA_STRING.getBytes("UTF-8");
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "onCreate() Caught exception.", ex);
			BLOB_DATA = null;
			return;
		}

		openHelper = new BlobTestOpenHelper(getApplicationContext(), DB_NAME);
		
		thread = new Thread(this);
		thread.start();
	}

	/*
	 * (非 Javadoc)
	 * 
	 * @see android.app.Activity#onPause()
	 */
	@Override protected void onPause()
	{
		try
		{
			thread.join();
			thread = null;
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "onPause() Caught exception.", ex);
		}
		
		super.onPause();
	}

	/**
	 * BLOBデータのテスト用データベースクラス 
	 */
	static private class BlobTestOpenHelper extends SQLiteOpenHelper
	{
		public BlobTestOpenHelper(Context context, String dbName)
		{
			super(context, DB_NAME, null, 1);
		}

		@Override public void onCreate(SQLiteDatabase db)
		{
			db.execSQL("CREATE TABLE savedata1(" + 
		                      "id INTEGER PRIMARY KEY," + 
					          "created," + 
		                      "updated," + 
					          "data BLOB)");

			db.execSQL("CREATE TABLE savedata2(" + 
                              "id INTEGER PRIMARY KEY," + 
			                  "created REAL NOT NULL," + 
//                              "updated REAL NOT NULL DEFAULT julianday(CURRENT_TIMESTAMP,'localtime')," + //これだとエラーになる 
                              "updated REAL NOT NULL DEFAULT CURRENT_TIMESTAMP," + // DEFAULTではSQLの関数が使用できないのでこちらは成功する。 
			                  "data BLOB)");

			db.beginTransaction();

			try
			{
				for(int i = 0; i < 10; i++)
					db.execSQL("insert into savedata1(id, created, updated) values(null, julianday(CURRENT_TIMESTAMP,'localtime'), julianday(CURRENT_TIMESTAMP,'localtime'));");

				db.setTransactionSuccessful();
			}
			catch(Exception ex)
			{
				Log.e(getClass().getSimpleName(), "");
			}
			finally
			{
				db.endTransaction();
			}
		}

		@Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
		{
		}
	}

	@Override public void run()
	{
		SQLiteDatabase db = openHelper.getWritableDatabase();

		db.beginTransaction();
		
		try
		{
			{
				ContentValues values = new ContentValues();

				// この方法ではupdatedの値が文字列として更新されてしまう
				values.put("updated", "julianday(CURRENT_TIMESTAMP,'localtime')");
				values.put("data", BLOB_DATA);
				
				db.update("savedata1", values, "id=2", null);
			}
			
			// 生のSQL文を作る
			{
				StringBuilder sb = new StringBuilder();
				sb.append("UPDATE savedata1 SET updated=julianday(CURRENT_TIMESTAMP,'localtime'), data=");
				
				sb.append("x'");
				
				for(byte b : BLOB_DATA)
					sb.append(String.format("%02x", b));

				sb.append("'");
				sb.append(" WHERE id=3;");
				
				String sql = sb.toString();
				db.execSQL(sql);
				
				Log.d(getClass().getSimpleName(), "SQL -> " + sql);
			}
			
			db.setTransactionSuccessful();
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "run() Caught exception.", ex);
		}
		finally
		{
			db.endTransaction();
		}

		//----------------
		// 結果の読み込み
		//----------------
		
		db = openHelper.getReadableDatabase();
		
		Cursor c = null;
		
		try
		{
			String[] columns = {"id", "datetime(created)", "datetime(updated)", "updated", "data"};
			c = db.query("savedata1", columns, null, null, null, null, "id ASC");
			
			final int count = c.getCount();
			
			c.moveToFirst();
			
			for(int n=0; n < count; n++)
			{
				final int id = c.getInt(0);
				final String created = c.getString(1);
				final String updated = c.getString(2);
				final String rawUpdated = c.getString(3);
				final byte[] data = c.getBlob(4);
				
				String dataString = (data!=null) ? new String(data, "UTF-8") : "";
				
				Log.i(getClass().getSimpleName(), "[" + n + "] id:" + id + ", created:" + created + ", updated:" + updated + "(" + rawUpdated + "), data:" + dataString);
				
				if(!c.moveToNext())
					break;
			}
		}
		catch(Exception ex)
		{
			Log.e(getClass().getSimpleName(), "run() Caught exception while reading db.", ex);
		}
		finally
		{
			if(c!=null)
				c.close();
		}
	}
}

// 結果
SQL -> UPDATE savedata1 SET updated=julianday(CURRENT_TIMESTAMP,'localtime'), data=x'424c4f42e38386e382b9e38388e794a8e381abe4bdbfe794a8e38199e3828be69687e5ad97e58897' WHERE id=3;
[0] id:1, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[1] id:2, created:2012-09-05 01:31:04, updated:null(julianday(CURRENT_TIMESTAMP,'localtime')), data:BLOBテスト用に使用する文字列
[2] id:3, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:05(2.45618e+06), data:BLOBテスト用に使用する文字列
[3] id:4, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[4] id:5, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[5] id:6, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[6] id:7, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[7] id:8, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[8] id:9, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
[9] id:10, created:2012-09-05 01:31:04, updated:2012-09-05 01:31:04(2.45618e+06), data:
関連記事
スポンサーサイト

テーマ : ゲーム開発
ジャンル : コンピュータ




コメントの投稿

非公開コメント

アクセス
あなたは
キーワード
カテゴリー
最近の記事
リンク
月別アーカイブ
ブロとも申請フォーム

この人とブロともになる

WEB検索
Google

RSSフィード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。