【ent】複合主キーで `edge schema primary key can only be defined on "id" or ... in the same order` が発生した場合の対処法

複合主キー関連のその他のエラーはこちら

https://qiita.com/Syuparn/items/cb473dbf5c9917e52f46

# TL; DR

複合主キーの `field.ID` の引数の順序を逆にすれば解消する

```go
func (StudentCourse) Annotations() []schema.Annotation {
	return []schema.Annotation{
		// 複合主キー
		field.ID("student_id", "course_id"),
	}
}
```

# エラーの再現

まずはスキーマを作成します。今回は以下のテーブルを考えます。

```sql
-- 講義受講者の成績
CREATE TABLE StudentCourses
(student_id integer NOT NULL.
 course_id integer NOT NULL,
 grade integer NOT NULL
PRIMARY KEY (student_id, course_id));

-- 学生一覧
CREATE TABLE Students
(id integer NOT NULL,
 name varchar(255) NOT NULL
PRIMARY KEY (id));

-- 講義一覧
CREATE TABLE Courses
(id integer NOT NULL,
 name varchar(255) NOT NULL
PRIMARY KEY (id));
```

複合主キーと関連テーブルを以下のように定義します。

```go
// StudentCourse holds the schema definition for the StudentCourse entity.
type StudentCourse struct {
	ent.Schema
}

func (StudentCourse) Annotations() []schema.Annotation {
	return []schema.Annotation{
		// 複合主キーを指定（デフォルトでは `id` というフィールドが主キーとなる）
		field.ID("course_id", "student_id"),
	}
}

// Fields of the StudentCourse.
func (StudentCourse) Fields() []ent.Field {
	return []ent.Field{
		field.Int("student_id"),
		field.Int("course_id"),
		field.Int("grade"),
	}
}

// Edges of the StudentCourse.
func (StudentCourse) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("student", Student.Type).
			Unique().
			Required().
			Field("student_id"),
		edge.To("course", Course.Type).
			Unique().
			Required().
			Field("course_id"),
	}
}
```

```go
// ...
// Fields of the Student.
func (Student) Fields() []ent.Field {
	return []ent.Field{
		field.Int("id"),
		field.String("name"),
	}
}

// Edges of the Student.
func (Student) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("registered_courses", Course.Type).
			Through("student_courses", StudentCourse.Type),
	}
}
```

```go
// ...
// Fields of the Course.
func (Course) Fields() []ent.Field {
	return []ent.Field{
		field.Int("id"),
		field.String("name"),
	}
}

// Edges of the Course.
func (Course) Edges() []ent.Edge {
	// 逆向きの関連をこちらにも指定
	return []ent.Edge{
		edge.From("students", Student.Type).
			Ref("registered_courses").
			Through("student_courses", StudentCourse.Type),
	}
}
```

`Edge()` を定義している理由については以下の記事をご覧ください。



この状態でソースコードを自動生成使用とすると以下のエラーが発生します。

```bash
$ go generate ./ent
entc/gen: resolving edges: edge schema primary key can only be defined on "id" or ("student_id", "course_id") in the same order
exit status 1
ent/generate.go:3: running "go": exit status 1
```

# 対処法

エラーメッセージにもあるように、複合主キーを `("student_id", "course_id")` の順序で定義することで解消します。

```diff
func (StudentCourse) Annotations() []schema.Annotation {
	return []schema.Annotation{
		// 複合主キーを指定（デフォルトでは `id` というフィールドが主キーとなる）
-		field.ID("course_id", "student_id"),
+		field.ID("student_id", "course_id"),
	}
}
```

これは、複合主キーとedge schemaを対応付ける際に順序依存で比較を行っているためです。

https://github.com/ent/ent/blob/9627017062f65baac5859b717234edbaf7f2803b/entc/gen/graph.go#L598

```go
			// Edge schema contains a composite primary key, and it was not resolved in previous iterations.
			if ant := fieldAnnotate(edgeT.Annotations); ant != nil && len(ant.ID) > 0 && len(edgeT.EdgeSchema.ID) == 0 {
				r1, r2 := e.Rel.Columns[0], e.Rel.Columns[1]
				if len(ant.ID) != 2 || ant.ID[0] != r1 || ant.ID[1] != r2 {
					return fmt.Errorf(`edge schema primary key can only be defined on "id" or (%q, %q) in the same order`, r1, r2)
				}
				// ...
			}
```

`field.ID` の引数は

- `edge.To` で指定したもの
- `edge.From` で指定したもの

の順に指定する必要があります。

そのため、`edge.To` で指定した `student` に対応する `student_id` が先行することで解消されます。

```go:entc/gen/graph.go
// Edges of the StudentCourse.
func (StudentCourse) Edges() []ent.Edge {
	return []ent.Edge{
		edge.To("student", Student.Type).
			Unique().
			Required().
			Field("student_id"),
		edge.To("course", Course.Type).
			Unique().
			Required().
			Field("course_id"),
	}
}
```