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>
でt
のxyz
にユニークインデックスが生成される。 -
index: x<first,second>
で複合インデックス。 -
x.first
もx.second
もt.__id__
を参照している。実にシンプル。なんだけど、second
がt
を参照してるってのはどうやってわかるんだろうなぁ?__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 のテストを削除してビルド&実行し直した。
おそらく主旨としては、x
が t
を参照し、t
も x
を参照する
というところなんだろうけど、これ既に 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__0
が1
でx.__id__
が1
のレコードを参照。x.x1__0
が1
でt.__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 っぽくないと思った。