DrizzleORM v0.28.0 릴리스
Aug 6, 2023

주요 변경 사항

중첩 관계 필터링 지원 중단

0.28.0 버전부터는 아래 예제가 동작하지 않습니다:

const usersWithPosts = await db.query.users.findMany({
  where: (table, { sql }) => (sql`json_array_length(${table.posts}) > 0`),
  with: {
    posts: true,
  },
});

where 콜백 내의 table 객체는 더 이상 withextras로부터 필드를 포함하지 않습니다. 이를 제거함으로써 더 효율적인 관계형 쿼리를 구축할 수 있게 되었고, 이는 행 읽기와 성능을 개선했습니다.

이전에 where 콜백에서 해당 필드를 사용했다면, 다음과 같은 해결 방법을 고려할 수 있습니다:

  1. 행을 가져온 후 코드 레벨에서 필터를 직접 적용하기
  2. 코어 API 사용하기

mysql2 드라이버에 관계형 쿼리 mode 설정 추가

Drizzle의 관계형 쿼리는 항상 하나의 SQL 문을 생성하여 데이터베이스에서 실행합니다. 하지만 이 방식에는 몇 가지 주의사항이 있습니다. 모든 데이터베이스에 최적의 지원을 제공하기 위해 모드 설정을 도입했습니다.

Drizzle의 관계형 쿼리는 내부적으로 서브쿼리의 Lateral Join을 사용합니다. 현재 PlanetScale은 이 기능을 지원하지 않습니다.

import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
import * as schema from './schema';

const client = await mysql.createConnection({
  uri: process.env.PLANETSCALE_DATABASE_URL,
});

const db = drizzle({ client, schema, mode: 'planetscale' });

대규모 스키마에서 IntelliSense 성능 개선

85개의 테이블, 666개의 컬럼, 26개의 열거형, 172개의 인덱스, 133개의 외래 키로 구성된 데이터베이스 스키마에 대해 진단을 수행했습니다. 내부 타입을 최적화하여 IntelliSense 속도가 430% 빨라졌습니다.

관계형 쿼리 성능 및 읽기 사용성 개선

이번 릴리스에서는 관계형 쿼리 API를 위한 쿼리 생성 방식을 완전히 변경했습니다. 주요 변경 사항은 다음과 같습니다:

  1. Lateral 조인: 새 버전에서는 “LEFT JOIN LATERAL” 절을 사용하여 관련 테이블에서 특정 데이터를 효율적으로 가져옵니다. MySQL(PlanetScale)과 SQLite에서는 간단한 서브쿼리 선택을 사용하여 쿼리 계획과 전반적인 성능을 개선했습니다.

  2. 선택적 데이터 검색: 새 버전에서는 테이블에서 필요한 데이터만 검색합니다. 이렇게 특정 데이터만 가져오면 불필요한 정보를 줄여 처리할 데이터셋이 작아지고 실행 속도가 빨라집니다.

  3. 집계 함수 감소: 새 버전에서는 집계 함수(예: COUNT, json_agg)의 수를 줄였습니다. Lateral 조인 내에서 json_build_array를 직접 사용하여 데이터를 더 효율적으로 집계함으로써 쿼리 성능을 개선했습니다.

  4. 단순화된 그룹화: 새 버전에서는 GROUP BY 절을 제거했습니다. Lateral 조인과 서브쿼리가 이미 데이터 집계를 더 효율적으로 처리하기 때문입니다.

다음은 Drizzle 쿼리 예제입니다:

const items = await db.query.comments.findMany({
  limit,
  orderBy: comments.id,
  with: {
    user: {
      columns: { name: true },
    },
    post: {
      columns: { title: true },
      with: {
        user: {
          columns: { name: true },
        },
      },
    },
  },
});

새로 생성된 쿼리:

SELECT "comments"."id",
       "comments"."user_id",
       "comments"."post_id",
       "comments"."content",
       "comments_user"."data" AS "user",
       "comments_post"."data" AS "post"
FROM "comments"
LEFT JOIN LATERAL (
    SELECT json_build_array("comments_user"."name") AS "data"
    FROM (
        SELECT *
        FROM "users" "comments_user"
        WHERE "comments_user"."id" = "comments"."user_id"
        LIMIT 1
    ) "comments_user"
) "comments_user" ON true
LEFT JOIN LATERAL (
    SELECT json_build_array("comments_post"."title", "comments_post_user"."data") AS "data"
    FROM (
        SELECT *
        FROM "posts" "comments_post"
        WHERE "comments_post"."id" = "comments"."post_id"
        LIMIT 1
    ) "comments_post"
    LEFT JOIN LATERAL (
        SELECT json_build_array("comments_post_user"."name") AS "data"
        FROM (
            SELECT *
            FROM "users" "comments_post_user"
            WHERE "comments_post_user"."id" = "comments_post"."user_id"
            LIMIT 1
        ) "comments_post_user"
    ) "comments_post_user" ON true
) "comments_post" ON true
ORDER BY "comments"."id"
LIMIT 1

이전에 생성된 쿼리:

SELECT "id",
       "user_id",
       "post_id",
       "content",
       "user"::JSON,
       "post"::JSON
FROM (
    SELECT "comments".*,
           CASE
               WHEN count("comments_post"."id") = 0 THEN '[]'
               ELSE json_agg(json_build_array("comments_post"."title", "comments_post"."user"::JSON))::text
           END AS "post"
    FROM (
        SELECT "comments".*,
               CASE
                   WHEN count("comments_user"."id") = 0 THEN '[]'
                   ELSE json_agg(json_build_array("comments_user"."name"))::text
               END AS "user"
        FROM "comments"
        LEFT JOIN (
            SELECT "comments_user".*
            FROM "users" "comments_user"
        ) "comments_user" ON "comments"."user_id" = "comments_user"."id"
        GROUP BY "comments"."id",
                 "comments"."user_id",
                 "comments"."post_id",
                 "comments"."content"
    ) "comments"
    LEFT JOIN (
        SELECT "comments_post".*
        FROM (
            SELECT "comments_post".*,
                   CASE
                       WHEN count("comments_post_user"."id") = 0 THEN '[]'
                       ELSE json_agg(json_build_array("comments_post_user"."name"))
                   END AS "user"
            FROM "posts" "comments_post"
            LEFT JOIN (
                SELECT "comments_post_user".*
                FROM "users" "comments_post_user"
            ) "comments_post_user" ON "comments_post"."user_id" = "comments_post_user"."id"
            GROUP BY "comments_post"."id"
        ) "comments_post"
    ) "comments_post" ON "comments"."post_id" = "comments_post"."id"
    GROUP BY "comments"."id",
             "comments"."user_id",
             "comments"."post_id",
             "comments"."content",
             "comments"."user"
) "comments"
LIMIT 1

더 자세한 내용은 관계형 쿼리 문서에서 확인할 수 있습니다.

모든 컬럼에 기본값을 사용해 행 삽입하기

이제 빈 객체나 빈 객체 배열을 제공하면 Drizzle이 데이터베이스에 모든 기본값을 삽입합니다.

// 모든 기본값을 사용해 1개의 행 삽입
await db.insert(usersTable).values({});

// 모든 기본값을 사용해 2개의 행 삽입
await db.insert(usersTable).values([{}, {}]);