SQLObjectでRDB以外を使えるようにがんばるの巻(1)

2009/11/30 23:53 | 0 Comments

最近はほとんどのWebアプリをPythonで書いていて、O/RマッパーはSQLObject 0.8を多少改変したものを使っている。(変更点としては、マルチプロセスあるいは複数台のAPサーバーで使えるようにキャッシュ機構を変えたり、あるいはリレーションの仕組みなどはそのまま使えるようにしつつテーブルごとに別のDBサーバーを指定できるようにしたりといった感じだ。)

データストアは基本的にはRDBでいいと思っているんだけど、これから大規模な(パフォーマンス要件が厳しいという意味での)アプリを書くことが増えそうなので、それであればやはりKVSにも簡単に対応できるようにしたくなってくる。もちろん、keyとvalueを放り込むだけのシンプルなKVSを使うだけであれば特に何も考える必要はないと思うんだけど、Google App EngineのデータストアやTokyo Tyrantなどは、検索機能についてもRDBにかなり近い感じで使えるようにしてくれている。joinなどはとっくの昔に使っていないので、あとはRDBとKVSの大きな違いといえばORが使えないくらいと言っていい。だとすれば、SQLObjectをそのまま使えるように出来れば、必要に応じてデータストアを簡単に切り替えられることになる。追加や更新、削除あたりは簡単に切り替えられそうだが、問題になるのはやはり検索だろう。SQLObjectでの検索の記法は直感的で非常に使いやすいので、それを何とか継承したい。というわけで、しばらくはこのあたりの変更のログ代わりにブログにまとめていこうと思う。

まず、SQLObjectでの検索時の記法についてまとめていく。たとえばnameとageというフィールドを持つPersonというモデルがあって、その中からAgeが30以上のレコードだけを取得したければ次のような感じで書ける。

for person in Person.select(Person.q.age < 30):
    print person.name

ここでポイントなのは、条件として「Person.q.age > 30」みたいな記述をそのまま渡せるところ。普通に考えれば、「a > 30」というように書けば、boolが返ってきそうな気がするのだが、そうではない処理がされているというわけだ。これはPythonの機能である比較演算子のオーバーライド(演算子といえばオーバーロードじゃないかという気もするけど、別に引数の型が関係するわけではなくて、サブクラスで上書きするだけだからオーバーロードではない)で実現されている。要は、クラスを定義するときに、__lt__などのインスタンスメソッドをオーバーライドすることで、比較の動作をカスタマイズできるということだ。たとえばこんな感じのコードが書ける。

class Person(object):
     def __init__(self,name):
          self.name = name
    
     def __lt__(self, other):
          return self.name +"(自分)と"+ other.name + "(相手)との比較"

p1 = Person("山田")
p2 = Person("鈴木")

#「山田(自分)と鈴木(相手)との比較」という文字列が表示される
print p1 < p2

#表示されるのは「<type 'str'>」
print (p1 < p2).__class__

最後の一文を見れば分かるように、「<」を使った比較であるにも関わらずboolではなくてstrを返している。SQLObjectを使って検索時に「Person.select(Person.q.age > 30)」というような書き方ができるのは、この仕組みを使っているわけだ。

じゃあ具体的なクラスの構造はどうなっているのか。SQLObjectではモデルのクラスにおいて、各フィールドに対応した「Person.q.age」といった変数が自動的に生成されるが、これらはSQLObjectFieldクラスのインスタンスになっている。そして、SQLObjectFieldの親クラスを辿っていくと、Field->SQLExpressionとたどり着き、このSQLExpressionクラスでほとんどの演算子がオーバーライドされていることが分かる。そして、SQLExpressionクラスでの演算子の戻り値は、SQLExpressionクラスのサブクラスであるSQLOpになっている(うーん、ややこしい)。そして、このSQLOpクラスが検索条件のセマンティックスを表現しているようだ。あとは、SQLOpクラスのインスタンスを使って、データベースに合わせたSQLのシンタックスが生成されるというわけである。とりあえずこの辺りに着目すれば、SQLObjectの検索記法からGQLを生成するくらいなら割と簡単に行けそうな気がしてきた。とりあえず今回はここまで。

名前 (必須)
コメント (必須)
確認文字 (必須) ひらがなで「ども」と入力してください。(スパム防止用)