Adobe AIR の SQL 機能を試してみるためにデモアプリを作ってみた

AIR には Flex(MXML/ActionScript) と Ajax(HTML/JavaScript) の二つの作成方法があるが、今回は後者を選んだ。

  1. http://help.adobe.com/ja_JP/AIR/1.5/devappshtml/
  2. http://www.adobe.com/jp/devnet/air/ajax/

開発環境は Aptana Studio を使った。
デモアプリはテキストエリアに SQL 文を書いて実行すると、結果が表示されるというもの。SELECT であればテーブルに結果が表示される。テーブルの表示は、Adobe だけに Spry のデータセット機能を使ってみることにした。

Aptana Studio でプロジェクトを作成

  1. File -> New.. -> Project -> Adobe AIR Project を選択する。
  2. プロジェクトに「AirSQLiteTest」を設定する。
  3. Import JavaScript Library までそのまま [Next] をクリック。
  4. Adobe Spry 1.6 にチェックを入れて [Finish] をクリック。

ソースコード

AirSQLiteTest.html が AIR 本体を構成する。ソースコードは以下。

<html>
<head>
<title>AIR SQL Test</title>
<script type="text/javascript" src="lib/air/AIRAliases.js"></script>
<script type="text/javascript" src="lib/spry/includes/SpryData.js"></script>
<style type="text/css">
div { margin:16px; }
#textSql {
 width:100%;
 height:120px;
}
#textResult {
 width:100%;
 height:60px;
}
p { margin:4px 0; }
table th,
table td { border:1px solid gray; }
th, td { padding:8px }
th { cursor: default; }
</style>
<script type="text/javascript">
/**
 * AIR SQL を操作する DBClient
 * 
 * @param {String} dbname データベース名
 * @param {Function} closure クロージャ(省略可能)
 *   指定された場合はクロージャ引数にこのオブジェクトが渡り、
 *   処理終了とともに接続が閉じられる。
 */
function DBClient(dbname, closure){
  if (closure) {
    var dbc = new DBClient(dbname);
    closure(dbc);
    dbc.close();
  } else {
    this.open(dbname);
  }
}
DBClient.prototype = {
  /*
   * データベースを開く
   * 
   * @param {String} dbname データベース名
   */
  open: function(dbname){
    try {
      this.conn = new air.SQLConnection();  
      var dbfile = null;
      if (dbname) {
        dbfile = air.File.applicationStorageDirectory.resolvePath(dbname + ".db");
      }
      this.conn.open(dbfile);
    } catch (error) {
      statustext.innerText = "Error opening database";
      air.trace("error.message:", error.message);
      air.trace("error.details:", error.details);
      return;
    }
  },
  /*
   * データベース接続を開じる
   */
  close: function(){
    if (this.conn) {
      this.conn.close();
      this.conn = null;
    }
  },
  /*
   * SQL文を実行する
   * 
   * @param {String} sqltext SQL文
   * @param {Function} cbsuccess クエリ成功時のコールバック関数
   * @param {Function} cberror クエリ失敗時のコールバック関数
   */
  execute: function(sqltext, cbsuccess, cberror){
    try {
      var stmt = new air.SQLStatement();
      stmt.sqlConnection = this.conn;
      stmt.text = sqltext;    
      stmt.execute();
      cbsuccess(stmt.getResult());
      return true;
    } catch (error) {
      if (cberror)
          cberror(error);
      return false;
    }
  }
}
</script>
</head>
<body>
  <div>
    <div>
      <p>SQL Statement:</p>
      <textarea id="textSql">CREATE TABLE IF NOT EXISTS sample (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  content TEXT,
  score INTEGER
)</textarea>
    </div>
    <div>
      <button id="buttonExecute">execute SQL</button>
    </div>
    <div>
      <p>Query Result:</p>
      <textarea id="textResult"></textarea>
    </div>
    <div>
      <table spry:region="Results">
        <caption>Select Result</caption>
        <thead>
          <tr>
            <th spry:sort="{ds_RowNumberPlus1}">#</th>
            <th spry:sort="id">id</th>
            <th spry:sort="content">content</th>
            <th spry:sort="score">score</th>
          </tr>
        </thead>
        <tbody spry:repeatchildren="Results">
          <tr>
            <td>{ds_RowNumberPlus1}</td><td>{id}</td><td>{content}</td><td>{score}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
<script type="text/javascript"><!--
  function $(id) { return document.getElementById(id) };

  var Results = new Spry.Data.DataSet();
   
  $("buttonExecute").onclick = function(e){
    // SQLを実行する
    DBClient("sampledb", function(dbh){
      dbh.execute($("textSql").value, function(result){
        // 成功
        $("textResult").value = "QUERY OK, " + result.rowsAffected + " rows affected";
        Results.data = result.data ? result.data : {};
        Results.loadData();
      }, function(error){
        // 失敗
        $("textResult").value = error.message + "\n" + error.detailID + ": " + error.details;
      });
    });
  };
//-->
</script>
</body>
</html>

試してみる

プロジェクトを選択した状態で、Run -> Run...
デフォルトで SQL 文には CREATE TABLE が入っている。

データをインサート。

SELECT でテーブルを表示。

SQL についての解説

AIR SQL では非同期処理も可能だが、SQL 操作をここでは同期処理している。
SQL 機能に用意された SQLConnection や SQLStatement をそのまま使ってもよいのだが、ラッパーを作った方が早かったので DBClient 定義した。
AIR SQL は SQLite3 がベースらしいのだが、CURRENT_TIMESTAMP は認識されないようだった。
参考)

Spry についての解説

今回使用した、Spry の動的な領域はまずデータセットを作成する。

var Results = new Spry.Data.DataSet();

それにマッピングするテーブルを HTML で定義しておく。spry:region にさきほどのオブジェクト名を入れる。この場合は書くテーブルの行となるのは、その Results の各配列要素のため繰り返し項目の親となる tbody にも spry:repeatchildren="Results" を定義する。
あとは、各列名を {column} として定義しておけばいい。spry:sort を使うと列ソート機能を簡単に追加できる。

<table spry:region="Results">
  <caption>Select Result</caption>
  <thead>
    <tr>
      <th spry:sort="{ds_RowNumberPlus1}">#</th>
      <th spry:sort="id">id</th>
      <th spry:sort="content">content</th>
      <th spry:sort="score">score</th>
    </tr>
  </thead>
  <tbody spry:repeatchildren="Results">
    <tr>
      <td>{ds_RowNumberPlus1}</td><td>{id}</td><td>{content}</td><td>{score}</td>
    </tr>
  </tbody>
</table>

あとは、SQL の実行結果の data プロパティの値を Results.data にセットして、Results.loadData() を呼んでやれば自動的に table の内容が更新される。