DrizzleORM v0.31.0 릴리스
May 31, 2024

주요 변경 사항

참고: drizzle-orm@0.31.0drizzle-kit@0.22.0 이상 버전과 함께 사용할 수 있습니다. Drizzle Kit도 마찬가지입니다. Drizzle Kit 커맨드를 실행하면 필요한 경우 업그레이드를 확인하고 알려줍니다. Drizzle Kit 업데이트는 아래에서 확인할 수 있습니다.

PostgreSQL 인덱스 API가 변경되었습니다

이전의 Drizzle+PostgreSQL 인덱스 API는 PostgreSQL 문서와 일치하지 않았고 잘못된 방식으로 구현되어 있었습니다. 다행히도 이 API는 쿼리에서 사용되지 않았고, drizzle-kit도 인덱스의 모든 속성을 지원하지 않았습니다. 이제 API를 올바르게 변경하고 drizzle-kit에서 완전히 지원할 수 있게 되었습니다.

이전 API의 문제점

// 이전 인덱스 선언 예시
index('name')
  .on(table.column1, table.column2, ...) 또는 .onOnly(table.column1, table.column2, ...)
  .concurrently()
  .using(sql``) // SQL 표현식
  .asc() 또는 .desc()
  .nullsFirst() 또는 .nullsLast()
  .where(sql``) // SQL 표현식

현재 API

// 첫 번째 예제: `.on()` 사용
index('name')
  .on(table.column1.asc(), table.column2.nullsFirst(), ...) 또는 .onOnly(table.column1.desc().nullsLast(), table.column2, ...)
  .concurrently()
  .where(sql``)
  .with({ fillfactor: '70' })

// 두 번째 예제: `.using()` 사용
index('name')
  .using('btree', table.column1.asc(), sql`lower(${table.column2})`, table.column1.op('text_ops'))
  .where(sql``) // SQL 표현식
  .with({ fillfactor: '70' })

이제 API가 PostgreSQL 문서와 일치하며, 더 명확하고 유연한 방식으로 인덱스를 정의할 수 있습니다.

New Features

🎉 “pg_vector” 확장 기능 지원

Drizzle 스키마 내에서 확장 기능을 생성하는 특별한 코드는 없습니다. 벡터 타입, 인덱스, 쿼리를 사용한다면, 여러분은 이미 pg_vector 확장 기능이 설치된 PostgreSQL 데이터베이스를 사용하고 있다고 가정합니다.

이제 pg_vector 인덱스를 지정하고, 쿼리, 정렬 등을 위해 pg_vector 함수를 활용할 수 있습니다.

pg_vector 문서에서 몇 가지 pg_vector 인덱스 예제를 가져와 Drizzle로 변환해 보겠습니다.

L2 거리, 내적, 코사인 거리

// L2 거리 인덱스 생성
// CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
// 내적 인덱스 생성
// CREATE INDEX ON items USING hnsw (embedding vector_ip_ops);
// 코사인 거리 인덱스 생성
// CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops);

const table = pgTable('items', {
    embedding: vector('embedding', { dimensions: 3 })
}, (table) => ({
    l2: index('l2_index').using('hnsw', table.embedding.op('vector_l2_ops')),
    ip: index('ip_index').using('hnsw', table.embedding.op('vector_ip_ops')),
    cosine: index('cosine_index').using('hnsw', table.embedding.op('vector_cosine_ops'))
}))

위 코드는 PostgreSQL에서 items 테이블에 대해 L2 거리, 내적, 코사인 거리를 계산하기 위한 인덱스를 생성하는 예제입니다. 각 인덱스는 hnsw 알고리즘을 사용하여 벡터 데이터를 효율적으로 검색할 수 있도록 설정됩니다.

L1 거리, 해밍 거리, 자카드 거리 - pg_vector 0.7.0 버전에서 추가됨

// CREATE INDEX ON items USING hnsw (embedding vector_l1_ops);
// CREATE INDEX ON items USING hnsw (embedding bit_hamming_ops);
// CREATE INDEX ON items USING hnsw (embedding bit_jaccard_ops);

const table = pgTable('table', {
    embedding: vector('embedding', { dimensions: 3 })
}, (table) => ({
    l1: index('l1_index').using('hnsw', table.embedding.op('vector_l1_ops')),
    hamming: index('hamming_index').using('hnsw', table.embedding.op('bit_hamming_ops')),
    bit: index('bit_jaccard_index').using('hnsw', table.embedding.op('bit_jaccard_ops'))
}))

쿼리를 작성할 때는 벡터를 위한 미리 정의된 함수를 사용하거나 SQL 템플릿 연산자를 이용해 커스텀 함수를 만들 수 있습니다.

다음 헬퍼 함수들도 사용 가능합니다:

import { l2Distance, l1Distance, innerProduct, 
          cosineDistance, hammingDistance, jaccardDistance } from 'drizzle-orm'

l2Distance(table.column, [3, 1, 2]) // table.column  '[3, 1, 2]'
l1Distance(table.column, [3, 1, 2]) // table.column  '[3, 1, 2]'

innerProduct(table.column, [3, 1, 2]) // table.column  '[3, 1, 2]'
cosineDistance(table.column, [3, 1, 2]) // table.column  '[3, 1, 2]'

hammingDistance(table.column, '101') // table.column  '101'
jaccardDistance(table.column, '101') // table.column  '101'

만약 pg_vector에 다른 함수를 사용하고 싶다면, 기존에 있는 함수를 참고하여 구현할 수 있습니다. 아래는 그 예시입니다:

export function l2Distance(
  column: SQLWrapper | AnyColumn,
  value: number[] | string[] | TypedQueryBuilder | string,
): SQL {
  if (is(value, TypedQueryBuilder) || typeof value === 'string') {
    return sql`${column}  ${value}`;
  }
  return sql`${column}  ${JSON.stringify(value)}`;
}

원하는 이름으로 함수를 만들고 연산자를 변경하면 됩니다. 이 예제는 숫자 배열, 문자열 배열, 문자열, 심지어 쿼리까지 허용합니다. 여러분이 원하는 타입을 자유롭게 만들거나, 기여하고 PR을 제출해도 좋습니다.

예제

pg_vector 문서에서 몇 가지 쿼리 예제를 가져와 Drizzle로 변환해 보겠습니다.

import { l2Distance } from 'drizzle-orm';

// SELECT * FROM items ORDER BY embedding  '[3,1,2]' LIMIT 5;
db.select().from(items).orderBy(l2Distance(items.embedding, [3,1,2]))

// SELECT embedding  '[3,1,2]' AS distance FROM items;
db.select({ distance: l2Distance(items.embedding, [3,1,2]) })

// SELECT * FROM items ORDER BY embedding  (SELECT embedding FROM items WHERE id = 1) LIMIT 5;
const subquery = db.select({ embedding: items.embedding }).from(items).where(eq(items.id, 1));
db.select().from(items).orderBy(l2Distance(items.embedding, subquery)).limit(5)

// SELECT (embedding  '[3,1,2]') * -1 AS inner_product FROM items;
db.select({ innerProduct: sql`(${maxInnerProduct(items.embedding, [3,1,2])}) * -1` }).from(items)

// 그리고 더 많은 예제들!

🎉 새로운 PostgreSQL 타입: point, line

이제 PostgreSQL 기하학적 타입에서 pointline을 사용할 수 있습니다.

point 타입

point 타입은 데이터베이스에서 매핑할 때 두 가지 모드를 제공합니다: tuplexy.

const items = pgTable('items', {
  point: point('point'),
  pointObj: point('point_xy', { mode: 'xy' }),
});

line 타입

line 타입도 데이터베이스에서 매핑할 때 두 가지 모드를 제공합니다: tupleabc.

const items = pgTable('items', {
  line: line('line'),
  lineObj: point('line_abc', { mode: 'abc' }),
});

🎉 기본적인 “postgis” 확장 기능 지원

Drizzle 스키마 내부에서 확장 기능을 생성하는 특정 코드는 없습니다. postgis 타입, 인덱스, 쿼리를 사용한다면, postgis 확장 기능이 설치된 PostgreSQL 데이터베이스를 사용하고 있다고 가정합니다.

postgis 확장 기능의 geometry 타입:

const items = pgTable('items', {
  geo: geometry('geo', { type: 'point' }),
  geoObj: geometry('geo_obj', { type: 'point', mode: 'xy' }),
  geoSrid: geometry('geo_options', { type: 'point', mode: 'xy', srid: 4000 }),
});

mode geometry 타입은 데이터베이스에서 매핑하기 위해 두 가지 모드를 제공합니다: tuplexy.

type

현재 릴리스에서는 PostgreSQL PostGIS 확장 기능의 geometry(Point) 타입인 point라는 미리 정의된 타입을 제공합니다. 다른 타입을 사용하고 싶다면 여기에 원하는 문자열을 지정할 수 있습니다.

Drizzle Kit 업데이트: drizzle-kit@0.22.0

이 릴리스 노트는 drizzle-kit@0.22.0에서 부분적으로 복제되었습니다.

New Features_eNszQ68Gaii84Wx4NjsMpw

🎉 새로운 타입 지원

Drizzle Kit이 이제 다음 타입들을 처리할 수 있습니다:

🎉 drizzle.config에 새로운 파라미터 추가 - extensionsFilters

PostGIS 확장 기능은 public 스키마에 몇 가지 내부 테이블을 생성합니다. 이는 PostGIS 확장 기능이 설치된 데이터베이스에서 pushintrospect를 사용할 경우, diff 작업에 이 테이블들이 모두 포함된다는 것을 의미합니다. 이 경우, tablesFilter를 지정하고 확장 기능에 의해 생성된 모든 테이블을 찾아 이 파라미터에 나열해야 합니다.

이제 여러분은 이러한 단계를 거칠 필요가 없습니다. 단순히 사용 중인 확장 기능의 이름을 extensionsFilters에 지정하면 Drizzle이 필요한 테이블을 자동으로 건너뜁니다.

현재는 postgis 옵션만 지원하지만, public 스키마에 테이블을 생성하는 다른 확장 기능도 추가할 계획입니다.

postgis 옵션은 geography_columns, geometry_columns, spatial_ref_sys 테이블을 건너뜁니다.

import { defineConfig } from 'drizzle-kit'

export default defaultConfig({
  dialect: "postgresql",
  extensionsFilters: ["postgis"],
})

Improvements

데이터베이스 자격 증명을 위한 zod 스키마 업데이트 및 모든 긍정적/부정적 케이스에 대한 테스트 작성

import { defineConfig } from 'drizzle-kit'

export default defaultConfig({
  dialect: "postgresql",
  dbCredentials: {
    ssl: true, // "require" | "allow" | "prefer" | "verify-full" | node:tls의 옵션
  }
})
import { defineConfig } from 'drizzle-kit'

export default defaultConfig({
  dialect: "mysql",
  dbCredentials: {
    ssl: "", // 문자열 | SslOptions (mysql2 패키지의 ssl 옵션)
  }
})

이 코드는 drizzle-kit 설정에서 PostgreSQL과 MySQL 데이터베이스의 SSL 연결을 설정하는 방법을 보여줍니다. PostgreSQL의 경우 ssl 속성에 true 또는 특정 SSL 모드를 지정할 수 있으며, MySQL의 경우 ssl 속성에 문자열 또는 mysql2 패키지의 SSL 옵션을 지정할 수 있습니다. 이 설정을 통해 데이터베이스 연결 시 SSL을 사용하여 보안을 강화할 수 있습니다.

libsqlbetter-sqlite3 드라이버를 위한 정규화된 SQLite URL

이 두 드라이버는 서로 다른 파일 경로 패턴을 사용합니다. Drizzle Kit은 두 패턴을 모두 수용하고 각각에 맞는 적절한 파일 경로 형식을 생성합니다.

MySQL과 SQLite의 인덱스 표현식 동작 업데이트

이번 릴리스에서 MySQL과 SQLite는 표현식을 SQL 쿼리에 올바르게 매핑합니다. 문자열 내의 표현식은 이스케이프되지 않지만, 컬럼은 이스케이프됩니다.

export const users = sqliteTable(
  'users',
  {
    id: integer('id').primaryKey(),
    email: text('email').notNull(),
  },
  (table) => ({
    emailUniqueIndex: uniqueIndex('emailUniqueIndex').on(sql`lower(${table.email})`),
  }),
);
-- 이전 버전
CREATE UNIQUE INDEX `emailUniqueIndex` ON `users` (`lower("users"."email")`);

-- 현재 버전
CREATE UNIQUE INDEX `emailUniqueIndex` ON `users` (lower("email"));

이제 SQL 쿼리에서 표현식이 더 정확하게 처리되며, 컬럼 이름만 이스케이프됩니다. 이로 인해 쿼리의 가독성과 정확성이 향상되었습니다.

버그 수정

How push and generate works for indexes

Limitations

표현식이 포함된 인덱스에는 수동으로 이름을 지정해야 합니다

예제

index().on(table.id, table.email) // 정상 동작하며 이름이 자동 생성됨
index('my_name').on(table.id, table.email) // 정상 동작

// 하지만

index().on(sql`lower(${table.email})`) // 오류 발생
index('my_name').on(sql`lower(${table.email})`) // 정상 동작

위 예제에서 볼 수 있듯이, 표현식이 포함된 인덱스를 생성할 때는 반드시 수동으로 이름을 지정해야 합니다. 그렇지 않으면 오류가 발생합니다.

기존 인덱스에서 아래 나열된 필드가 변경된 경우, push는 구문을 생성하지 않습니다:

push 워크플로우를 사용하면서 인덱스에서 이러한 필드를 변경하려면 다음 단계를 따라야 합니다:

  1. 인덱스를 주석 처리합니다.
  2. push를 실행합니다.
  3. 주석을 해제하고 해당 필드를 변경합니다.
  4. 다시 push를 실행합니다.

generate 커맨드의 경우, 새로운 Drizzle 인덱스 API의 어떤 속성이 변경되더라도 drizzle-kit이 트리거되므로 여기에는 제한이 없습니다.