동적 쿼리 λΉŒλ”©

Drizzle의 λͺ¨λ“  쿼리 λΉŒλ”λŠ” 기본적으둜 SQL에 μ΅œλŒ€ν•œ λ§žμΆ”λ €κ³  ν•˜κΈ° λ•Œλ¬Έμ—, λŒ€λΆ€λΆ„μ˜ λ©”μ„œλ“œλŠ” ν•œ 번만 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, SELECT λ¬Έμ—μ„œλŠ” WHERE 절이 ν•˜λ‚˜λ§Œ μžˆμ„ 수 μžˆμœΌλ―€λ‘œ .where() λ©”μ„œλ“œλŠ” ν•œ 번만 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€:

const query = db
    .select()
    .from(users)
    .where(eq(users.id, 1))
    .where(eq(users.name, 'John')); // ❌ νƒ€μž… μ—λŸ¬ - where()은 ν•œ 번만 ν˜ΈμΆœν•  수 있음

이전 ORM λ²„μ „μ—μ„œλŠ” μ΄λŸ¬ν•œ μ œν•œμ΄ κ΅¬ν˜„λ˜μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ—, λ§Žμ€ μ‚¬μš©μžλ“€μ΄ μ—¬λŸ¬ .where() ν˜ΈμΆœμ„ ν•˜λ‚˜μ˜ 쑰건으둜 β€œλ³‘ν•©β€ν•  κ²ƒμœΌλ‘œ κΈ°λŒ€ν•˜λ©° ν˜Όλž€μ„ κ²ͺμ—ˆμŠ΅λ‹ˆλ‹€.

μ΄λŸ¬ν•œ λ™μž‘μ€ 일반적인 쿼리 λΉŒλ”©, 즉 전체 쿼리λ₯Ό ν•œ λ²ˆμ— 생성할 λ•Œ μœ μš©ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ 쿼리λ₯Ό λ™μ μœΌλ‘œ λΉŒλ“œν•˜λ €λŠ” 경우, 예λ₯Ό λ“€μ–΄ 쿼리 λΉŒλ”λ₯Ό λ°›μ•„μ„œ μΆ”κ°€ κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” 곡유 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•  λ•Œ λ¬Έμ œκ°€ λ©λ‹ˆλ‹€. 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ Drizzle은 쿼리 λΉŒλ”μ— νŠΉλ³„ν•œ β€˜λ™μ β€™ λͺ¨λ“œλ₯Ό μ œκ³΅ν•˜λ©°, 이 λͺ¨λ“œμ—μ„œλŠ” λ©”μ„œλ“œλ₯Ό μ—¬λŸ¬ 번 ν˜ΈμΆœν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이 λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•˜λ €λ©΄ 쿼리 λΉŒλ”μ—μ„œ .$dynamic()을 ν˜ΈμΆœν•΄μ•Ό ν•©λ‹ˆλ‹€.

νŽ˜μ΄μ§€ λ²ˆν˜Έμ™€ 선택적인 νŽ˜μ΄μ§€ 크기λ₯Ό 기반으둜 쿼리에 LIMITκ³Ό OFFSET μ ˆμ„ μΆ”κ°€ν•˜λŠ” κ°„λ‹¨ν•œ withPagination ν•¨μˆ˜λ₯Ό κ΅¬ν˜„ν•΄ λ³΄κ² μŠ΅λ‹ˆλ‹€:

function withPagination(
    qb: T,
    page: number = 1,
    pageSize: number = 10,
) {
    return qb.limit(pageSize).offset((page - 1) * pageSize);
}

const query = db.select().from(users).where(eq(users.id, 1));
withPagination(query, 1); // ❌ νƒ€μž… μ—λŸ¬ - 쿼리 λΉŒλ”κ°€ 동적 λͺ¨λ“œκ°€ μ•„λ‹˜

const dynamicQuery = query.$dynamic();
withPagination(dynamicQuery, 1); // βœ… 정상 λ™μž‘

withPagination ν•¨μˆ˜λŠ” μ œλ„€λ¦­ ν•¨μˆ˜μ΄κΈ° λ•Œλ¬Έμ—, 쿼리 λΉŒλ”μ˜ κ²°κ³Ό νƒ€μž…μ„ μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ 쑰인을 μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

function withFriends(qb: T) {
    return qb.leftJoin(friends, eq(friends.userId, users.id));
}

let query = db.select().from(users).where(eq(users.id, 1)).$dynamic();
query = withFriends(query);

μ΄λŠ” PgSelect와 같은 νƒ€μž…λ“€μ΄ 동적 쿼리 λΉŒλ”©μ„ μœ„ν•΄ νŠΉλ³„νžˆ μ„€κ³„λ˜μ—ˆκΈ° λ•Œλ¬Έμ— κ°€λŠ₯ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ νƒ€μž…λ“€μ€ 동적 λͺ¨λ“œμ—μ„œλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒμ€ 동적 쿼리 λΉŒλ”©μ—μ„œ μ œλ„€λ¦­ λ§€κ°œλ³€μˆ˜λ‘œ μ‚¬μš©ν•  수 μžˆλŠ” λͺ¨λ“  νƒ€μž… λͺ©λ‘μž…λ‹ˆλ‹€:

DialectType
QuerySelectInsertUpdateDelete
PostgresPgSelectPgInsertPgUpdatePgDelete
PgSelectQueryBuilder
MySQLMySqlSelectMySqlInsertMySqlUpdateMySqlDelete
MySqlSelectQueryBuilder
SQLiteSQLiteSelectSQLiteInsertSQLiteUpdateSQLiteDelete
SQLiteSelectQueryBuilder

...QueryBuilder νƒ€μž…λ“€μ€ 독립 μ‹€ν–‰ν˜• 쿼리 λΉŒλ” μΈμŠ€ν„΄μŠ€μ™€ ν•¨κ»˜ μ‚¬μš©ν•˜κΈ° μœ„ν•œ κ²ƒμž…λ‹ˆλ‹€. λ°μ΄ν„°λ² μ΄μŠ€ 쿼리 λΉŒλ”λŠ” μ΄λ“€μ˜ ν•˜μœ„ ν΄λž˜μŠ€μ΄λ―€λ‘œ, 이듀도 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

import { QueryBuilder } from 'drizzle-orm/pg-core';

function withFriends(qb: T) {
    return qb.leftJoin(friends, eq(friends.userId, users.id));
}

const qb = new QueryBuilder();
let query = qb.select().from(users).where(eq(users.id, 1)).$dynamic();
query = withFriends(query);