1
1

More than 5 years have passed since last update.

ocaml-ormはオブジェクトをどのようにRDBに格納するのか?(その2)

Last updated at Posted at 2014-06-15

ocaml-ormはどのようにオブジェクトをRDBに格納するのか?(その1) の続きです。

foreign

RDB の一対多の最もシンプルな形ですね。

foreign.mlから抜粋
type t = {
  foo: string;
  bar: int64;
  xyz: char;
} and x = {
  first: t;
  second: t;
  third: int;
} with orm (
 unique: t<xyz>, t<bar>;
 index: x<first,second>
)

let s1 = { foo="hello"; bar=100L; xyz='a' }
let s2 = { foo="world"; bar=200L; xyz='z' }
let x = { first=s1; second=s2; third=111 }
sqlite> .tables
__links__  __types__  t          x        
sqlite> .schema
CREATE TABLE __links__ (parent TEXT, field TEXT, child TEXT);
CREATE TABLE __types__ (n TEXT, t TEXT);
CREATE TABLE t (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, foo STRING,bar INTEGER,xyz INTEGER);
CREATE TABLE x (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, first INTEGER,second INTEGER,third INTEGER);
CREATE UNIQUE INDEX idx_t_bar ON t (bar);
CREATE UNIQUE INDEX idx_t_xyz ON t (xyz);
CREATE INDEX idx_x_first_second ON x (first,second);
CREATE TRIGGER t_update_cache AFTER DELETE ON t FOR EACH ROW BEGIN SELECT SYNC_CACHE_t(OLD.__id__); END;
CREATE TRIGGER x_update_cache AFTER DELETE ON x FOR EACH ROW BEGIN SELECT SYNC_CACHE_x(OLD.__id__); END;
sqlite> select * from __links__;
parent      field       child     
----------  ----------  ----------
x           first       t         
sqlite> select * from __types__;
n           t                               
----------  --------------------------------
t           E/t/{Rfoo:I:S*bar:I:I64*xyz:I:C}
x           E/x/{Rfirst:I:E/t/{Rfoo:I:S*bar:
sqlite> select * from t;
__id__      foo         bar         xyz       
----------  ----------  ----------  ----------
1           world       200         122       
2           hello       100         97        
sqlite> select * from x;
__id__      first       second      third     
----------  ----------  ----------  ----------
1           2           1           111    
  • unique: t<xyz>txyz にユニークインデックスが生成される。
  • index: x<first,second> で複合インデックス。
  • x.firstx.secondt.__id__ を参照している。実にシンプル。なんだけど、secondt を参照してるってのはどうやってわかるんだろうなぁ? __links__ に情報がないわけで。すると残るは __types__ しかないんだが、ソース軽く読んだ感じではそういう箇所見当たらないんだよなぁ。

recursive

recursive.mlから抜粋
type y = char with orm

type t = {
  t1: string;
  t2: x option
} and x = {
  x1: t option;
  x2: y
} with orm

type z = t with orm

type a = { a : y } with orm


let vy = 'a'
let rec vt = { t1= "hello"; t2=(Some vx) }
and vx = { x1=(Some vt); x2=vy }
let vz = vt
let va = { a = vy }

これはテストが全部終わった段階でレコードが全部消えてしまうように
なっているので、最後の delete のテストを削除してビルド&実行し直した。

おそらく主旨としては、xt を参照し、tx を参照する
というところなんだろうけど、これ既に variant_nested のところで
やったので、だいたい想像つきますね。

それよりは、option 型が出てくるのが初めてなので、そこに注目したい。

sqlite> .tables
__links__  __types__  t          x          y        
sqlite> .schema
CREATE TABLE __links__ (parent TEXT, field TEXT, child TEXT);
CREATE TABLE __types__ (n TEXT, t TEXT);
CREATE TABLE t (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, t1 STRING,t2__0__isset INTEGER,t2__0 INTEGER);
CREATE TABLE x (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, x1__0__isset INTEGER,x1__0 INTEGER,x2 INTEGER);
CREATE TABLE y (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, val INTEGER);
CREATE TRIGGER t_update_cache AFTER DELETE ON t FOR EACH ROW BEGIN SELECT SYNC_CACHE_t(OLD.__id__); END;
CREATE TRIGGER x_update_cache AFTER DELETE ON x FOR EACH ROW BEGIN SELECT SYNC_CACHE_x(OLD.__id__); END;
CREATE TRIGGER y_update_cache AFTER DELETE ON y FOR EACH ROW BEGIN SELECT SYNC_CACHE_y(OLD.__id__); END;
sqlite> select * from __links__;
parent      field       child     
----------  ----------  ----------
x           x2          y         
t           t2__0       x         
x           x1__0       t         
sqlite> select * from __types__;
n           t         
----------  ----------
y           E/y/C     
t           R@t@{Rt1:I
x           R@x@{Rx1:I
sqlite> select * from t;
__id__      t1          t2__0__isset  t2__0     
----------  ----------  ------------  ----------
1           hello       1             1         
sqlite> select * from x;
__id__      x1__0__isset  x1__0       x2        
----------  ------------  ----------  ----------
1           1             1           1         
sqlite> select * from y;
__id__      val       
----------  ----------
1           97   
  • option 型は NULL を使うのかと思っていたが、どうやら __isset というカラムに値の有無を表すフラグを立てているっぽい。
  • 参照関係は variant_nested と同じですね。t.t2__01x.__id__1 のレコードを参照。x.x1__01t.__id__1 のレコードを参照。という形で互いに参照しあっている。

array_simple

array_simple.mlから抜粋
type s = {
  foo: int array;
  bar: string
} with orm

let t1 = { foo=[|1|]; bar="t1" }
let t2 = { foo=[|1;2;3|]; bar="t2" }
let t3 = { foo=[||]; bar="t3" }
sqlite> .tables
__links__  __types__  s          s__foo__0
sqlite> .schema
CREATE TABLE __links__ (parent TEXT, field TEXT, child TEXT);
CREATE TABLE __types__ (n TEXT, t TEXT);
CREATE TABLE s (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, foo INTEGER,bar STRING);
CREATE TABLE s__foo__0 (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, __next__ INTEGER,__next_chunk__ INTEGER,__size__ INTEGER,val INTEGER);
CREATE UNIQUE INDEX idx_s__foo__0_enum ON s__foo__0 (__id__,__next__);
CREATE TRIGGER s_foo_cleanup AFTER UPDATE OF foo ON s FOR EACH ROW BEGIN SELECT CLEAN_UP_s__foo__0(OLD.foo,NEW.foo); END;
CREATE TRIGGER s_update_cache AFTER DELETE ON s FOR EACH ROW BEGIN SELECT SYNC_CACHE_s(OLD.__id__); END;
sqlite> select * from __links__;
parent      field       child     
----------  ----------  ----------
s           foo         s__foo__0 
sqlite> select * from __types__;
n           t         
----------  ----------
s__foo__0   !I63|     
s           E/s/{Rfoo:
sqlite> select * from s;
__id__      foo         bar       
----------  ----------  ----------
1           1           t1        
2           4           t2        
3                       t3        
sqlite> select * from s__foo__0;
__id__      __next__    __next_chunk__  __size__    val       
----------  ----------  --------------  ----------  ----------
1                                       1           1         
2                                       1           3         
3           2                           2           2         
4           3                           3           1    
  • array 管理用に自動的に s__foo__0 というテーブルが作成され、ここに array のデータが入るらしい。
  • 空array に対しては NULL が入っているっぽい。
  • s__foo__0.val が array 内の値。
  • s__foo__0.__next__ が array 内の次のレコードの s__foo__0.__id__ を指す。つまり、s.__id__2 のレコードに着目すると、
    • s.foo=4 なので、まずは s__foo__0.__id__ = 4 のレコードに辿り着く(val = 1)。
    • すると、__next__ = 3 なので、s__foo__0.__id__ = 3 のレコードに辿り着く(val = 2)。
    • すると、__next__ = 2 なので、s__foo__0.__id__ = 2 のレコードに辿り着く(val = 3)。
    • すると、__next__ = NULL なので、ここで array はおしまい。
  • __size____next_chunk__ がなぜ必要なのか謎だけど、軽くソースを grep した感じだと、array を取ってくるために s__foo__0 をひたすら自己参照で JOIN しまくっているらしいんだけど、長い array だと JOIN が大きくなりすぎるので、64 個ずつに区切って取得してくるのに使っているっぽい。うーん、あんまり RDBMS の機能を活かしてないマッピングな気がするが、良いんだろうか?

list_simple

list_simple.mlから抜粋
type x = {
  foo: string list;
  bar: (char * string) list;
  pla: bool;
} with orm(debug:none)

let x1 = { foo=["a1";"a2";"a3";"a4"]; bar=[ ('a',"AA"); ('b',"BB"); ('c',"CC") ] ; pla=true }

list と array で何か違いがあるのか。タプルの list はどうなるのか、
辺りに注目ですね。

sqlite> .tables
__links__  __types__  x          x__bar__0  x__foo__0
sqlite> .schema
CREATE TABLE __links__ (parent TEXT, field TEXT, child TEXT);
CREATE TABLE __types__ (n TEXT, t TEXT);
CREATE TABLE x (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, foo INTEGER,bar INTEGER,pla INTEGER);
CREATE TABLE x__bar__0 (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, __next__ INTEGER,__next_chunk__ INTEGER,__size__ INTEGER,__1 INTEGER,__2 STRI
NG);
CREATE TABLE x__foo__0 (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, __next__ INTEGER,__next_chunk__ INTEGER,__size__ INTEGER,val STRING);
CREATE UNIQUE INDEX idx_x__bar__0_enum ON x__bar__0 (__id__,__next__);
CREATE UNIQUE INDEX idx_x__foo__0_enum ON x__foo__0 (__id__,__next__);
CREATE TRIGGER x_bar_cleanup AFTER UPDATE OF bar ON x FOR EACH ROW BEGIN SELECT CLEAN_UP_x__bar__0(OLD.bar,NEW.bar); END;
CREATE TRIGGER x_foo_cleanup AFTER UPDATE OF foo ON x FOR EACH ROW BEGIN SELECT CLEAN_UP_x__foo__0(OLD.foo,NEW.foo); END;
CREATE TRIGGER x_update_cache AFTER DELETE ON x FOR EACH ROW BEGIN SELECT SYNC_CACHE_x(OLD.__id__); END;
sqlite> select * from __links__;
parent      field       child     
----------  ----------  ----------
x           bar         x__bar__0 
x           foo         x__foo__0 
sqlite> select * from __types__;
n           t         
----------  ----------
x__bar__0   [(C*S)]   
x__foo__0   [S]       
x           E/x/{Rfoo:
sqlite> select * from x;
__id__      foo         bar         pla       
----------  ----------  ----------  ----------
1           4           3           1         
sqlite> select * from x__bar__0;
__id__      __next__    __next_chunk__  __size__    __1         __2       
----------  ----------  --------------  ----------  ----------  ----------
1                                       1           99          CC        
2           1                           2           98          BB        
3           2                           3           97          AA        
sqlite> select * from x__foo__0;
__id__      __next__    __next_chunk__  __size__    val       
----------  ----------  --------------  ----------  ----------
1                                       1           a4        
2           1                           2           a3        
3           2                           3           a2        
4           3                           4           a1  
  • array と同じっぽい。
  • タプルの array だと __1 とか __2 というカラム名になる。

alltypes

alltypes.mlから抜粋
type x = {
  one: char;
  two: string;
  three: int;
  four: int32;
  five: bool;
  six: int64;
  seven: unit;
  eight: string option;
  nine: float;
  ten: (int * string);
  eleven: string list;
  twelve: (char * int32 * unit) option;
  thirteen: (char * (string * int64) option);
}
and y=int with orm

let x = { one='a'; two="foo"; three=1; four=2l; 
     five=true; six=3L; seven=(); eight=(Some "bar");
     nine=6.9; ten=(100,"hello"); eleven=["aa";"bb";"cc"];
     twelve=(Some ('t',9l,())); thirteen=('d', (Some ("abc",999L))) }

後は unit ぐらいがわかれば十分かなという感じがするので、
unit を使っている seven と twelve だけ見ますよ。

sqlite> .tables
__links__     __types__     x             x__eleven__0
sqlite> .schema
CREATE TABLE __links__ (parent TEXT, field TEXT, child TEXT);
CREATE TABLE __types__ (n TEXT, t TEXT);
CREATE TABLE x (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, one INTEGER,two STRING,three INTEGER,four INTEGER,five INTEGER,six INTEGER,seven INTE
GER,eight__0__isset INTEGER,eight__0 STRING,nine FLOAT,ten__1 INTEGER,ten__2 STRING,eleven INTEGER,twelve__0__isset INTEGER,twelve__0__1 INTEGE
R,twelve__0__2 INTEGER,twelve__0__3 INTEGER,thirteen__1 INTEGER,thirteen__2__0__isset INTEGER,thirteen__2__0__1 STRING,thirteen__2__0__2 INTEGE
R);
CREATE TABLE x__eleven__0 (__id__ INTEGER PRIMARY KEY AUTOINCREMENT, __next__ INTEGER,__next_chunk__ INTEGER,__size__ INTEGER,val STRING);
CREATE UNIQUE INDEX idx_x__eleven__0_enum ON x__eleven__0 (__id__,__next__);
CREATE TRIGGER x_eleven_cleanup AFTER UPDATE OF eleven ON x FOR EACH ROW BEGIN SELECT CLEAN_UP_x__eleven__0(OLD.eleven,NEW.eleven); END;
CREATE TRIGGER x_update_cache AFTER DELETE ON x FOR EACH ROW BEGIN SELECT SYNC_CACHE_x(OLD.__id__); END;
sqlite> select * from __links__;
parent      field       child       
----------  ----------  ------------
x           eleven      x__eleven__0
sqlite> select * from __types__;
n             t         
------------  ----------
x__eleven__0  [S]       
x             E/x/{Rone:

sqlite> select seven, twelve__0__isset, twelve__0__1, twelve__0__2, twelve__0__3 from x;
seven       twelve__0__isset  twelve__0__1  twelve__0__2  twelve__0__3
----------  ----------------  ------------  ------------  ------------
0           1                 116           9             0  
  • unit は 0 を入れてるっぽい?

以上、おしまい。

foreign は RDBMS のフツーの使い方だと思うんだけど、array とか list は「オブジェクトのシリアライズ先がたまたま RDB でした」って感じで全然 RDB っぽくないと思った。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1