Deno で試すデータベースアクセス(SurrealDB編 - 2)

SurrealDB は、JOIN しないで効率よくデータが取れることを売りにしているところがある。
この機能は Record Linksと紹介されている。
が、親から子は非常に取りやすいのだが、子から親の方向に辿ろうとすると、苦しいものだったとわかった。

今回は、もう1つのグラフエッジを作成してデータ連携させる RELATE を試したのでメモしておく。
こちらの方が、よほど親->子、子->親も階層が深くても取りやすかった。

参考

動作

RELATE

Deno 向けSDKにはドキュメントに、RELATEを使えるメソッドの設定はないようなので .query を使って設定していく。

app-10.ts(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
type Place = {
id?: string;
name: string;
address: string;
};

type Food = {
id?: string;
name: string;
price: number;
currency: string;
};

type FoodWithProductionArea = Omit<Food, "productionArea"> & { productionArea: Place };

const [placeA] = await db.create<Place>("place", {
name: "tokyo",
address: "JPN-0000-0000",
});

const [apple] = await db.create<Food>("food", {
name: "apple",
price: 100,
currency: "JPY",
});

let result = await db.query(
`
RELATE $from->productionArea->$to;
RELATE $to->production->$from;
`,
{
from: apple.id,
to: placeA.id,
}
);
console.log(JSON.stringify(result, null, 2));
// => [
// {
// "time": "59.7µs",
// "status": "OK",
// "result": [
// {
// "id": "productionArea:ed1pez27btn4szuzka0m",
// "in": "food:w6e07nlp8a7jiia3e6rb",
// "out": "place:cjtg87991qgho4pfa42y"
// }
// ]
// }
// ]

console.log(await db.select("productionArea"));
// => [
// {
// id: "productionArea:ed1pez27btn4szuzka0m",
// in: "food:w6e07nlp8a7jiia3e6rb",
// out: "place:cjtg87991qgho4pfa42y"
// }
// ]

RELATE を使うと関連付けの名前のテーブルを作成し、関連付ける2つのレコードを inout のふたつで管理されている。

この、エッジを使ってデータの引き当てをすると次のようになる。

app-11.ts(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
await db.query(
`RELATE $from->productionArea->$to;`,
{
from: apple.id,
to: placeA.id,
}
);

let result = await db.query<FoodWithProductionArea[]>(
`
SELECT *, ->productionArea->place.*
FROM $id
`,
{
id: apple.id,
}
);
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "75.1µs",
// "status": "OK",
// "result": [
// {
// "->productionArea": {
// "->place": [
// {
// "address": "JPN-0000-0000",
// "id": "place:idrkpyyxnsvw1917db0z",
// "name": "tokyo"
// }
// ]
// },
// "currency": "JPY",
// "id": "food:fsee8nwfa87n7sn19sp9",
// "name": "apple",
// "price": 100
// }
// ]
// }

result = await db.query<FoodWithProductionArea[]>(
`
SELECT *, <-productionArea<-food.* as food
FROM $id
`,
{
id: placeA.id,
}
);
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "68.2µs",
// "status": "OK",
// "result": [
// {
// "address": "JPN-0000-0000",
// "food": [
// {
// "currency": "JPY",
// "id": "food:fsee8nwfa87n7sn19sp9",
// "name": "apple",
// "price": 100
// }
// ],
// "id": "place:idrkpyyxnsvw1917db0z",
// "name": "tokyo"
// }
// ]
// }

-><- を使って、親子関係無く引き当てができる。
キーへ -> が入ってくるので、適宜 as を使う方がきれいに収まる(見える)。

RELATE を一意にする

何も対応しないと RELATE は、多重に貼れてしまう。なのでこんなことが起きる。

app-12.ts(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
await db.query(
`
RELATE $from->productionArea->$to;
RELATE $from->productionArea->$to;
RELATE $from->productionArea->$to;
`,
{
from: apple.id,
to: placeA.id,
}
);

let result = await db.query<FoodWithProductionArea[]>(
`
SELECT *, ->productionArea->place.*
FROM $id
`,
{
id: apple.id,
}
);
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "141.5µs",
// "status": "OK",
// "result": [
// {
// "->productionArea": {
// "->place": [
// {
// "address": "JPN-0000-0000",
// "id": "place:b5jimtltooezukawfivg",
// "name": "tokyo"
// },
// {
// "address": "JPN-0000-0000",
// "id": "place:b5jimtltooezukawfivg",
// "name": "tokyo"
// },
// {
// "address": "JPN-0000-0000",
// "id": "place:b5jimtltooezukawfivg",
// "name": "tokyo"
// }
// ]
// },
// "currency": "JPY",
// "id": "food:w6uoa807f1pa0f12bo03",
// "name": "apple",
// "price": 100
// }
// ]
// }

RELATEが多重になっているので、placeが3件出てしまう。

これについては、ドキュメントに対応が記載されており、unique index を貼る方法が紹介されている。

app-12-1.ts(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// unique index を設定
await db.query(
`
DEFINE INDEX unique_relate_productionArea
ON TABLE productionArea
COLUMNS in, out UNIQUE;
`
);

const relateResult = await db.query(
`
RELATE $from->productionArea->$to;
RELATE $from->productionArea->$to;
RELATE $from->productionArea->$to;
`,
{
from: apple.id,
to: placeA.id,
}
);

// 2件目の RELATE からエラーになっている
console.log(relateResult);
// => [
// {
// time: "97.2µs",
// status: "OK",
// result: [
// {
// id: "productionArea:04yearab5riaci4r2hr1",
// in: "food:cajstv9i0tni3c9s5v6o",
// out: "place:1s7wsjb4u16dcomr6x4p"
// }
// ]
// },
// {
// time: "37µs",
// status: "ERR",
// detail: "Database index `unique_relate_productionArea` already contains [food:cajstv9i0tni3c9s5v6o, place:1s7"... 69 more characters
// },
// {
// time: "27.8µs",
// status: "ERR",
// detail: "Database index `unique_relate_productionArea` already contains [food:cajstv9i0tni3c9s5v6o, place:1s7"... 69 more characters
// }
// ]

let result = await db.query<FoodWithProductionArea[]>(
`
SELECT *, ->productionArea->place.*
FROM $id
`,
{
id: apple.id,
}
);

// RELATE は1件しか成立してないので、引き当てられるplaceも1件だけ
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "80.1µs",
// "status": "OK",
// "result": [
// {
// "->productionArea": {
// "->place": [
// {
// "address": "JPN-0000-0000",
// "id": "place:8qof7ixagpzwy9fjj55q",
// "name": "tokyo"
// }
// ]
// },
// "currency": "JPY",
// "id": "food:ak9xy7ly049cndmkytql",
// "name": "apple",
// "price": 100
// }
// ]
// }

以上の設定で多重に引き当ててしまうことはなくなった。

グラフエッジの削除

他にも機能はあるが最後にグラフエッジを削除してみる。

公式のドキュメントよりも、Issue の方が親切だった。

Documentation: Unrelating a relation

その上で DELETE $from->productionArea WHERE out=$to;
もしくは DELETE FROM $from->productionArea WHERE out=$to; は動かなかった。

app-13.ts(抜粋)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
const [placeA] = await db.create<Place>("place", {
name: "tokyo",
address: "JPN-0000-0000",
});

const [apple] = await db.create<Food>("food", {
name: "apple",
price: 100,
currency: "JPY",
});

await db.query(`RELATE $from->productionArea->$to;`, {
from: apple.id,
to: placeA.id,
});

let result = await db.query<FoodWithProductionArea[]>(
`SELECT *, ->productionArea->place.* FROM $id`,
{
id: apple.id,
}
);
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "72.4µs",
// "status": "OK",
// "result": [
// {
// "->productionArea": {
// "->place": [
// {
// "address": "JPN-0000-0000",
// "id": "place:34o3y2ltcu1fcbq3nzmb",
// "name": "tokyo"
// }
// ]
// },
// "currency": "JPY",
// "id": "food:cp0h6a27rsbh7fuf3hlp",
// "name": "apple",
// "price": 100
// }
// ]
// }

await db.query(`DELETE productionArea WHERE in = $from AND out=$to;`, {
from: apple.id,
to: placeA.id,
});

result = await db.query<FoodWithProductionArea[]>(
`SELECT *, ->productionArea->place.* FROM $id`,
{
id: apple.id,
}
);
console.log(JSON.stringify(result[0], null, 2));
// => {
// "time": "55.7µs",
// "status": "OK",
// "result": [
// {
// "->productionArea": {
// "->place": []
// },
// "currency": "JPY",
// "id": "food:cp0h6a27rsbh7fuf3hlp",
// "name": "apple",
// "price": 100
// }
// ]
// }

issue を見ると DELETE productionArea WHERE in = $from AND out=$to; の形式は、効率が悪いらしい。
が、先に uniq index を貼って有れば、in out で検索しても(index がRDBでイメージするもの相当なら)十分パフォーマンスが出そうではある。


SurrealDB をもう少しいじってみた。これだけでも全体はまだ触れていないが、積極的に使いたい。

では。