본론에 앞서 golang ORM인 entgo는 문서화가 생각보다 부실하다. 예제가 부족해서 개발자가 부딪혀가며 배울 수 밖에 없다...
ORM은 raw sql query를 작성하지 않고도 어플리케이션 개발 언어를 통해서 db 조작을 할 수 있게 하는 기술이다. 근데 가끔씩 ORM이 제공해주지 않는 기능이 있다. 이러한 기능들은 DB 벤더마다 다르다. 내 기억 상으로 Spring JPA는 native query라는 것을 활용하는 걸로 알고 있다. 이 기능은 DB 벤더가 바뀌면 작동하지 않는다는 문제가 있다. (ORM 추상화를 포기하는 거라 보면 된다)
뭐 어쨋든, 나는 LENGTH
라는 db function이 golang ORM인 entgo에도 있는 줄 알았다. 근데 없어.... 그래서 커스텀 Sql로 진행했다.
먼저 공식 문서 출처부터 제공한다.
https://entgo.io/docs/feature-flags#custom-sql-modifiers
사용자 user
와 애완동물 pet
이 있다고 하자. 일대다 관계를 맺고 있다고 하자. 만약 애완동물 이름인 뽀삐인 애완동물들의 주인 중에서 이름이 가장 짧은 주인의 정보를 얻고 싶다고 해보자. (예시가 억지스러운 건 양해바란다. 좀 급조했다.)
SELECT u.id, LENGTH(u.name)as name_len ,u.name
FROM pets p
INNER JOIN users u ON p.user_pets = u.id
where p.name = '뽀삐'
GROUP BY LENGTH(u.name), p.id
ORDER BY name_len
LIMIT 1;
elem, err := client.User.Query().
Modify(
func(s *sql.Selector) {
t1 := sql.Table(user.Table).As("u")
t2 := sql.Table(pet.Table).As("p")
s.Select(
t1.C(user.FieldID),
sql.As("LENGTH(u.name)", "name_len"),
t1.C(user.FieldName),
).
From(t1).
Join(t2).
On(t1.C(user.FieldID), t2.C(pet.UserColumn)).
Where(sql.EQ(t2.C(pet.FieldName), "뽀삐")).
GroupBy("name_len", pet.FieldID).
OrderBy("name_len").
Limit(1)
},
).
Only(ctx)
해설
1. Modify
: custom Sql을 만드는 데 쓰이는 코드다.
2. t1.C(user.FieldID)
: 여기서 C는 칼럼의 약자다. 즉, t1 테이블의 id 칼럼이라는 뜻이다. 이 때 t1은 user 테이블이다.
3. sql.As("LENGTH(u.name)", "name_len")
: db의 as 구문을 사용할 수 있다. 이 때, 첫 번째 인자는 raw query인 string을 넣으면 된다. 잘 보면 알겠지만, t1 := sql.Table(user.Table).As("u")
에서 지정된 별칭 u
를 쓰고 있다. sql ambiguous error가 발생하면 이를 통해 해결해보자.
4. 다음 결과는 User
엔티티에 저장된다. 즉, Id와 name은 User
엔티티에 저장된다. 다른 필드는 nil로 처리된다.
s.Select(
t1.C(user.FieldID),
sql.As("LENGTH(u.name)", "name_len"),
t1.C(user.FieldName),
).