【ITニュース解説】Enforcing XOR (Either/Or) Fields in SurrealDB
2025年09月05日に「Dev.to」が公開したITニュース「Enforcing XOR (Either/Or) Fields in SurrealDB」について初心者にもわかりやすいように丁寧に解説しています。
ITニュース概要
SurrealDBで、あるフィールドが2つのうちどちらか一方のみを持つXOR制約を実装する方法を紹介。リポジトリのオーナーをユーザーまたは組織のどちらか一方に限定する例を挙げ、DEFINE FIELD文とTHROW文を使って制約を定義する。SQLのCHECK制約と同等の機能を、より簡潔に実現する別の方法も提示。データベーススキーマ自体に制約を組み込むことで、データの整合性を保つ。
ITニュース解説
SurrealDBでXOR制約(どちらか一方)を実装する方法
データベース設計において、レコードが2つのフィールドのうち1つだけを持ち、両方同時またはどちらも持たない状況がある。たとえば、リポジトリはユーザーまたは組織のいずれかが所有するが、両方同時、またはどちらでもない、ということはありえない。これはXOR(排他的論理和)制約と呼ばれる。
この状況は、ERモデリングではアーク関係としても知られている。ER図では、関係線に弧を描くことで示される。
SurrealDBでアーク関係をモデル化する方法を、リポジトリテーブルのスキーマ定義を通して説明する。
まず、いくつかのDEFINEステートメントを使用してスキーマを定義する。
1DEFINE TABLE repository SCHEMAFULL; 2 3DEFINE FIELD name ON repository TYPE string; 4-- XORチェックの前にフィールドを省略できるように`option<table_name>`を使用 5DEFINE FIELD user ON repository TYPE option<record<user>>; 6DEFINE FIELD organization ON repository TYPE option<record<organization>>;
次に、XORチェックを行う。VALUE句を使用して値を設定し、XOR制約に違反した場合にTHROWを使用してエラーを返す。
1-- SurrealDBのフィールドはキーと値のペアで表現される 2-- `VALUE`はフィールドの生の "値" を設定するために使用され、"キー" は省略される 3DEFINE FIELD owner_type ON repository VALUE { 4 -- 1. どちらも設定されていないかを確認 5 IF user = NONE AND organization = NONE { 6 THROW "リポジトリには、ユーザーまたは組織のオーナーが必要" 7 } 8 -- 2. 両方設定されているかを確認 9 ELSE IF user != NONE AND organization != NONE { 10 THROW "リポジトリはユーザーと組織の両方のオーナーを持つことはできない" 11 }; 12 -- 3. (オプション) バリデーションが成功した場合、オーナータイプを返す 13 RETURN IF user != NONE { 14 'user'; 15 } ELSE { 16 'organization'; 17 }; 18};
考えられる4つのケースをテストする。
1-- ユーザーオーナーのみを持つリポジトリの作成 2CREATE repository SET 3 name = 'my_repo', 4 user = user:abdo; 5 6-- 組織オーナーのみを持つリポジトリの作成 7CREATE repository SET 8 name = 'my_repo', 9 organization = organization:abdo; 10 11-- エラー: ユーザーと組織の両方が設定されているため失敗する 12CREATE repository SET 13 name = 'hamadas_repo', 14 user = user:hamada, 15 organization = organization:hamada_org; 16-- スロー: 'エラーが発生しました: リポジトリはユーザーと組織の両方のオーナーを持つことはできません' 17 18-- エラー: ユーザーも組織も設定されていないため失敗する 19CREATE repository SET 20 name = 'hamada_repo'; 21-- スロー: 'エラーが発生しました: リポジトリには、ユーザーまたは組織のオーナーが必要です'
SurrealDB Discordでjimpexによって提案された、より簡潔なアプローチがある。
1DEFINE FIELD owner ON repository TYPE record<user | organization>; 2DEFINE FIELD owner_type ON repository VALUE record::tb(owner); 3 4CREATE ONLY repository SET owner = user:andy; 5CREATE ONLY repository SET owner = organization:dunder_mifflin; 6CREATE ONLY repository SET owner = fail:test;
これは、ownerフィールドをユーザーまたは組織のレコードのいずれかとして定義し、owner_typeフィールドをownerフィールドのタイプに基づいて自動的に設定する方法だ。
リレーショナルデータベースの知識がある人向けに、SQLでの同等のロジックを示す。
1CREATE TABLE users ( 2 id INT PRIMARY KEY, 3 name VARCHAR(255) NOT NULL 4); 5 6CREATE TABLE organizations ( 7 id INT PRIMARY KEY, 8 name VARCHAR(255) NOT NULL 9); 10 11CREATE TABLE repository ( 12 id INT PRIMARY KEY, 13 name VARCHAR(255) NOT NULL, 14 15 user_id INT, 16 organization_id INT, 17 18 FOREIGN KEY (user_id) REFERENCES users(id), 19 FOREIGN KEY (organization_id) REFERENCES organizations(id), 20 21 CONSTRAINT owner_xor_check CHECK ( 22 (user_id IS NOT NULL AND organization_id IS NULL) 23 OR 24 (user_id IS NULL AND organization_id IS NOT NULL) 25 ) 26);
このアプローチでは、バリデーションロジックをアプリケーションからスキーマ自体に移動するため、データベースが信頼できる唯一の情報源となる。CHECK制約は、データベースレベルでデータの整合性を保証するのに役立つ。
この記事では、アーク関係とSurrealDBでXORフィールドを実装する方法について解説した。