ラベル Database Events の投稿を表示しています。 すべての投稿を表示
ラベル Database Events の投稿を表示しています。 すべての投稿を表示

Database Events (7)

Database Events (6) の続きです。前回、最初の方でスクリプトメニューから実行すると処理速度は速い、と書きました。これは、Database Events に限った話ではなく、どのアプリケーションのスクリプトでも速くなります(Carbon、Cocoa 両方ともかどうかまでは調べていませんが)。

ところで...System Events の用語説明には表示されない Hidden Suite という Suite をご存知でしょうか?

この Suite では、9 個の命令が定義されています。cancel、confirm、decrement、do action、do script、increment、key down、key up、pick の 9 個です。これらの使い方を詳らかにするのが目的ではないので端折りますが、多くは UI Element の操作を行うための命令です。do action と do script は(System Events 1.0 の頃から使っている人は知っていると思いますが)、フォルダアクションを実行する命令と OSA Script を実行する命令です。今回、取り上げるのは do script 命令。この命令は、run script 命令と同じように引数で指定したスクリプトファイルを実行するための命令です。

おそらく...スクリプトメニューは、この命令を使っているのではないかと思います(ただの推測なのですが)。スクリプトメニューは、Cocoa/Objective-C なのでしょうが、その中で NSAppleScript を使って System Events の do script を実行しているのではないかと思います。以下のスクリプトを実行すると System Events が返ってくることから考えてみても。

Script Editor で開く

on run
    tell application "System Events"
        set f to name of processes whose frontmost of it is true
    end tell

    display dialog (f as text)
end run

スクリプトメニューは、SystemUIServer が動かしているのですが。

つまり、スクリプトを do script 命令で動かせば処理速度は速くなる、ということが言えると思います。では、実験。

まず、実験した環境を。iMac G5(2 GHz Power PC G5)でメモリは 1 GB。HDD は、400 GB。OS は、最新。Mac OS X 10.4.8 です。AppleScript 1.10.7。Script Editor 2.1.1。

以下のスクリプトをコンパイル済みスクリプトとして保存します。

Script Editor で開く

on run
    set ex to ".dbev"
    set theName to "tmp"
    set dir to path to desktop as Unicode text
    set dbFile to dir & theName & ex

    tell application "Database Events"
        if exists database theName then
            set db to database theName
        else
            if exists database (POSIX path of dbFile) then
                set db to database (POSIX path of dbFile)
            else
                set db to make new database with properties {name:theName, location:dir}
                save db
            end if
        end if

        set cd to current date
        tell db
            repeat with i from 1 to 500
                make new record with properties {name:""}
            end repeat
            save
        end tell
        set num to (current date) - cd

        delete db
        quit
    end tell

    return num
end run

単純にデータベースに record を 500 個新規作成するだけのものです。これを Script Editor 上で実行すると、約 27 秒。次に、スクリプトメニューから実行します。このとき、約 10 秒。全く処理速度が違います。そして、do script 命令。

データベースに record を追加するスクリプトを do script 命令で 50 回呼び出して平均値をとってみました(データベスは、毎回削除して新規に作りながら)。結果、9.52 秒。スクリプトメニューの結果と同じです。

ついでにと思い、run script 命令も試してみます。これも 50 回呼び出して平均値をとってみました。結果は、6.12 秒。do script 命令に比べ、約 3 秒近く早くなっています。驚くべき事実。何がお前をそこまで変えてしまったんだ、と問わずにはいられません。

というか、AppleScript の処理速度って上がっていますよね。ただ、Script Editor で開発、デバックを行っていると気づかないし、むしろ、遅い。AppleScript Studio でアプリケーションにしたら速度が上がるなと思っていましたが、ようは Script Editor が悪かったんですね。これって既知のこと?うわっ...時代に取り残されていたよ。

これなら、積極的に Database Events を使ってみようという気になるもの。また、Script Editor でスクリプトを作っていて処理速度が遅いな、と思ったとき一度スクリプトメニューから実行してみるのもいいかも。ただ、デバックがより面倒になるよな...。

Database Events (6)

久しぶりに Database Events。Database Events は、一連の記事(Database Events (1)、Database Events (2)、Database Events (3)、Database Events (4)、Database Events (5))を書いて以来、いつか使えるといいな...といった感じで暇なときに時々いじっていたりしました。どうにもパフォーマンスが悪いし...きっと使い方が悪いのかもしれないと思い。

ところで...。iTunes 関連のスクリプトで有名な Doug's AppleScripts for iTunes に Most Played Artists というスクリプトがあります。これ、試してみたことあるでしょうか?

iTunes でのアーティストごとの再生回数をランキング形式でファイルに書き出してくれるものですが、Database Events を使っています。このスクリプトを開いてみると最初にコメントが書かれていますが、ここに 5154 曲の再生回数を集計するのに 6 分 18 秒とあります。Dual 1.8GHz G5 で。

Database Events は、どうにも遅かったような...と思い、はたと気づく。スクリプトをいったんアプリケーション形式で保存して実行してみます。2232 曲で 4 分。スクリプトのままスクリプトメニューから実行してみる。2232 曲で 1 分 50 秒...。これ、使えるじゃん。Script Editor で実行していたから遅かったのか。迂闊だった...。

試してみると、他のアプリケーションでも同じでした。スクリプトメニューから実行すると処理が早くなる。Script Editor から試すと遅い。開発環境になんて不向きなんでしょう。まさか、これって既知のこと?うわっ、時代に取り残されていたよ...。

そんなわけで急遽 Database Events に取り組む。Database Events が使えるなら、なんだか可能性が増える感じがするなー、と思う。根拠もアイデアもないけど。

DBMS を使うとき、最初にデータベースの構造を設計すると思います。後で変更できないからですね。が、Database Events では、変更し放題です。record には、field いくつでも作ることができます。また、最初の record と 次の record が同じ field を持たないといけないということもありません。record と field の name 属性は、同じものが複数あってもかまいません。database を必ずファイルに保存しないといけない、ということもありません。処理のために一時的なデータ置き場として使うことが可能です。使い捨てとして。Doug's AppleScripts for iTunes の Most Played Artists は、まさにそういった使い方をしています。

Database Events であるデータベースファイルを開くと、他のスクリプトからでもそのデータベースを使うことができ(共有される)、また、閉じることも可能です。たとえ、処理中であっても。これを防ぐ方法はありません。with transaction が解決法として思い浮かびますが、Database Events は対応していません。Database Events は、終了するときにすべてのデータベースを閉じます。これも、他のスクリプトで何らかの処理を行っていても、否応無しにデータベースは閉じられます。System Events に追加された一連のトランザクション関連の命令(abort transaction、begin transaction、end transaction)が解決方法を提供してくれるのかもしれませんが、使い方は Web 上では発見できず。同じ疑問を書いていた人はいたけど。これは、探し方が悪いのかもしれませんが。

database は、write 命令のファイル書き出しのようにデータベースファイルを開き、閉じることができます。対応するのは、open 命令と close 命令。が、close 命令は不安定で時々エラーになります。確実に閉じることができるのは delete 命令。delete 命令は、record と field の削除も行えます。open 命令でファイルをデータベースファイルを開くことができますが、POSIX path を指定する必要があります。make 命令で database を作るとき、location 属性に指定するのは、POSIX file の方です。location 属性以外は、すべて POSIX path で指定します。

exists 命令では開かれているデータベースが存在するかどうかを確認することができるとともに、POSIX path を指定することでデータベースファイルが存在するかどうかも確認することができます。

ある一つのデータベースを複数のスクリプトから変更すると、後から来た方の命令でデータは上書きされます。このとき、トランザクションを行っていなくてもデータの不整合がおこることはありません。どうも、内部的に適切に処理を行っているような感じがします。ただ、処理中に他のスクリプトからデータベースを閉じられる可能性がある、ということに留意しておく必要はあります。このときはもちろんエラーになり、データは正しく保存されません(もしくは、データベースが壊れる)。

open 命令は、既存のデータベースを開くことができますが、open するたびに Database Events で開かれているデータベースの数が増えていきます。これは、make 命令も同じです。

と、まあいろいろと書きましたが、実際のスクリプトで見ていきましょう。まず、デスクトップに PREF.dbev というデータベースがあります。open 命令で開くには、以下のようにします。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to open database (POSIX path of dbPath)
    databases
    --> {database "PREF" of application "Database Events", database "PREF" of application "Database Events"}
end tell

データベースファイルの POSIX path を指定し、かつ、database とクラスを指定します。これで開くことができるのですが、結果はなぜか同じものが二つ返ってきます。実のところ...というほどでもないのですが、データベースファイルを開くには以下のようにするだけでいいのでした。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to database (POSIX path of dbPath)
    databases
    --> {database "PREF" of application "Database Events"}
end tell

database (POSIX path) とするだけでデータベースが開かれます。その結果に open 命令を行っているので同じものが複数返ってくるのです。以下のような感じですね。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to database (POSIX path of dbPath)
    set dbCopy to open db
    databases
    --> {database "PREF" of application "Database Events", database "PREF" of application "Database Events"}
end tell

open 命令は、既に開いているデータベースを再度開き、同じものを結果として返します。複製を作っているような感じなのですが...何度も繰り返して open を行うと、使わないにもかかわらず開かれているデータベースの数は増えていきます。上記のスクリプトを何回か繰り返して実行してみると分かると思います。これは、make 命令も同じです。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    make new database with properties {name:theFile, location:dbPath}
    databases
end tell

このスクリプトを何回か繰り返してもエラーにはなりません。ただ、開かれているデータベースの数が増えるだけです。不必要にも関わらず、同じデータベースが複数開かれているということもありえるので注意が必要です(open 命令ではなく、データベースファイルのパスを指定する方では、数は増えません)。

データベースを閉じるのが close 命令です。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to database (POSIX path of dbPath)
    set dbCopy to open db
    close db
    databases
    --> {database "PREF" of application "Database Events"}
end tell

また、delete 命令でも閉じることができます。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to database (POSIX path of dbPath)
    set dbCopy to open db
    close db
    delete dbCopy
    databases
    --> {}
end tell

ただ、close 命令は(何が原因か分かりませんが)エラーになることがあります。

exists で database(もしくは、record や field)の存在を確認することができます。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    set db to database (POSIX path of dbPath)
    exists database "PREF"
    --> true
end tell

exists にパスを指定することでデータベースファイルが存在するかどうかの確認もできます。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    exists database (POSIX path of dbPath)
    --> true
    databases
    --> {database "PREF" of application "Database Events"}
end tell

存在すれば真を返し、データベースは開かれます。存在しないと偽を返し、エラーにはなりません。データベースファイルがなければ作成し、あればそれを開くという処理は open 命令を使うよりも exists 命令を使う方がいいでしょう。

ただ、Database Events はどんなファイルであってもデータベースとして開いてしまうようで、例えば、テキストファイルを指定してもデータベースとして開いてしまいます。以下のスクリプトは、スクリプトファイルを指定した例です。

Script Editor で開く

set theFile to "Database.scpt"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    exists database (POSIX path of dbPath)
    --> true
    databases
    --> {database "Database" of application "Database Events"}
end tell

開けるからといって使えるわけではありません。この辺りの注意はスクリプトを作る人の責任になる...のでしょうね。

Database Events は、起動しているあいだに開かれたデータベースをすべて知っています。

Script Editor で開く

set theFile to "PREF.dbev"
set dir to path to desktop as Unicode text
set dbPath to dir & theFile

tell application "Database Events"
    exists database (POSIX path of dbPath)
    databases
    --> {database "PREF" of application "Database Events"}
end tell

このスクリプトを実行します。その後、新規ドキュメントを作って以下のスクリプトを実行します。

Script Editor で開く

tell application "Database Events"
    delete databases
    databases
    --> {}
end tell

このように他のスクリプトから閉じることができます。もちろん、操作もできます。が、処理中のデータベースがあるかどうかを調べることはできません。Database Events は、終了時にすべてのデータベースを自動的に閉じます。そのために quit delay という属性で Database Events が起動してから何秒後に終了するかを指定することができます。ただ、データベースを修正したかどうか、セーブする必要があるかどうかといったことを調べる方法は Database Events には用意されていません。しかし、修正されていた場合、データベースを閉じるときに Database Events が保存を行います(データベースがファイルとして既に保存されているなら、です)。Database Events が終了するときではありませんので注意を。

他のスクリプトからでも開かれているデータベースにアクセスできますが、操作の競合やデータの不整合が起こることはないようです。処理は順番待ちで実行されます。小難しい処理は Database Events が責任を持って行っている、という感じでしょうか。データベースの操作にそれほど神経質にならなくてもいいのかもしれません(あくまで、主観。もしかしたら、全然違うかもしれません)。

最後に一つだけ書きたいことがあるのですが...さすがに、これ以上書くと長くなるので次回に続く...ということで。

Database Events (5)

処理速度はともかく、Database Events は、field の value 属性に実際のデータを保存します。用語辞書を見ると value 属性は値に anything と記述されています。SQLite は、バージョン 3 になって一応型が定義されています。本当に何でも value に設定できるのか試してみます。

ここまでで文字列と数値(実数、整数)がそのまま保存できるのは分かっています。前回、特に断っていなかったのですが、AppleScript の date も保存できています。value 属性を読み込むと date 型の値が返ってきます。

では、他のものではどうなるでしょうか。ということで以下のスクリプト。

Script Editor で開く

read file ((path to pictures folder as Unicode text) & "Safari.tiff") as data

AppleScript のいろんなデータを保存してみました。結論から言うと、上記スクリプトの中で保存、再読み込みが可能なのは date クラスと真偽値だけです(エラーにならないのですが、返ってくる値が一律 false になります)。alias 参照は、保存できますが読み込んだときにスラッシュ区切りの POSIX path に変換されてしまいます。

AppleScript の参照も保存できません。ということは、ある field の value に他のデータベースの field の参照をいれておくこともできません。その field を参照すると他のデータベースの field にアクセスするというようなことは無理ですね(というか、それは無茶)。

文字列で保存できるならほとんどの状況でどんなデータも保存できるので困ることもないと思います。それでも、リストやレコードなんかは内部で変換でもして保存して欲しい気もします。まぁ、多くの場合、画像を as data で保存することなんてないでしょうし、画像ファイルのパス文字列が保存できればそれでいいでしょう。リストもレコードも保存方法を考えると解決できるものです。ハンドラやスクリプトオブジェクトをそのまま保存できるなら、ハンドラデータベースが構築できると思ったのですが、これも文字列にしておけばいいですね。

そんなわけでデータベースに保存しておくのは文字列と数値にしておくのが無難です。

データベースの名前ですが、これは日本語も使えます。

Script Editor で開く

set manList to {{"山本", "浩二"}, {"金本", "雅次"}, {"園山", "香織"}, {"井上", "雅子"}}

tell application "Database Events"
    make new database with properties {name:"交友録"}
    tell database "交友録"
        repeat with i from 1 to count manList
            set recordValue to item i of manList as Unicode text
            set theRecord to make new record with properties {name:recordValue}
            tell theRecord
                make new field with properties {name:"姓", value:item 1 of (item i of manList)}
                make new field with properties {name:"名", value:item 2 of (item i of manList)}
            end tell
        end repeat
        save
    end tell
end tell

このようにして field にも日本語を利用することができます。もちろん以下のように取得も可能です。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:交友録")

tell application "Database Events"

    tell database databaseFile
        name of records
        -- {"山本浩二", "金本雅次", "園山香織", "井上雅子"}
        value of field "姓" of records
        -- {"山本", "金本", "園山", "井上"}
        value of fields of (records whose value of field "名" of it contains "子")
        -- {"井上雅子", "井上", "雅子"}
    end tell
end tell

Database Events は、できたてのアプリケーションでまだまだ不安定なのか、よく反応が返ってこないことがあります。そんなときは、一度終了するともとに戻ります(System Events もですが...)。

Script Editor で開く

tell application "Database Events" to quit

また、単に処理に手間取ってなかなか結果が返ってこないこともあるので with timeout でタイムアウトも設定しておく方がいいかもしれません。

これまで長々と見てきましたが、Database Events は AppleScript で SQLite の全機能が利用できるということではなく、単純な行と列だけで表現する簡易データベースという感じです。機能的には新しい行の追加と削除ぐらいしかできないようで、行の並べ替えなんかは無理です(以前、例として載せたようなソートではなく、record をそのまま並べ替えることです)。並べ替えは実際にデータを表示するときに行えばいいのでそれほど問題はないですが、それだけコードが長くなるので、できれば実装して欲しいところです。

また、Database Events は、その用意されている用語からも分かるようにデータベースの中に複数の table を作ることもできません。プライマリーキーを設定してリレーショナルデータベースというのも(確証はないのですが、おそらく)できないでしょう。もちろん、SQLite でなら可能です。あれ? テーブルを指定できないということは、CoreData アプリケーションが保存した SQL フォーマットのデータベースも読むことができない、ということですね? ...できませんでした。。。てっきり、連携できると思っていたのですが。この辺りのことに期待していた人には悪い知らせです。すいません。

Terminal から sqlite3 コマンドを使うと分かるのですが、Database Events で作成したデータベースは、ZFIELD、ZRECORD、Z_METADATA、Z_PRIMARYKEY と 4 つのテーブルを持っています(midore さんに情報をいただきました。ありがとうございました)。ZFIELD で field が、ZRECORD で record が管理されているようです。Database Events は、これらのテーブルの内容を解析してデータを返しているようです。一方、CoreData アプリケーションが保存した SQL フォーマットのファイルもいくつかテーブルが作成されています。Developer Tools の CoreData のサンプルアプリケーション、OutlineEdit は次のようなテーブルを持っていました。ZNOTE、ZPRIORITY、Z_METADATA、Z_PRIMARYKEY。メタデータとプライマリーキーのテーブルは共通しています。こういうデータベースの作られ方というのは、CoreData を利用するときに特有のものなのでしょね(CoreData の資料にいい加減目を通さないと...)。

実際のところ、Database Events でデータベースを利用するためには、Database Events で利用できるデータ構成でないと駄目なのでしょう。sqlite3 を使えばどのアプリケーションが保存したデータベースも操作できるのですが、CoreData アプリケーションのデータベースを Database Events で利用することはできないということですね。

最後に。掲示板の方で Perl のハッシュのような使い方ができれば...とありました(TUNA さん。ありがとうございます)。ハッシュって連想配列ですよね? Perl 特有のデータ型の。Objective-C でいうところの辞書(NSDictionary)ですね。AppleScript で言えばレコード。あれは、Perl らしい使い方がいろいろと用意されていて便利ですね。Database Events で代用しようと思えば...、単純に record の name 属性をキーにして field をひとつ追加してその中にデータを入れておけばいいのでは? たとえば、次のような感じです。

Script Editor で開く

--すでに Hash.dbev というデータベースがある
set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:Hash.dbev")

--データの追加
my addRecord(databaseFile, "整数", 10)
my addRecord(databaseFile, "実数", pi)
my addRecord(databaseFile, "文字列", "我が輩は猫である")
my addRecord(databaseFile, "日付", current date)
--データの取得
getData(databaseFile, "整数")
--データの変更
setData(databaseFile, "文字列", "気は優しくて力持ち")
--キーの一覧を取得
keys(databaseFile)
--キーとデータをリストで取得
each(databaseFile)

on addRecord(databaseFile, keyStr, theData)
    tell application "Database Events"
        tell database databaseFile
            set theRecord to make new record with properties {name:keyStr}
            tell theRecord
                make new field with properties {name:"value", value:theData}
            end tell
            save
        end tell
    end tell
end addRecord

on setData(databaseFile, keyStr, newData)
    tell application "Database Events"
        tell database databaseFile
            set value of field "value" of record keyStr to newData
            save
        end tell
    end tell
end setData

on getData(databaseFile, keyStr)
    tell application "Database Events"
        tell database databaseFile
            set theResult to value of field "value" of record keyStr
        end tell
    end tell

    return theResult
end getData

on keys(databaseFile)
    tell application "Database Events"
        tell database databaseFile
            set theResult to name of records
        end tell
    end tell

    return theResult
end keys

on values(databaseFile)
    tell application "Database Events"
        tell database databaseFile
            set theResult to value of field "value" of records
        end tell
    end tell

    return theResult
end values

on each(databaseFile)
    tell application "Database Events"
        set theResult to {}
        tell database databaseFile
            set end of theResult to name of records
            set end of theResult to value of field "value" of records
        end tell
    end tell

    return theResult
end each

これでは使えるデータが文字列と数値のみですが。リストやレコードに対応すればもう少し便利に使えるかも。

Database Events (4)

気にはなっていたのですが、このサイトの RDF を Safari で表示するとスクリプトが見にくいですね。スクリプトのインデントを CSS で整形しているのでこうなってしまうのですが...。どうやって回避しようか。と、悩んでみる振りをしていますが、このままでいく可能性大ですが...。

Database Events (1)、Database Events (2)、Database Events (3) と続いてきましたが、基本的なことはいったん終了して他のことを調べてみます。まずは、誰もが気になる(と思う)処理速度。SQLite をコマンドラインで使用すると何万件とあるデータをコンマ xxx 秒といった感じで即座に取り込んでくれます。

AppleScript から Database Events を利用するのであれば、そこまでの速度は、期待できません。ともあれ、やってみます。

次のようなスクリプトを用意して current date で時間を計ってみました。

--テスト用データを作成
tell application "Mail"
    set dataList to {}
    set theList to messages of mailbox 2
    repeat with thisItem in (a reference to theList)
        set end of dataList to {subject of thisItem, sender of thisItem, date sent of thisItem}
    end repeat
end tell

tell application "Database Events"
    --データベースを作成
    make new database with properties {name:"Sample"}
    save database "Sample"
    tell database "Sample"
        set cd to current date -- 計測開始
        repeat with thisData in dataList
            set theRecord to make new record with properties {name:item 1 of thisData}
            tell theRecord
                make new field with properties {name:"sender", value:item 2 of thisData}
                make new field with properties {name:"date sent", value:item 3 of thisData}
            end tell
        end repeat
        set n to (current date) - cd --計測終了
        save
        return n
    end tell
end tell

Mail のメッセージでテストデータを作成し、それを新しく作成したデータベースに追加するスクリプトです。mailbox 2 の中には、1275 件のメールがありました。これらのデータを新規作成し、追加するのに 393 秒かかりました。約 6 分 30 秒です。ちょっと...遅くない?

いろいろ試してみたのですが、record の追加を一気に行うと速度的にはかなり遅くなります。どれぐらいのデータを一気に追加すると遅くなるか...。例えば、以下のようなスクリプトでデータ 1000 件を追加すると約 38 秒です。

Script Editor で開く

tell application "Database Events"
    make new database with properties {name:"Test"}
    save database "Test"
    tell database "Test"
        repeat with i from 1 to 1000
            make new record with properties {name:i as Unicode text}
        end repeat
        save
    end tell
end tell

このスクリプトで数を変えていろいろ試してみましたが、せいぜい我慢できるのは 100 件ぐらいまでです。ただ、すでに record が 1000 個ある状態で新しく record を 1 件追加しても速度が遅いというわけではないです。このように繰り返しで一気に追加するのが遅いのです。

速度のことでいえば、record の削除でも同じことがいえます。削除は、delete 命令で行えるのですが、record が 5000 件あるときに

delete records

とすると、タイムアウトになります。いつまでたっても削除処理が完了しないのでどれぐらい時間がかかるのか分かりませんが、削除のときも一気に削除を行おうとすると、いつまでたっても反応が返ってこない、という状態に陥ります。ただ、これも単純に一件を削除するだけなら処理速度的に問題はありません。

record の数が多くなったときに処理が遅くなるのは新規作成、削除のときだけではありません。フィルタ参照も遅くなります。これも、番号参照などで record を決め打ちするなら最初の record でも最後の record でも真ん中の record でも速度的には変わりません。

tell application "Database Events"
    tell database databaseFile
        record 1 -- 変わらない
        last record -- 変わらない
        middle record -- 変わらない
        some record -- 変わらない
        records whose name of it contains "10" --データ数が多いと遅くなる
        records 100 thru 200 --データの範囲が多いと遅くなる
    end tell
end tell

上記のように番号参照、相対参照、中央参照、任意参照といった取り出すデータ数が 1 つのときは、速度低下がありません。が、フィルタ参照は、データ(record)の数が増えるとそれだけ速度が低下します。範囲参照も一気に取り出すデータ数が多くなると速度が低下します。

この処理速度の遅さは、SQLite に問題があるのではありません。Database Events の問題だと思います。今後、改良されてもう少しましになるといいのですが、今のところ CSV を一気に取り込むなんて処理は期待できそうにないです。データを追加、削除するのは sqlite3 で行うとしても、フィルタ参照が遅くなるなら Database Events の使い道って著しく限定されてしまうような...。

Database Events (2)

前回、データベースを作成し、保存しました。今度は、データを追加していきます。データは、record と field で表現します。record が 行になり、field が列になります。最初に record を追加し、その record に field を作成し、データを追加していくという形になります。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        set newRecord to make new record with properties {name:"1"}
        save
    end tell
end tell

make で新しくオブジェクトを作成するとき、Database Events では at オプションが(なぜか)利用できません。ですので、データベースに tell して、その中で新しい record を作成します。record には、name 属性と id 属性があります。id 属性は、一意のもので Database Events が自動的に付加します。変更はできません。name 属性は、行の名前に当たるものですが、これは field "name" と同じものです。

分かりにくいですね。record を新しく作成したとき、既にひとつだけ field が追加された状態で作成されます。それが field "name" です。name という列がひとつだけある表と考えるといいでしょうか。record を作成し、name 属性を設定しているのですが、実際は field "name" に値を設定しているのです。この辺りが少し分かりにくいかもしれません。

field を作成するのも database や record を作成するときと同じ感じです。では、次のようなデータ(都道府県の県庁所在地と人口)をデータベースに追加してみます。

Script Editor で開く

set theData to {{|name|:"北海道", capital:"札幌", population:5691737}, {|name|:"青森県", capital:"青森", population:1504358}, {|name|:"岩手県", capital:"盛岡", population:1427987}, {|name|:"宮城県", capital:"仙台", population:2340145}, {|name|:"秋田県", capital:"秋田", population:1209196}, {|name|:"山形県", capital:"山形", population:1249165}, {|name|:"福島県", capital:"福島", population:2138605}, {|name|:"茨城県", capital:"水戸", population:2990472}, {|name|:"栃木県", capital:"宇都宮", population:1998186}, {|name|:"群馬県", capital:"前橋", population:2013753}, {|name|:"埼玉県", capital:"浦和", population:6838164}, {|name|:"千葉県", capital:"千葉", population:5863182}, {|name|:"東京都", capital:"東京", population:11680490}, {|name|:"神奈川県", capital:"横浜", population:8324355}, {|name|:"新潟県", capital:"新潟", population:2487980}, {|name|:"富山県", capital:"富山", population:1126782}, {|name|:"石川県", capital:"金沢", population:1175511}, {|name|:"福井県", capital:"福井", population:828087}, {|name|:"山梨県", capital:"甲府", population:883847}, {|name|:"長野県", capital:"長野", population:2299468}, {|name|:"岐阜県", capital:"岐阜", population:2108530}, {|name|:"静岡県", capital:"静岡", population:3754758}, {|name|:"愛知県", capital:"名古屋", population:6875723}, {|name|:"三重県", capital:"津", population:1855860}, {|name|:"滋賀県", capital:"大津", population:1316331}, {|name|:"奈良県", capital:"奈良", population:1447496}, {|name|:"和歌山県", capital:"和歌山", population:1094120}, {|name|:"京都府", capital:"京都", population:2561860}, {|name|:"大阪府", capital:"大阪", population:8624045}, {|name|:"兵庫県", capital:"神戸", population:5500842}, {|name|:"岡山県", capital:"岡山", population:1958385}, {|name|:"鳥取県", capital:"鳥取", population:618868}, {|name|:"島根県", capital:"松江", population:765980}, {|name|:"広島県", capital:"広島", population:2876405}, {|name|:"山口県", capital:"山口", population:1540354}, {|name|:"徳島県", capital:"徳島", population:835781}, {|name|:"香川県", capital:"高松", population:1035579}, {|name|:"愛媛県", capital:"松山", population:1517190}, {|name|:"高知県", capital:"高知", population:821199}, {|name|:"福岡県", capital:"福岡", population:4955439}, {|name|:"佐賀県", capital:"佐賀", population:883960}, {|name|:"長崎県", capital:"長崎", population:1537280}, {|name|:"大分県", capital:"大分", population:1238496}, {|name|:"熊本県", capital:"熊本", population:1870473}, {|name|:"宮崎県", capital:"宮崎", population:1188341}, {|name|:"鹿児島県", capital:"鹿児島", population:1790437}, {|name|:"沖縄県", capital:"那覇", population:1313804}}

tell application "Database Events"
    make new database with properties {name:"MyDatabase"}
    tell database "MyDatabase"
        repeat with thisItem in theData
            set thisItem to contents of thisItem
            set theRecord to make new record with properties {name:|name| of thisItem}
            tell theRecord
                make new field with properties {name:"capital", value:capital of thisItem}
                make new field with properties {name:"population", value:population of thisItem}
            end tell
        end repeat
        save
    end tell
end tell

やはり、保存はきちんと行っておきます。ここでは record の name 属性に県名を指定しています。

Database Events (3)

前々回は、データベースを作成しました。前回は、データベースにデータを追加してみました。今度は、データを利用してみます。前々回、前回で作成した都道府県データベースを使っていますので、まずは、そちらを参照してみてください。

Database Events には、データの操作に関する命令なんかは用意されていません。ですので、ソートや検索などは AppleScript の機能を利用して行うことになります。record の name 属性に県名を指定しておいたので名前参照でレコードを指定することができます。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        record "愛知県"
    end tell
end tell

範囲参照や全要素参照、任意参照、中央参照、番号参照も使うことができます。おおむねほとんどの参照方法を利用できますが、やはり、一番強力なのはフィルタ参照でしょう。以下のようにすることで人口 400 万人以上の県名だけを取得することができます。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        name of records whose value of field "population" of it is greater than 4000000
    end tell
end tell

--> {"北海道", "埼玉県", "千葉県", "東京都", "神奈川県", "愛知県", "大阪府", "兵庫県", "福岡県"}

何人以上、何人以下ということも可能です。指定する field を変更することで文字列でフィルタ参照を行うことももちろん可能です。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        name of records whose name of it contains "川"
    end tell
end tell

川の字の入っている県名だけを抜き出しています。フィルタ参照を利用することでデータの取得はかなり柔軟に行えると思います。問題は、ソートですね。AppleScript にはソートの命令がないので自前でソートのハンドラを作るか、シェルの sort コマンドを利用するのが最も手早い解決になります。例えば、人口 400 万人以上の県を人口でソートして結果を返す、といったことを行ってみましょう。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        -- 人口 400 万人以上を抽出
        set theResult to value of field "population" of (records whose value of field "population" of it is greater than 4000000)
        -- 結果の比較用のリストを作成
        set beforeSortList to name of records whose value of field "population" of it is greater than 4000000
        --ソートし、人口が多い順に結果を反転
        my simpleSort(theResult)
        set theResult to reverse of theResult

        -- ソートした結果を元にレコードを取得
        set sortedList to {}
        repeat with thisValue in theResult
            set thisRecord to (records whose value of field "population" of it is (thisValue as integer))
            set end of sortedList to name of (item 1 of thisRecord)
        end repeat

        --結果を比較(分かりやすいように県名で)
        {beforeSortList, sortedList}
    end tell
end tell

on simpleSort(theList)
    -- 昇順
    set scoreCount to count theList -- リスト内の数   
    set sortedCount to 1 -- ソート済みの最初の範囲

    repeat while sortedCount < scoreCount
        set minimumIndex to sortedCount
        repeat with i from sortedCount to scoreCount
            if item minimumIndex of theList > item i of theList then
                set minimumIndex to i
            end if
        end repeat
        set temp to item minimumIndex of theList
        set item minimumIndex of theList to item sortedCount of theList
        set item sortedCount of theList to temp
        set sortedCount to sortedCount + 1
    end repeat
end simpleSort

ソートする項目が少ないので AppleScript だけで行っています。最初に人口のリストを取得しています。このリストをソートし、ソートした結果を元に繰り返しでフィルタ参照で目的の record を探しています。これで人口 400 万人以上の県を人口の多い順にソートした結果になります。ちなみにシェルの sort を使って同じ結果を取得するには、次のような方法があります。

Script Editor で開く

set theFolder to path to documents folder as Unicode text
set databaseFile to POSIX path of (theFolder & "データベース:MyDatabase.dbev")

tell application "Database Events"
    tell database databaseFile
        -- 人口400 万人以上を抽出
        set theResult to value of fields of (records whose value of field "population" of it is greater than 4000000)

        -- シェルの sort で使うスペース区切りのデータを作成
        set tmpText to ""
        set lastNum to count theResult
        repeat with i from 1 to count theResult
            set theList to item i of theResult

            if i is lastNum then
                set tmpText to tmpText & my listToText(reverse of theList, space)
            else
                set tmpText to tmpText & my listToText(reverse of theList, space) & (ASCII character 10)
            end if
        end repeat
        --return tmpText
        my shellSort(tmpText)
    end tell
end tell

on listToText(theList, delimStr)
    set the AppleScript's text item delimiters to delimStr
    set theText to theList as text
    set the AppleScript's text item delimiters to ""
    return theText
end listToText

on shellSort(theData)
    do shell script "echo " & quoted form of theData & " | sort -nr"
end shellSort

この場合は、最初のフィルタで全ての field の値を取り出しています。結果は、

11680490 東京 東京都
8624045 大阪 大阪府
8324355 横浜 神奈川県
6875723 名古屋 愛知県
6838164 浦和 埼玉県
5863182 千葉 千葉県
5691737 札幌 北海道
5500842 神戸 兵庫県
4955439 福岡 福岡県

このようなテキストになります。あるいは、このソートの方が使い勝手がいいかもしれません。いずれにしても AppleScript にソートがないために少しばかりスクリプトの手数が必要になってきます。

Database Events (1)

最近、Database Events を利用する状況が出てきたので、少し調べてみました。Mac OS X 10.4 を導入したときにも少し触れたことがあるのですが、今度は利用を前提にどれぐらい使えるものかを。

まず、基本的なことから。Database Events は、SQLite を AppleScript から操作できるバックグラウンドアプリケーションです(SQLite だと思う。どうも、情報が少なくて)。SQLite 自体は、C 言語のライブラリです。C 言語から利用できますし、Terminal からコマンドを使うことでデータベースを操作することもできます。SQLite は、SQL をベースに開発されています。SQL よりも処理が高速だったりします。SQL との違いは、ファイル単位でデータベースを構築することです。

ファイル単位というのがよく分からないと思いますが、MySQL のようにサーバーで利用する(のでも可能ですが)、巨大なデータベースを構築するというより、アプリケーションが利用するデータを保持しておく簡易データベースとして利用することができます。

Database Events で作成したファイルは、他のアプリケーションでも利用可能な汎用的なものになります。誤解を招く表現ですが、こういうことです。Mac OS X 10.4 では、CoreData が導入されています。CoreData を利用するアプリケーションは、SQLite のファイル形式でファイルを保存することができます。こういったファイルは Database Events で利用可能ですし、逆も同様です。

Database Events は、このようなことから最初にデータベースファイルを作成する必要があります。基本的な流れとしては、

  1. データベースファイルを作成
  2. レコードを追加する
  3. フィールドを作りデータを放り込んでいく

という形になります。

では、実際に使ってみます。Database Events は、/System/Library/CoreServices にあります。System Events や Image Events と同じですね。用語辞書を開いてみると...、データベースを利用するための最低限の属性しか定義されていません。database と record と field...。ソートや範囲を決めてのデータの取り出や検索や、そういったデータベースを操作するための命令のようなものはありません。これは、すべて AppleScript で処理をしろということなのでしょうか?

もう少し、使い勝手がよくてもいいような。まず、最初にデータベースを作成します。

Script Editor で開く

tell application "Database Events"
    make new database
end tell

database クラスの属性には name と location があります。name は、データベースの名前(通常データベースファイルの名前になります)、location は、データベースファイルの保管場所です。これらは、データベースを作成するときに指定しておくと効率的です。

Script Editor で開く

tell application "Database Events"
    make new database with properties {name:"MyDatabase"}
end tell

location を指定していませんが、Database Events はデータベースの保存場所をデフォルトで持っています。Documents フォルダ内に「データベース」(日本語環境です)というフォルダを作成し、その中に自動的に保存していきます。しかし、ここで注意しないといけないのは save 命令を使ってきちんと保存しないといくらファイルを作成しても利用できないようになることです。

Script Editor で開く

tell application "Database Events"
    make new database with properties {name:"MyDatabase"}
    save database "MyDatabase"
end tell

このように save を行うと ~/Documents/データベース/MyDatabase.dbev というファイルが作成されます。location を指定しない場合は、全てこのフォルダに保存されます。保存を行っておくと Database Events を終了した後でも次のようにすることでデータベースを再度利用することができます。

Script Editor で開く

set databaseFile to (path to documents folder as Unicode text) & "データベース:MyDatabase.dbev"

tell application "Database Events"
    tell database (POSIX path of databaseFile)
        properties
    end tell
end tell

save を行わなかった場合、いくらファイルが作成されていたとしてもそのファイルにアクセスすることはできません。この保存は、record を追加しても field を追加しても、その都度保存しないといけません。