Drizzle 스키마 내에서 확장 기능을 생성하기 위한 특별한 코드는 없습니다. 벡터 타입, 인덱스, 쿼리를 사용하고 있다면,
pg_vector
확장 기능이 설치된 PostgreSQL 데이터베이스를 사용하고 있다고 가정합니다.
PostgreSQL 확장 기능
pg_vector
pg_vector
는 PostgreSQL을 위한 오픈소스 벡터 유사성 검색 도구입니다.
여러분의 데이터와 함께 벡터를 저장할 수 있습니다. 지원 기능은 다음과 같습니다:
- 정확한 및 근사 최근접 이웃 검색
- 단정밀도, 반정밀도, 이진 및 희소 벡터
- L2 거리, 내적, 코사인 거리, L1 거리, 해밍 거리, 자카드 거리
컬럼 타입
vector
벡터 데이터를 다른 데이터와 함께 저장할 수 있습니다.
더 자세한 정보는 공식 pg_vector 문서를 참고하세요. 문서 보기
const table = pgTable('table', {
embedding: vector({ dimensions: 3 })
})
CREATE TABLE IF NOT EXISTS "table" (
"embedding" vector(3)
);
인덱스
Drizzle에서 제공하는 인덱스 API를 사용하면 PostGIS를 위한 모든 인덱스를 작성할 수 있습니다.
예제
// CREATE INDEX custom_idx ON table USING GIST (geom);
const table = pgTable('table', {
geo: geometry({ type: 'point' }),
}, (table) => ({
gist: index('custom_idx').using('gist', table.geo)
}))
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({ 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
테이블에 대한 다양한 거리 측정 방식의 인덱스를 생성하는 예제입니다. 각 인덱스는 hnsw
알고리즘을 사용하며, vector_l2_ops
, vector_ip_ops
, vector_cosine_ops
연산자를 통해 각각 L2 거리, 내적, 코사인 거리를 계산합니다.
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({ 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'))
}))
pg_vector 0.7.0 버전에서는 L1 거리, 해밍 거리, 자카드 거리와 같은 새로운 거리 측정 방법이 추가되었습니다. 이를 통해 벡터 데이터를 더욱 효율적으로 인덱싱하고 검색할 수 있습니다. 위의 코드 예제는 이러한 거리 측정 방법을 사용하여 인덱스를 생성하는 방법을 보여줍니다. 각각의 인덱스는 hnsw
알고리즘을 사용하며, vector_l1_ops
, bit_hamming_ops
, 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)
// 그리고 더 많은 예제들이 있습니다!
postgis
Drizzle 스키마 내부에서 확장 기능을 생성하는 특별한 코드는 없습니다. PostGIS 타입, 인덱스, 쿼리를 사용한다면, postgis
확장 기능이 설치된 PostgreSQL 데이터베이스를 사용하고 있다고 가정합니다.
PostGIS 웹사이트에서 언급한 대로:
PostGIS는 PostgreSQL 관계형 데이터베이스의 기능을 확장하여 지리 공간 데이터를 저장, 인덱싱, 쿼리하는 기능을 추가합니다.
PostGIS 확장 기능과 함께 introspect
또는 push
커맨드를 사용하면서 PostGIS 테이블을 포함하고 싶지 않다면, extensionsFilters
를 사용하여 모든 PostGIS 테이블을 무시할 수 있습니다.
컬럼 타입: Geometry
geometry
여러분의 데이터와 함께 지리 데이터를 저장할 수 있습니다.
더 자세한 정보는 공식 PostGIS 문서를 참고하세요: PostGIS 공식 문서
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
타입은 데이터베이스에서 매핑할 때 두 가지 모드를 제공합니다: tuple
과 xy
.
tuple
은 삽입 시에 사용되며, 선택 시에는 튜플로 매핑됩니다. 따라서 데이터베이스의 지리 데이터는 Drizzle을 통해[1, 2]
와 같은 형태로 타입이 지정됩니다.xy
는 삽입 시에 사용되며, 선택 시에는x
와y
좌표를 가진 객체로 매핑됩니다. 따라서 데이터베이스의 지리 데이터는 Drizzle을 통해{ x: 1, y: 2 }
와 같은 형태로 타입이 지정됩니다.
type
현재 릴리스에서는 point
라는 미리 정의된 타입을 제공합니다. 이는 PostgreSQL PostGIS 확장에서의 geometry(Point)
타입에 해당합니다. 다른 타입을 사용하고 싶다면 여기에 원하는 문자열을 지정할 수 있습니다.