This document covers all PostgreSQL-specific features in Drizzle ORM, including column types, extensions, schemas, views, sequences, row-level security, and advanced PostgreSQL functionality.
import { integer } from 'drizzle-orm/pg-core';
/**
* Creates a 32-bit integer column.
*
* @param name - Column name (optional if using object syntax)
* @returns Column builder for integer type
*
* @remarks
* - Stores values from -2,147,483,648 to 2,147,483,647
* - Maps to JavaScript number type
* - Can be used with identity columns
*
* @example
* ```typescript
* const users = pgTable('users', {
* age: integer('age'),
* count: integer('count').notNull().default(0),
* id: integer('id').primaryKey(),
* });
* ```
*/
function integer(name?: string): PgIntegerBuilder;import { bigint } from 'drizzle-orm/pg-core';
/**
* Creates a 64-bit integer column.
*
* @param name - Column name (optional)
* @returns Column builder for bigint type
*
* @remarks
* - Stores values from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
* - Maps to JavaScript string by default (to avoid precision loss)
* - Use mode: 'number' for JavaScript number (not recommended for large values)
* - Use mode: 'bigint' for JavaScript BigInt
*
* @example
* ```typescript
* const analytics = pgTable('analytics', {
* views: bigint('views', { mode: 'bigint' }),
* timestamp: bigint('timestamp', { mode: 'number' }),
* largeId: bigint('large_id', { mode: 'string' }), // default
* });
* ```
*/
function bigint(
name?: string,
config?: { mode?: 'string' | 'number' | 'bigint' }
): PgBigIntBuilder;import { smallint } from 'drizzle-orm/pg-core';
/**
* Creates a 16-bit integer column.
*
* @param name - Column name (optional)
* @returns Column builder for smallint type
*
* @remarks
* - Stores values from -32,768 to 32,767
* - Maps to JavaScript number type
* - Useful for saving storage space when range is limited
*
* @example
* ```typescript
* const products = pgTable('products', {
* stock: smallint('stock').notNull(),
* priority: smallint('priority').default(0),
* });
* ```
*/
function smallint(name?: string): PgSmallIntBuilder;import { serial, bigserial, smallserial } from 'drizzle-orm/pg-core';
/**
* Creates an auto-incrementing integer column.
*
* @param name - Column name (optional)
* @returns Column builder for serial type
*
* @remarks
* - serial: 32-bit auto-incrementing integer (1 to 2,147,483,647)
* - bigserial: 64-bit auto-incrementing integer (1 to 9,223,372,036,854,775,807)
* - smallserial: 16-bit auto-incrementing integer (1 to 32,767)
* - Automatically creates a sequence
* - Has NOT NULL constraint by default
* - Prefer identity columns (generatedAlwaysAsIdentity) in new projects
*
* @example
* ```typescript
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* legacyId: bigserial('legacy_id'),
* smallId: smallserial('small_id'),
* });
* ```
*/
function serial(name?: string): PgSerialBuilder;
function bigserial(name?: string): PgBigSerialBuilder;
function smallserial(name?: string): PgSmallSerialBuilder;import { numeric, decimal } from 'drizzle-orm/pg-core';
/**
* Creates a numeric column with arbitrary precision.
*
* @param name - Column name (optional)
* @param config - Numeric configuration
* @returns Column builder for numeric type
*
* @remarks
* - Stores exact numeric values with user-specified precision
* - Default mode returns string to avoid precision loss
* - mode: 'number' returns JavaScript number (may lose precision)
* - mode: 'bigint' returns JavaScript BigInt (for integers only)
* - precision: total number of digits (max 1000)
* - scale: number of digits after decimal point
* - decimal is an alias for numeric
*
* @example
* ```typescript
* const products = pgTable('products', {
* price: numeric('price', { precision: 10, scale: 2 }), // string
* weight: numeric('weight', { precision: 8, scale: 3, mode: 'number' }),
* exactValue: numeric('exact_value', { precision: 20, scale: 0, mode: 'bigint' }),
* flexible: numeric('flexible'), // no precision limit
* });
* ```
*/
function numeric<TMode extends 'string' | 'number' | 'bigint'>(
name?: string,
config?: {
precision?: number;
scale?: number;
mode?: TMode;
}
): PgNumericBuilder<TMode>;
const decimal = numeric; // aliasimport { real } from 'drizzle-orm/pg-core';
/**
* Creates a single-precision floating point column.
*
* @param name - Column name (optional)
* @returns Column builder for real type
*
* @remarks
* - 4-byte floating point (6 decimal digits precision)
* - Maps to JavaScript number
* - Inexact storage, use numeric for exact values
*
* @example
* ```typescript
* const sensors = pgTable('sensors', {
* temperature: real('temperature'),
* humidity: real('humidity'),
* });
* ```
*/
function real(name?: string): PgRealBuilder;import { doublePrecision } from 'drizzle-orm/pg-core';
/**
* Creates a double-precision floating point column.
*
* @param name - Column name (optional)
* @returns Column builder for double precision type
*
* @remarks
* - 8-byte floating point (15 decimal digits precision)
* - Maps to JavaScript number
* - Inexact storage, use numeric for exact values
* - Default floating point type in PostgreSQL
*
* @example
* ```typescript
* const coordinates = pgTable('coordinates', {
* latitude: doublePrecision('latitude'),
* longitude: doublePrecision('longitude'),
* altitude: doublePrecision('altitude'),
* });
* ```
*/
function doublePrecision(name?: string): PgDoublePrecisionBuilder;import { text } from 'drizzle-orm/pg-core';
/**
* Creates a variable-length text column.
*
* @param name - Column name (optional)
* @param config - Text configuration
* @returns Column builder for text type
*
* @remarks
* - Unlimited length (up to 1GB)
* - Maps to JavaScript string
* - Can specify enum values for validation
*
* @example
* ```typescript
* const posts = pgTable('posts', {
* title: text('title').notNull(),
* content: text('content'),
* status: text('status', { enum: ['draft', 'published', 'archived'] }),
* });
* ```
*/
function text<TEnum extends string[]>(
name?: string,
config?: { enum?: TEnum }
): PgTextBuilder<TEnum>;import { varchar } from 'drizzle-orm/pg-core';
/**
* Creates a variable-length character column with length limit.
*
* @param name - Column name (optional)
* @param config - Varchar configuration
* @returns Column builder for varchar type
*
* @remarks
* - Variable length with optional maximum
* - Maps to JavaScript string
* - No performance advantage over text in PostgreSQL
* - length parameter is optional but recommended for compatibility
*
* @example
* ```typescript
* const users = pgTable('users', {
* username: varchar('username', { length: 50 }).notNull(),
* email: varchar('email', { length: 255 }),
* shortCode: varchar('short_code', { length: 10 }),
* });
* ```
*/
function varchar(
name?: string,
config?: {
length?: number;
enum?: string[];
}
): PgVarcharBuilder;import { char } from 'drizzle-orm/pg-core';
/**
* Creates a fixed-length character column.
*
* @param name - Column name (optional)
* @param config - Char configuration
* @returns Column builder for char type
*
* @remarks
* - Fixed length, blank-padded to length
* - Maps to JavaScript string (padding stripped on retrieval)
* - Use for fixed-length codes or identifiers
* - length defaults to 1 if not specified
*
* @example
* ```typescript
* const countries = pgTable('countries', {
* code: char('code', { length: 2 }).primaryKey(), // ISO country codes
* currencyCode: char('currency_code', { length: 3 }),
* flag: char('flag'), // single character, length: 1
* });
* ```
*/
function char(
name?: string,
config?: {
length?: number;
enum?: string[];
}
): PgCharBuilder;import { date } from 'drizzle-orm/pg-core';
/**
* Creates a date column (calendar date without time).
*
* @param name - Column name (optional)
* @param config - Date configuration
* @returns Column builder for date type
*
* @remarks
* - Stores calendar date (year, month, day) only
* - Maps to JavaScript Date object by default
* - mode: 'string' returns date as string (YYYY-MM-DD)
*
* @example
* ```typescript
* const events = pgTable('events', {
* eventDate: date('event_date'),
* birthDate: date('birth_date', { mode: 'string' }),
* createdAt: date('created_at').defaultNow(),
* });
* ```
*/
function date<TMode extends 'date' | 'string'>(
name?: string,
config?: { mode?: TMode }
): PgDateBuilder<TMode>;import { timestamp } from 'drizzle-orm/pg-core';
/**
* Creates a timestamp column (date and time).
*
* @param name - Column name (optional)
* @param config - Timestamp configuration
* @returns Column builder for timestamp type
*
* @remarks
* - Stores date and time (with optional timezone)
* - Maps to JavaScript Date object by default
* - mode: 'string' returns timestamp as ISO string
* - withTimezone: true stores with timezone info (recommended)
* - withTimezone: false stores local time without timezone
* - precision: fractional seconds precision (0-6 digits)
*
* @example
* ```typescript
* const posts = pgTable('posts', {
* createdAt: timestamp('created_at').defaultNow(),
* updatedAt: timestamp('updated_at', { mode: 'date', withTimezone: true }),
* publishedAt: timestamp('published_at', {
* mode: 'string',
* withTimezone: true,
* precision: 3
* }),
* });
* ```
*/
function timestamp<TMode extends 'date' | 'string'>(
name?: string,
config?: {
mode?: TMode;
withTimezone?: boolean;
precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
): PgTimestampBuilder<TMode>;import { time } from 'drizzle-orm/pg-core';
/**
* Creates a time column (time of day without date).
*
* @param name - Column name (optional)
* @param config - Time configuration
* @returns Column builder for time type
*
* @remarks
* - Stores time of day only (no date)
* - Maps to JavaScript string
* - withTimezone: true includes timezone offset
* - precision: fractional seconds precision (0-6 digits)
*
* @example
* ```typescript
* const schedule = pgTable('schedule', {
* openTime: time('open_time'),
* closeTime: time('close_time'),
* meetingTime: time('meeting_time', {
* withTimezone: true,
* precision: 0
* }),
* });
* ```
*/
function time(
name?: string,
config?: {
withTimezone?: boolean;
precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
}
): PgTimeBuilder;import { interval } from 'drizzle-orm/pg-core';
/**
* Creates an interval column (time span/duration).
*
* @param name - Column name (optional)
* @param config - Interval configuration
* @returns Column builder for interval type
*
* @remarks
* - Stores time intervals/durations
* - Maps to JavaScript string (PostgreSQL interval format)
* - Supports various interval fields (year, month, day, hour, etc.)
* - precision: fractional seconds precision (0-6 digits)
* - fields: restrict interval to specific fields
*
* @example
* ```typescript
* const tasks = pgTable('tasks', {
* duration: interval('duration'),
* timeout: interval('timeout', { precision: 3 }),
* yearInterval: interval('year_interval', { fields: 'year' }),
* dayToSecond: interval('day_to_second', { fields: 'day to second' }),
* });
* ```
*/
function interval(
name?: string,
config?: {
precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
fields?:
| 'year'
| 'month'
| 'day'
| 'hour'
| 'minute'
| 'second'
| 'year to month'
| 'day to hour'
| 'day to minute'
| 'day to second'
| 'hour to minute'
| 'hour to second'
| 'minute to second';
}
): PgIntervalBuilder;import { boolean } from 'drizzle-orm/pg-core';
/**
* Creates a boolean column.
*
* @param name - Column name (optional)
* @returns Column builder for boolean type
*
* @remarks
* - Stores true/false values
* - Maps to JavaScript boolean
* - PostgreSQL accepts various input formats: TRUE, 't', 'yes', 'y', '1'
*
* @example
* ```typescript
* const users = pgTable('users', {
* isActive: boolean('is_active').default(true),
* emailVerified: boolean('email_verified').notNull().default(false),
* isPremium: boolean('is_premium'),
* });
* ```
*/
function boolean(name?: string): PgBooleanBuilder;import { json } from 'drizzle-orm/pg-core';
/**
* Creates a JSON column (stored as text).
*
* @param name - Column name (optional)
* @returns Column builder for json type
*
* @remarks
* - Stores JSON data as text
* - Maps to any TypeScript type (with type parameter)
* - Validates JSON on input
* - Not optimized for querying
* - Consider jsonb for better performance
*
* @example
* ```typescript
* interface UserSettings {
* theme: 'light' | 'dark';
* notifications: boolean;
* }
*
* const users = pgTable('users', {
* settings: json('settings').$type<UserSettings>(),
* metadata: json('metadata').$type<Record<string, unknown>>(),
* });
* ```
*/
function json<TData = unknown>(name?: string): PgJsonBuilder<TData>;import { jsonb } from 'drizzle-orm/pg-core';
/**
* Creates a JSONB column (binary JSON).
*
* @param name - Column name (optional)
* @returns Column builder for jsonb type
*
* @remarks
* - Stores JSON data in binary format
* - Maps to any TypeScript type (with type parameter)
* - Supports efficient querying and indexing
* - Slightly slower on insert (due to conversion)
* - Faster for querying compared to json
* - Removes whitespace and duplicate keys
* - Preferred over json for most use cases
*
* @example
* ```typescript
* interface Address {
* street: string;
* city: string;
* country: string;
* }
*
* const users = pgTable('users', {
* profile: jsonb('profile').$type<{
* bio: string;
* website?: string;
* }>(),
* address: jsonb('address').$type<Address>(),
* });
* ```
*/
function jsonb<TData = unknown>(name?: string): PgJsonbBuilder<TData>;import { inet } from 'drizzle-orm/pg-core';
/**
* Creates an inet column (IPv4 or IPv6 host address).
*
* @param name - Column name (optional)
* @returns Column builder for inet type
*
* @remarks
* - Stores IPv4 or IPv6 host addresses with optional netmask
* - Maps to JavaScript string
* - Supports CIDR notation (e.g., '192.168.1.1/24')
* - Can store host addresses within networks
*
* @example
* ```typescript
* const connections = pgTable('connections', {
* ipAddress: inet('ip_address').notNull(),
* proxyIp: inet('proxy_ip'),
* });
*
* // Insert example: '192.168.1.1' or '2001:db8::1'
* ```
*/
function inet(name?: string): PgInetBuilder;import { cidr } from 'drizzle-orm/pg-core';
/**
* Creates a cidr column (IPv4 or IPv6 network).
*
* @param name - Column name (optional)
* @returns Column builder for cidr type
*
* @remarks
* - Stores IPv4 or IPv6 network addresses
* - Maps to JavaScript string
* - Requires CIDR notation (e.g., '192.168.1.0/24')
* - Stores network addresses, not host addresses
*
* @example
* ```typescript
* const networks = pgTable('networks', {
* subnet: cidr('subnet').notNull(),
* vpcRange: cidr('vpc_range'),
* });
*
* // Insert example: '192.168.0.0/16' or '2001:db8::/32'
* ```
*/
function cidr(name?: string): PgCidrBuilder;import { macaddr } from 'drizzle-orm/pg-core';
/**
* Creates a macaddr column (MAC address, 6 bytes).
*
* @param name - Column name (optional)
* @returns Column builder for macaddr type
*
* @remarks
* - Stores 6-byte MAC addresses
* - Maps to JavaScript string
* - Accepts various formats: 'XX:XX:XX:XX:XX:XX', 'XX-XX-XX-XX-XX-XX'
*
* @example
* ```typescript
* const devices = pgTable('devices', {
* macAddress: macaddr('mac_address').notNull().unique(),
* routerMac: macaddr('router_mac'),
* });
*
* // Insert example: '08:00:2b:01:02:03'
* ```
*/
function macaddr(name?: string): PgMacaddrBuilder;import { macaddr8 } from 'drizzle-orm/pg-core';
/**
* Creates a macaddr8 column (MAC address, 8 bytes).
*
* @param name - Column name (optional)
* @returns Column builder for macaddr8 type
*
* @remarks
* - Stores 8-byte MAC addresses (EUI-64 format)
* - Maps to JavaScript string
* - Accepts various formats including EUI-48 and EUI-64
*
* @example
* ```typescript
* const devices = pgTable('devices', {
* macAddress64: macaddr8('mac_address_64').notNull(),
* });
*
* // Insert example: '08:00:2b:01:02:03:04:05'
* ```
*/
function macaddr8(name?: string): PgMacaddr8Builder;import { point } from 'drizzle-orm/pg-core';
/**
* Creates a point column (geometric point).
*
* @param name - Column name (optional)
* @param config - Point configuration
* @returns Column builder for point type
*
* @remarks
* - Stores 2D geometric point (x, y)
* - mode: 'tuple' returns [x, y] array (default)
* - mode: 'xy' returns { x, y } object
* - Maps to JavaScript array or object
*
* @example
* ```typescript
* const locations = pgTable('locations', {
* position: point('position'), // [x, y]
* center: point('center', { mode: 'xy' }), // { x, y }
* });
*
* // Insert examples:
* // tuple mode: [10.5, 20.3]
* // xy mode: { x: 10.5, y: 20.3 }
* ```
*/
function point<TMode extends 'tuple' | 'xy'>(
name?: string,
config?: { mode?: TMode }
): PgPointBuilder<TMode>;import { line } from 'drizzle-orm/pg-core';
/**
* Creates a line column (infinite line).
*
* @param name - Column name (optional)
* @returns Column builder for line type
*
* @remarks
* - Stores infinite line in 2D space
* - Represented as { A, B, C } in equation Ax + By + C = 0
* - Maps to JavaScript object { a: number, b: number, c: number }
*
* @example
* ```typescript
* const geometry = pgTable('geometry', {
* boundary: line('boundary'),
* });
*
* // Insert example: { a: 1, b: 2, c: 3 }
* ```
*/
function line(name?: string): PgLineBuilder;import { uuid } from 'drizzle-orm/pg-core';
/**
* Creates a UUID column (universally unique identifier).
*
* @param name - Column name (optional)
* @returns Column builder for uuid type
*
* @remarks
* - Stores 128-bit UUID values
* - Maps to JavaScript string
* - Format: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
* - Can use PostgreSQL functions: gen_random_uuid(), uuid_generate_v4()
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* const users = pgTable('users', {
* id: uuid('id').defaultRandom().primaryKey(),
* externalId: uuid('external_id').default(sql`gen_random_uuid()`),
* legacyId: uuid('legacy_id'),
* });
* ```
*/
function uuid(name?: string): PgUuidBuilder;All PostgreSQL column types can be converted to array types using the .array() method.
/**
* Converts any PostgreSQL column to an array type.
*
* @param size - Optional array size constraint
* @returns Array column builder
*
* @remarks
* - Can be called on any column type
* - Supports multi-dimensional arrays (call .array() multiple times)
* - Maps to JavaScript array
* - Can specify array size for constraints
*
* @example
* ```typescript
* const posts = pgTable('posts', {
* // One-dimensional arrays
* tags: text('tags').array(),
* scores: integer('scores').array(),
* prices: numeric('prices', { precision: 10, scale: 2 }).array(),
*
* // Fixed-size array
* fixedArray: integer('fixed_array').array(5),
*
* // Multi-dimensional arrays
* matrix: integer('matrix').array().array(), // integer[][]
* cube: real('cube').array().array().array(), // real[][][]
*
* // Array with enum
* statuses: text('statuses', {
* enum: ['draft', 'published']
* }).array(),
* });
*
* // Insert examples:
* await db.insert(posts).values({
* tags: ['typescript', 'postgresql'],
* scores: [1, 2, 3, 4, 5],
* matrix: [[1, 2], [3, 4]],
* });
*
* // Query with array operators
* import { arrayContains } from 'drizzle-orm/pg-core';
*
* await db.select()
* .from(posts)
* .where(arrayContains(posts.tags, ['typescript']));
* ```
*/
method array<TSize extends number>(size?: TSize): PgArrayBuilder;import {
arrayContains,
arrayContained,
arrayOverlaps
} from 'drizzle-orm/pg-core';
/**
* PostgreSQL array comparison operators.
*
* @remarks
* - arrayContains: checks if array contains all values (@> operator)
* - arrayContained: checks if array is contained by values (<@ operator)
* - arrayOverlaps: checks if arrays have common elements (&& operator)
*
* @example
* ```typescript
* // Array contains
* await db.select()
* .from(posts)
* .where(arrayContains(posts.tags, ['typescript', 'orm']));
*
* // Array is contained
* await db.select()
* .from(posts)
* .where(arrayContained(posts.tags, ['typescript', 'orm', 'database']));
*
* // Arrays overlap
* await db.select()
* .from(posts)
* .where(arrayOverlaps(posts.tags, ['typescript', 'javascript']));
* ```
*/
function arrayContains(column: PgColumn, values: unknown[]): SQL;
function arrayContained(column: PgColumn, values: unknown[]): SQL;
function arrayOverlaps(column: PgColumn, values: unknown[]): SQL;PostgreSQL supports custom enum types for type-safe string values.
import { pgEnum } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL enum type from an array of values.
*
* @param name - Enum type name
* @param values - Array of enum values
* @returns Enum column factory
*
* @remarks
* - Creates a reusable enum type in PostgreSQL
* - Provides type safety in TypeScript
* - Can be used in multiple tables
* - Values are immutable after creation (use migrations to alter)
*
* @example
* ```typescript
* // Define enum
* export const statusEnum = pgEnum('status', [
* 'pending',
* 'processing',
* 'completed',
* 'failed'
* ]);
*
* export const roleEnum = pgEnum('role', ['user', 'admin', 'moderator']);
*
* // Use in tables
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* role: roleEnum('role').notNull().default('user'),
* });
*
* const tasks = pgTable('tasks', {
* id: serial('id').primaryKey(),
* status: statusEnum('status').default('pending'),
* });
*
* // Type-safe inserts
* await db.insert(tasks).values({
* status: 'completed', // ✓ type-safe
* // status: 'invalid', // ✗ type error
* });
* ```
*/
function pgEnum<T extends [string, ...string[]]>(
name: string,
values: T
): PgEnum<T>;import { pgEnum } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL enum type from a TypeScript enum object.
*
* @param name - Enum type name
* @param enumObject - TypeScript enum or object with string values
* @returns Enum column factory
*
* @remarks
* - Integrates with TypeScript enums
* - Provides bidirectional type safety
* - Values extracted from object values (not keys)
*
* @example
* ```typescript
* // TypeScript enum
* enum Priority {
* Low = 'low',
* Medium = 'medium',
* High = 'high',
* Critical = 'critical'
* }
*
* // Create PostgreSQL enum
* export const priorityEnum = pgEnum('priority', Priority);
*
* // Use in table
* const tasks = pgTable('tasks', {
* id: serial('id').primaryKey(),
* priority: priorityEnum('priority').notNull().default(Priority.Medium),
* });
*
* // Use with TypeScript enum
* await db.insert(tasks).values({
* priority: Priority.High, // ✓ type-safe
* });
*
* // Alternative: object literal
* const Status = {
* Draft: 'draft',
* Published: 'published',
* } as const;
*
* export const statusEnum = pgEnum('status', Status);
* ```
*/
function pgEnum<E extends Record<string, string>>(
name: string,
enumObject: E
): PgEnumObject<E>;PostGIS adds support for geographic objects to PostgreSQL.
import { geometry } from 'drizzle-orm/pg-core';
/**
* Creates a PostGIS geometry column.
*
* @param name - Column name (optional)
* @param config - Geometry configuration
* @returns Column builder for geometry type
*
* @remarks
* - Requires PostGIS extension: CREATE EXTENSION IF NOT EXISTS postgis;
* - mode: 'tuple' returns [x, y] (default)
* - mode: 'xy' returns { x, y }
* - type: geometry type (point, linestring, polygon, etc.)
* - srid: Spatial Reference System Identifier (e.g., 4326 for WGS84)
*
* @example
* ```typescript
* const locations = pgTable('locations', {
* // Basic point geometry
* position: geometry('position', { type: 'point' }),
*
* // With SRID (WGS84)
* gpsCoords: geometry('gps_coords', {
* type: 'point',
* srid: 4326
* }),
*
* // With xy mode
* center: geometry('center', {
* type: 'point',
* mode: 'xy'
* }),
*
* // Other geometry types
* boundary: geometry('boundary', { type: 'polygon' }),
* route: geometry('route', { type: 'linestring' }),
* });
*
* // Insert examples:
* await db.insert(locations).values({
* position: [40.7128, -74.0060], // tuple mode
* center: { x: 40.7128, y: -74.0060 }, // xy mode
* });
* ```
*/
function geometry<TMode extends 'tuple' | 'xy'>(
name?: string,
config?: {
mode?: TMode;
type?: 'point' | 'linestring' | 'polygon' | (string & {});
srid?: number;
}
): PgGeometryBuilder<TMode>;pgvector adds support for vector similarity search in PostgreSQL.
import { vector } from 'drizzle-orm/pg-core';
/**
* Creates a pgvector vector column for embeddings.
*
* @param name - Column name (optional)
* @param config - Vector configuration
* @returns Column builder for vector type
*
* @remarks
* - Requires pgvector extension: CREATE EXTENSION IF NOT EXISTS vector;
* - Stores high-dimensional vectors for similarity search
* - dimensions: number of dimensions (required, max 16000)
* - Maps to JavaScript number array
* - Use for machine learning embeddings (OpenAI, etc.)
*
* @example
* ```typescript
* const documents = pgTable('documents', {
* id: serial('id').primaryKey(),
* content: text('content'),
* embedding: vector('embedding', { dimensions: 1536 }), // OpenAI ada-002
* });
*
* // Insert with embeddings
* await db.insert(documents).values({
* content: 'Hello world',
* embedding: [0.1, 0.2, 0.3, ...], // 1536 dimensions
* });
*
* // Similarity search
* import { l2Distance } from 'drizzle-orm';
*
* const queryEmbedding = [...]; // your query vector
* await db.select()
* .from(documents)
* .orderBy(l2Distance(documents.embedding, queryEmbedding))
* .limit(5);
* ```
*/
function vector<D extends number>(
name?: string,
config: { dimensions: D }
): PgVectorBuilder<D>;import { halfvec } from 'drizzle-orm/pg-core';
/**
* Creates a pgvector halfvec column (half-precision vectors).
*
* @param name - Column name (optional)
* @param config - Halfvec configuration
* @returns Column builder for halfvec type
*
* @remarks
* - Half-precision (16-bit) floating point vectors
* - Uses 50% less storage than vector type
* - Slightly less precise than full vector
* - dimensions: number of dimensions (required)
*
* @example
* ```typescript
* const images = pgTable('images', {
* id: serial('id').primaryKey(),
* embedding: halfvec('embedding', { dimensions: 512 }),
* });
* ```
*/
function halfvec<D extends number>(
name?: string,
config: { dimensions: D }
): PgHalfvecBuilder<D>;import { bit } from 'drizzle-orm/pg-core';
/**
* Creates a bit string column for binary vectors.
*
* @param name - Column name (optional)
* @param config - Bit configuration
* @returns Column builder for bit type
*
* @remarks
* - Stores binary vectors as bit strings
* - Used with pgvector for binary embeddings
* - dimensions: number of bits (required)
* - Maps to JavaScript string of '0' and '1' characters
*
* @example
* ```typescript
* const fingerprints = pgTable('fingerprints', {
* id: serial('id').primaryKey(),
* hash: bit('hash', { dimensions: 256 }),
* });
*
* // Insert binary vector
* await db.insert(fingerprints).values({
* hash: '1010101001010101...', // 256 bits
* });
* ```
*/
function bit<D extends number>(
name?: string,
config: { dimensions: D }
): PgBitBuilder<D>;import { sparsevec } from 'drizzle-orm/pg-core';
/**
* Creates a pgvector sparsevec column (sparse vectors).
*
* @param name - Column name (optional)
* @param config - Sparsevec configuration
* @returns Column builder for sparsevec type
*
* @remarks
* - Stores sparse vectors efficiently
* - Only non-zero values are stored
* - dimensions: maximum number of dimensions (required)
* - Maps to JavaScript string (pgvector format)
*
* @example
* ```typescript
* const features = pgTable('features', {
* id: serial('id').primaryKey(),
* sparseEmbedding: sparsevec('sparse_embedding', { dimensions: 10000 }),
* });
*
* // Format: "{index:value,index:value,...}/dimensions"
* await db.insert(features).values({
* sparseEmbedding: '{1:0.5,100:0.8,500:0.3}/10000',
* });
* ```
*/
function sparsevec<D extends number>(
name?: string,
config: { dimensions: D }
): PgSparsevecBuilder<D>;import {
l2Distance,
l1Distance,
innerProduct,
cosineDistance,
hammingDistance,
jaccardDistance,
} from 'drizzle-orm';
/**
* Vector distance functions for similarity search.
*
* @remarks
* - l2Distance: Euclidean distance (L2 norm)
* - l1Distance: Manhattan distance (L1 norm)
* - innerProduct: Inner product (dot product)
* - cosineDistance: Cosine distance (1 - cosine similarity)
* - hammingDistance: Hamming distance (for bit vectors)
* - jaccardDistance: Jaccard distance (for bit vectors)
*
* @example
* ```typescript
* const documents = pgTable('documents', {
* embedding: vector('embedding', { dimensions: 1536 }),
* });
*
* const queryVector = [...]; // your query embedding
*
* // L2 distance (most common)
* await db.select()
* .from(documents)
* .orderBy(l2Distance(documents.embedding, queryVector))
* .limit(10);
*
* // Cosine similarity (for normalized vectors)
* await db.select()
* .from(documents)
* .orderBy(cosineDistance(documents.embedding, queryVector))
* .limit(10);
*
* // Inner product (for maximum similarity)
* await db.select({
* similarity: innerProduct(documents.embedding, queryVector)
* })
* .from(documents)
* .orderBy(desc(innerProduct(documents.embedding, queryVector)));
* ```
*/
function l2Distance(column: PgColumn, vector: number[]): SQL;
function l1Distance(column: PgColumn, vector: number[]): SQL;
function innerProduct(column: PgColumn, vector: number[]): SQL;
function cosineDistance(column: PgColumn, vector: number[]): SQL;
function hammingDistance(column: PgColumn, vector: string): SQL;
function jaccardDistance(column: PgColumn, vector: string): SQL;/**
* Creates an index with vector-specific methods.
*
* @remarks
* - hnsw: Hierarchical Navigable Small World (approximate search)
* - ivfflat: Inverted File Flat (approximate search)
* - btree: B-tree index (exact search, slower)
*
* Use operator classes to specify distance function:
* - vector_l2_ops: L2 distance
* - vector_ip_ops: Inner product
* - vector_cosine_ops: Cosine distance
* - vector_l1_ops: L1 distance
* - bit_hamming_ops: Hamming distance (for bit)
* - bit_jaccard_ops: Jaccard distance (for bit)
*
* @example
* ```typescript
* import { index } from 'drizzle-orm/pg-core';
*
* const documents = pgTable('documents', {
* id: serial('id').primaryKey(),
* embedding: vector('embedding', { dimensions: 1536 }),
* }, (table) => ({
* // HNSW index (recommended for large datasets)
* embeddingIndex: index('embedding_idx')
* .using('hnsw', table.embedding.op('vector_l2_ops'))
* .with({ m: 16, ef_construction: 64 }),
*
* // IVFFlat index (faster build, slower search)
* embeddingIvfIndex: index('embedding_ivf_idx')
* .using('ivfflat', table.embedding.op('vector_cosine_ops'))
* .with({ lists: 100 }),
* }));
*
* // HNSW parameters:
* // - m: max connections per layer (higher = better recall, more memory)
* // - ef_construction: build-time search depth (higher = better index)
*
* // IVFFlat parameters:
* // - lists: number of clusters (sqrt(rows) is a good default)
* ```
*/Schemas provide namespacing for database objects.
import { pgSchema } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL schema namespace.
*
* @param name - Schema name
* @returns Schema object with table/view/enum/sequence builders
*
* @remarks
* - Organizes database objects into namespaces
* - Cannot use 'public' (that's the default schema)
* - Provides schema-scoped table, view, enum, and sequence builders
*
* @example
* ```typescript
* // Create schema
* export const authSchema = pgSchema('auth');
*
* // Define tables in schema
* export const users = authSchema.table('users', {
* id: serial('id').primaryKey(),
* email: text('email').notNull(),
* });
*
* export const sessions = authSchema.table('sessions', {
* id: uuid('id').primaryKey(),
* userId: integer('user_id').references(() => users.id),
* });
*
* // Define enum in schema
* export const roleEnum = authSchema.enum('role', ['user', 'admin']);
*
* // Define view in schema
* export const activeUsers = authSchema.view('active_users').as((qb) =>
* qb.select().from(users).where(eq(users.isActive, true))
* );
*
* // Define sequence in schema
* export const userIdSeq = authSchema.sequence('user_id_seq');
*
* // Multiple schemas
* export const analyticsSchema = pgSchema('analytics');
* export const events = analyticsSchema.table('events', {
* id: serial('id').primaryKey(),
* type: text('type').notNull(),
* });
* ```
*/
function pgSchema<TName extends string>(name: TName): PgSchema<TName>;
interface PgSchema<TName extends string> {
readonly schemaName: TName;
// Define table in schema
table<TTableName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(
name: TTableName,
columns: TColumns,
extraConfig?: (self: BuildExtraConfigColumns<TTableName, TColumns>) => PgTableExtraConfig
): PgTableWithColumns<{ name: TTableName; schema: TName; columns: BuildColumns<TTableName, TColumns> }>;
// Define view in schema
view<TName extends string>(name: TName): ViewBuilder<TName>;
view<TName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(
name: TName,
columns: TColumns
): ManualViewBuilder<TName, TColumns>;
// Define materialized view in schema
materializedView<TName extends string>(name: TName): MaterializedViewBuilder<TName>;
materializedView<TName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(
name: TName,
columns: TColumns
): ManualMaterializedViewBuilder<TName, TColumns>;
// Define enum in schema
enum<T extends [string, ...string[]]>(
name: string,
values: T
): PgEnum<T>;
enum<E extends Record<string, string>>(
name: string,
enumObject: E
): PgEnumObject<E>;
// Define sequence in schema
sequence(
name: string,
options?: PgSequenceOptions
): PgSequence;
}import { pgView } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL view definition.
*
* @remarks
* - Views are virtual tables based on queries
* - Can have configuration options (checkOption, securityBarrier, securityInvoker)
* - Can be used like tables in SELECT queries
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* name: text('name').notNull(),
* isActive: boolean('is_active').default(true),
* });
*
* // Inferred columns from query
* export const activeUsers = pgView('active_users').as((qb) =>
* qb.select({
* id: users.id,
* name: users.name,
* })
* .from(users)
* .where(eq(users.isActive, true))
* );
*
* // With view options
* export const secureView = pgView('secure_view')
* .with({
* checkOption: 'cascaded',
* securityBarrier: true,
* securityInvoker: true,
* })
* .as((qb) => qb.select().from(users));
*
* // Manual column definition
* export const userView = pgView('user_view', {
* id: integer('id'),
* name: text('name'),
* }).as(sql`SELECT id, name FROM users`);
*
* // Existing view (not managed by Drizzle)
* export const legacyView = pgView('legacy_view', {
* id: integer('id'),
* data: text('data'),
* }).existing();
*
* // Query from view
* await db.select().from(activeUsers);
* ```
*/
function pgView<TName extends string>(name: TName): ViewBuilder<TName>;
function pgView<TName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(
name: TName,
columns: TColumns
): ManualViewBuilder<TName, TColumns>;
interface ViewBuilder<TName extends string> {
// Add view options
with(config: {
checkOption?: 'local' | 'cascaded';
securityBarrier?: boolean;
securityInvoker?: boolean;
}): this;
// Define view query
as<TSelectedFields extends ColumnsSelection>(
query: TypedQueryBuilder<TSelectedFields> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelectedFields>)
): PgView<TName, false, TSelectedFields>;
}
interface ManualViewBuilder<TName extends string, TColumns> {
with(config: ViewWithConfig): this;
as(query: SQL): PgView<TName, false, BuildColumns<TName, TColumns>>;
existing(): PgView<TName, true, BuildColumns<TName, TColumns>>;
}import { pgMaterializedView } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL materialized view definition.
*
* @remarks
* - Materialized views cache query results physically
* - Must be refreshed to update data
* - Can have indexes for faster queries
* - Support many storage parameters
*
* @example
* ```typescript
* const orders = pgTable('orders', {
* id: serial('id').primaryKey(),
* total: numeric('total', { precision: 10, scale: 2 }),
* createdAt: timestamp('created_at').defaultNow(),
* });
*
* // Basic materialized view
* export const orderStats = pgMaterializedView('order_stats').as((qb) =>
* qb.select({
* totalOrders: count(),
* totalRevenue: sum(orders.total),
* })
* .from(orders)
* );
*
* // With storage parameters
* export const userStats = pgMaterializedView('user_stats')
* .with({
* fillfactor: 70,
* autovacuumEnabled: true,
* autovacuumVacuumThreshold: 50,
* })
* .tablespace('fast_storage')
* .using('heap')
* .as((qb) => qb.select().from(users));
*
* // Create without data initially
* export const emptyView = pgMaterializedView('empty_view')
* .withNoData()
* .as((qb) => qb.select().from(orders));
*
* // Refresh materialized view
* await db.refreshMaterializedView(orderStats);
*
* // Refresh concurrently (requires unique index)
* await db.refreshMaterializedView(orderStats).concurrently();
*
* // Refresh with no data
* await db.refreshMaterializedView(orderStats).withNoData();
* ```
*/
function pgMaterializedView<TName extends string>(
name: TName
): MaterializedViewBuilder<TName>;
function pgMaterializedView<TName extends string, TColumns extends Record<string, PgColumnBuilderBase>>(
name: TName,
columns: TColumns
): ManualMaterializedViewBuilder<TName, TColumns>;
interface MaterializedViewBuilder<TName extends string> {
// Storage parameters
with(config: {
fillfactor?: number;
toastTupleTarget?: number;
parallelWorkers?: number;
autovacuumEnabled?: boolean;
vacuumIndexCleanup?: 'auto' | 'off' | 'on';
vacuumTruncate?: boolean;
autovacuumVacuumThreshold?: number;
autovacuumVacuumScaleFactor?: number;
autovacuumVacuumCostDelay?: number;
autovacuumVacuumCostLimit?: number;
autovacuumFreezeMinAge?: number;
autovacuumFreezeMaxAge?: number;
autovacuumFreezeTableAge?: number;
autovacuumMultixactFreezeMinAge?: number;
autovacuumMultixactFreezeMaxAge?: number;
autovacuumMultixactFreezeTableAge?: number;
logAutovacuumMinDuration?: number;
userCatalogTable?: boolean;
}): this;
// Access method (default: heap)
using(method: string): this;
// Tablespace
tablespace(name: string): this;
// Create without data
withNoData(): this;
// Define view query
as<TSelectedFields extends ColumnsSelection>(
query: TypedQueryBuilder<TSelectedFields> | ((qb: QueryBuilder) => TypedQueryBuilder<TSelectedFields>)
): PgMaterializedView<TName, false, TSelectedFields>;
}
// Refresh operations
interface PgRefreshMaterializedView {
concurrently(): this;
withNoData(): this;
execute(): Promise<void>;
}import { pgSequence } from 'drizzle-orm/pg-core';
/**
* Creates a PostgreSQL sequence.
*
* @param name - Sequence name
* @param options - Sequence configuration
* @returns Sequence object
*
* @remarks
* - Sequences generate unique numeric identifiers
* - Used automatically by serial columns
* - Can be used explicitly with nextval()
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* // Define sequence
* export const orderIdSeq = pgSequence('order_id_seq', {
* startWith: 1000,
* increment: 1,
* minValue: 1000,
* maxValue: 999999,
* cache: 20,
* cycle: false,
* });
*
* // Use in table
* const orders = pgTable('orders', {
* id: integer('id')
* .default(sql`nextval('order_id_seq')`)
* .primaryKey(),
* orderNumber: text('order_number'),
* });
*
* // Use in schema
* const mySchema = pgSchema('myschema');
* export const schemaSeq = mySchema.sequence('my_seq', {
* startWith: 1,
* increment: 1,
* });
*
* // Get next value
* const result = await db.execute(
* sql`SELECT nextval('order_id_seq')`
* );
* ```
*/
function pgSequence(
name: string,
options?: {
/**
* Starting value of the sequence
* @default 1
*/
startWith?: number | string;
/**
* Increment value (can be negative)
* @default 1
*/
increment?: number | string;
/**
* Minimum value of the sequence
*/
minValue?: number | string;
/**
* Maximum value of the sequence
*/
maxValue?: number | string;
/**
* Number of sequence values to cache
* @default 1
*/
cache?: number | string;
/**
* Whether to cycle when reaching min/max
* @default false
*/
cycle?: boolean;
}
): PgSequence;import { pgPolicy, pgRole } from 'drizzle-orm/pg-core';
/**
* Creates a row-level security policy.
*
* @param name - Policy name
* @param config - Policy configuration
* @returns Policy object
*
* @remarks
* - Controls which rows users can access
* - Must enable RLS on table with .enableRLS()
* - Supports different commands (select, insert, update, delete, all)
* - Can be permissive (default) or restrictive
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* // Define roles
* export const authenticatedRole = pgRole('authenticated');
* export const adminRole = pgRole('admin');
*
* // Define table with RLS enabled
* export const posts = pgTable('posts', {
* id: serial('id').primaryKey(),
* userId: integer('user_id').notNull(),
* title: text('title').notNull(),
* published: boolean('published').default(false),
* }, (table) => ({
* // Enable RLS
* enableRLS: table.enableRLS(),
*
* // Users can only see their own posts
* userPolicy: pgPolicy('user_select_own', {
* as: 'permissive',
* for: 'select',
* to: authenticatedRole,
* using: sql`user_id = current_user_id()`,
* }),
*
* // Users can only insert their own posts
* insertPolicy: pgPolicy('user_insert_own', {
* for: 'insert',
* to: authenticatedRole,
* withCheck: sql`user_id = current_user_id()`,
* }),
*
* // Users can only update their own unpublished posts
* updatePolicy: pgPolicy('user_update_own', {
* for: 'update',
* to: authenticatedRole,
* using: sql`user_id = current_user_id() AND NOT published`,
* withCheck: sql`user_id = current_user_id()`,
* }),
*
* // Admins can do anything
* adminPolicy: pgPolicy('admin_all', {
* as: 'permissive',
* for: 'all',
* to: adminRole,
* using: sql`true`,
* }),
*
* // Restrictive policy: prevent deleting published posts
* restrictDeletePolicy: pgPolicy('restrict_published_delete', {
* as: 'restrictive',
* for: 'delete',
* to: 'public',
* using: sql`NOT published`,
* }),
* }));
*
* // Enable RLS on table (alternative syntax)
* export const comments = pgTable('comments', {
* id: serial('id').primaryKey(),
* postId: integer('post_id').notNull(),
* content: text('content').notNull(),
* }).enableRLS();
* ```
*/
function pgPolicy(
name: string,
config?: {
/**
* Policy type: permissive (OR) or restrictive (AND)
* @default 'permissive'
*/
as?: 'permissive' | 'restrictive';
/**
* SQL command(s) the policy applies to
* @default 'all'
*/
for?: 'all' | 'select' | 'insert' | 'update' | 'delete';
/**
* Role(s) the policy applies to
* Can be: 'public', 'current_role', 'current_user', 'session_user', role name, or PgRole
* @default 'public'
*/
to?: 'public' | 'current_role' | 'current_user' | 'session_user' | string | PgRole | (string | PgRole)[];
/**
* USING expression - determines which rows are visible
*/
using?: SQL;
/**
* WITH CHECK expression - determines which rows can be added/modified
*/
withCheck?: SQL;
}
): PgPolicy;import { pgRole } from 'drizzle-orm/pg-core';
/**
* Creates a database role reference.
*
* @param name - Role name
* @param config - Role configuration
* @returns Role object
*
* @remarks
* - References database roles for use in policies
* - Can mark as existing to reference roles not managed by Drizzle
* - Configuration options control role privileges (for creation only)
*
* @example
* ```typescript
* // Define roles
* export const appUser = pgRole('app_user');
* export const appAdmin = pgRole('app_admin', {
* createDb: true,
* createRole: true,
* inherit: true,
* });
*
* // Reference existing role
* export const postgresRole = pgRole('postgres').existing();
*
* // Use in policies
* const posts = pgTable('posts', {
* id: serial('id').primaryKey(),
* content: text('content'),
* }, (table) => ({
* userPolicy: pgPolicy('user_policy', {
* to: appUser,
* using: sql`true`,
* }),
*
* adminPolicy: pgPolicy('admin_policy', {
* to: [appAdmin, postgresRole],
* using: sql`true`,
* }),
* }));
* ```
*/
function pgRole(
name: string,
config?: {
/**
* Can create databases
*/
createDb?: boolean;
/**
* Can create roles
*/
createRole?: boolean;
/**
* Inherits privileges of roles it is a member of
*/
inherit?: boolean;
}
): PgRole;
interface PgRole {
readonly name: string;
/**
* Marks role as existing (not managed by Drizzle)
*/
existing(): this;
}PostgreSQL supports multiple index methods for different use cases.
import { index, uniqueIndex } from 'drizzle-orm/pg-core';
/**
* Creates an index with specific method and operator class.
*
* @remarks
* Available index methods:
* - btree: B-tree index (default) - sorted data, supports <, <=, =, >=, >
* - hash: Hash index - only supports = operator
* - gist: GiST index - generalized search tree, for geometric and full-text
* - gin: GIN index - inverted index, for arrays, jsonb, full-text
* - brin: BRIN index - block range index, for very large tables
* - spgist: SP-GiST index - space-partitioned GiST
* - hnsw: HNSW index (pgvector) - for vector similarity
* - ivfflat: IVFFlat index (pgvector) - for vector similarity
*
* @example
* ```typescript
* const products = pgTable('products', {
* id: serial('id').primaryKey(),
* name: text('name'),
* tags: text('tags').array(),
* metadata: jsonb('metadata'),
* location: geometry('location', { type: 'point' }),
* embedding: vector('embedding', { dimensions: 1536 }),
* createdAt: timestamp('created_at').defaultNow(),
* }, (table) => ({
* // B-tree index (default) - for sorting and range queries
* nameIndex: index('name_idx').on(table.name),
*
* // Composite B-tree index
* nameCreatedIndex: index('name_created_idx')
* .on(table.name, table.createdAt),
*
* // B-tree with order and nulls
* createdDescIndex: index('created_desc_idx')
* .on(table.createdAt.desc().nullsLast()),
*
* // Hash index - only for equality
* idHashIndex: index('id_hash_idx')
* .using('hash', table.id),
*
* // GIN index for arrays
* tagsIndex: index('tags_idx')
* .using('gin', table.tags),
*
* // GIN index for JSONB with operator class
* metadataIndex: index('metadata_idx')
* .using('gin', table.metadata.op('jsonb_path_ops')),
*
* // GiST index for geometric data
* locationIndex: index('location_idx')
* .using('gist', table.location),
*
* // BRIN index for large sequential data
* createdBrinIndex: index('created_brin_idx')
* .using('brin', table.createdAt),
*
* // HNSW vector index
* embeddingHnswIndex: index('embedding_hnsw_idx')
* .using('hnsw', table.embedding.op('vector_l2_ops'))
* .with({ m: 16, ef_construction: 64 }),
*
* // IVFFlat vector index
* embeddingIvfIndex: index('embedding_ivf_idx')
* .using('ivfflat', table.embedding.op('vector_cosine_ops'))
* .with({ lists: 100 }),
*
* // Partial index
* activeIndex: index('active_idx')
* .on(table.name)
* .where(sql`active = true`),
*
* // Concurrent index creation
* concurrentIndex: index('concurrent_idx')
* .on(table.name)
* .concurrently(),
*
* // Unique index
* uniqueNameIndex: uniqueIndex('unique_name_idx')
* .on(table.name),
* }));
* ```
*/
interface IndexBuilderOn {
// Create regular index on columns
on(...columns: [PgColumn | SQL, ...(PgColumn | SQL)[]]): IndexBuilder;
// Create index on table only (not partitions)
onOnly(...columns: [PgColumn | SQL, ...(PgColumn | SQL)[]]): IndexBuilder;
// Create index with specific method
using(
method: 'btree' | 'hash' | 'gist' | 'spgist' | 'gin' | 'brin' | 'hnsw' | 'ivfflat' | (string & {}),
...columns: [PgColumn | SQL, ...(PgColumn | SQL)[]]
): IndexBuilder;
}
interface IndexBuilder {
// Create index concurrently (without locking table)
concurrently(): this;
// Add storage parameters
with(params: Record<string, any>): this;
// Add partial index condition
where(condition: SQL): this;
}
// Column methods for index customization
interface PgColumn {
/**
* Set sort order for index
*/
asc(): this;
desc(): this;
/**
* Set null sorting for index
*/
nullsFirst(): this;
nullsLast(): this;
/**
* Set operator class for index
*
* Common operator classes:
* - Numeric: int2_ops, int4_ops, int8_ops, numeric_ops
* - Text: text_ops, varchar_ops, bpchar_ops
* - Date/Time: date_ops, timestamp_ops, timestamptz_ops
* - JSONB: jsonb_ops, jsonb_path_ops
* - Arrays: array_ops
* - pgvector: vector_l2_ops, vector_ip_ops, vector_cosine_ops, vector_l1_ops
* - bit: bit_hamming_ops, bit_jaccard_ops
*/
op(opClass: string): this;
}/**
* SELECT DISTINCT ON - returns first row for each unique value.
*
* @remarks
* - PostgreSQL-specific feature
* - Returns first row from each group after sorting
* - Columns in DISTINCT ON must match leftmost ORDER BY columns
*
* @example
* ```typescript
* const posts = pgTable('posts', {
* id: serial('id').primaryKey(),
* userId: integer('user_id').notNull(),
* title: text('title').notNull(),
* createdAt: timestamp('created_at').defaultNow(),
* });
*
* // Get latest post for each user
* const latestPosts = await db
* .selectDistinctOn([posts.userId])
* .from(posts)
* .orderBy(posts.userId, desc(posts.createdAt));
*
* // With additional selections
* const latestPostsWithDetails = await db
* .selectDistinctOn([posts.userId], {
* userId: posts.userId,
* latestTitle: posts.title,
* createdAt: posts.createdAt,
* })
* .from(posts)
* .orderBy(posts.userId, desc(posts.createdAt));
*
* // Multiple distinct columns
* const results = await db
* .selectDistinctOn([posts.userId, posts.title])
* .from(posts)
* .orderBy(posts.userId, posts.title, desc(posts.createdAt));
* ```
*/
interface PgDatabase {
selectDistinctOn<TSelection>(
on: (PgColumn | SQL)[],
fields?: TSelection
): PgSelectBuilder<TSelection>;
}/**
* RETURNING clause - returns affected rows from INSERT/UPDATE/DELETE.
*
* @remarks
* - PostgreSQL-specific feature (also available in SQLite)
* - Returns data from modified rows
* - Can return specific columns or all columns
* - Useful for getting generated IDs, timestamps, etc.
*
* @example
* ```typescript
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* name: text('name').notNull(),
* email: text('email').notNull(),
* createdAt: timestamp('created_at').defaultNow(),
* });
*
* // INSERT with RETURNING
* const [newUser] = await db.insert(users)
* .values({ name: 'John', email: 'john@example.com' })
* .returning();
* // Returns: { id: 1, name: 'John', email: 'john@example.com', createdAt: Date }
*
* // Return specific columns
* const [result] = await db.insert(users)
* .values({ name: 'Jane', email: 'jane@example.com' })
* .returning({ id: users.id, name: users.name });
* // Returns: { id: 2, name: 'Jane' }
*
* // UPDATE with RETURNING
* const updated = await db.update(users)
* .set({ name: 'John Doe' })
* .where(eq(users.id, 1))
* .returning();
*
* // DELETE with RETURNING
* const deleted = await db.delete(users)
* .where(eq(users.id, 1))
* .returning({ id: users.id, name: users.name });
*
* // Multiple rows
* const insertedUsers = await db.insert(users)
* .values([
* { name: 'User1', email: 'user1@example.com' },
* { name: 'User2', email: 'user2@example.com' },
* ])
* .returning({ id: users.id });
* // Returns: [{ id: 3 }, { id: 4 }]
* ```
*/
interface PgInsertBuilder {
returning(): Promise<TTable[]>;
returning<TSelection>(fields: TSelection): Promise<TSelection[]>;
}
interface PgUpdateBuilder {
returning(): Promise<TTable[]>;
returning<TSelection>(fields: TSelection): Promise<TSelection[]>;
}
interface PgDeleteBuilder {
returning(): Promise<TTable[]>;
returning<TSelection>(fields: TSelection): Promise<TSelection[]>;
}/**
* Row locking (FOR UPDATE/SHARE) - locks rows for concurrent access control.
*
* @remarks
* - PostgreSQL-specific feature (also available in MySQL)
* - Prevents concurrent modifications during transactions
* - Different lock strengths for different use cases
*
* Lock strengths:
* - 'update': FOR UPDATE - exclusive lock, blocks all other locks
* - 'no key update': FOR NO KEY UPDATE - allows concurrent key updates
* - 'share': FOR SHARE - shared lock, allows other shares
* - 'key share': FOR KEY SHARE - weakest lock, allows most concurrency
*
* @example
* ```typescript
* const accounts = pgTable('accounts', {
* id: serial('id').primaryKey(),
* balance: numeric('balance', { precision: 10, scale: 2 }),
* });
*
* // FOR UPDATE - exclusive lock
* await db.transaction(async (tx) => {
* const [account] = await tx
* .select()
* .from(accounts)
* .where(eq(accounts.id, 1))
* .for('update');
*
* // Update balance safely
* await tx.update(accounts)
* .set({ balance: String(Number(account.balance) + 100) })
* .where(eq(accounts.id, 1));
* });
*
* // FOR UPDATE with NOWAIT - fail immediately if locked
* const result = await db
* .select()
* .from(accounts)
* .where(eq(accounts.id, 1))
* .for('update', { noWait: true });
*
* // FOR UPDATE with SKIP LOCKED - skip locked rows
* const availableAccounts = await db
* .select()
* .from(accounts)
* .where(eq(accounts.balance, '0'))
* .for('update', { skipLocked: true })
* .limit(10);
*
* // FOR SHARE - allows concurrent reads
* const account = await db
* .select()
* .from(accounts)
* .where(eq(accounts.id, 1))
* .for('share');
*
* // FOR NO KEY UPDATE - allows key updates
* const result = await db
* .select()
* .from(accounts)
* .for('no key update');
*
* // FOR KEY SHARE - weakest lock
* const result = await db
* .select()
* .from(accounts)
* .for('key share');
*
* // Lock specific tables in join
* const result = await db
* .select()
* .from(accounts)
* .leftJoin(transactions, eq(accounts.id, transactions.accountId))
* .for('update', { of: accounts });
* ```
*/
interface PgSelect {
for(
strength: 'update' | 'no key update' | 'share' | 'key share',
config?: {
/**
* Fail immediately if row is locked (don't wait)
*/
noWait?: boolean;
/**
* Skip locked rows instead of waiting
*/
skipLocked?: boolean;
/**
* Specify which tables to lock in a join
*/
of?: PgTable;
}
): this;
}Identity columns provide SQL standard auto-increment functionality.
/**
* Identity columns - SQL standard auto-increment.
*
* @remarks
* - Modern alternative to serial columns
* - GENERATED ALWAYS: value always generated (cannot be overridden)
* - GENERATED BY DEFAULT: value generated only if not provided
* - Supports all sequence options
* - Preferred over serial in new projects
*
* @example
* ```typescript
* const users = pgTable('users', {
* // GENERATED ALWAYS AS IDENTITY
* id: integer('id').generatedAlwaysAsIdentity().primaryKey(),
*
* // GENERATED BY DEFAULT AS IDENTITY
* legacyId: integer('legacy_id').generatedByDefaultAsIdentity(),
*
* // With sequence options
* customId: integer('custom_id').generatedAlwaysAsIdentity({
* startWith: 1000,
* increment: 1,
* minValue: 1000,
* maxValue: 999999,
* cache: 20,
* cycle: false,
* }),
*
* // With named sequence
* seqId: integer('seq_id').generatedAlwaysAsIdentity({
* name: 'user_seq_id',
* startWith: 1,
* increment: 1,
* }),
*
* // Bigint identity
* bigId: bigint('big_id', { mode: 'number' })
* .generatedAlwaysAsIdentity(),
*
* name: text('name').notNull(),
* });
*
* // GENERATED ALWAYS - cannot override
* await db.insert(users).values({
* name: 'John',
* // id: 123, // ✗ Error: cannot override GENERATED ALWAYS
* });
*
* // GENERATED BY DEFAULT - can override
* await db.insert(users).values({
* name: 'Jane',
* legacyId: 999, // ✓ Can provide value
* });
* ```
*/
interface PgIntColumnBuilder {
/**
* GENERATED ALWAYS AS IDENTITY
* Value is always generated, cannot be overridden
*/
generatedAlwaysAsIdentity(
sequence?: PgSequenceOptions & { name?: string }
): this;
/**
* GENERATED BY DEFAULT AS IDENTITY
* Value is generated if not provided
*/
generatedByDefaultAsIdentity(
sequence?: PgSequenceOptions & { name?: string }
): this;
}Generated columns compute values from other columns.
/**
* Generated columns - computed from other columns.
*
* @remarks
* - GENERATED ALWAYS AS (expression) STORED
* - Computed from other columns in the same row
* - Automatically updated when dependencies change
* - Stored physically (STORED is the only option in PostgreSQL)
* - Can be indexed
* - Cannot be directly inserted or updated
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* firstName: text('first_name').notNull(),
* lastName: text('last_name').notNull(),
*
* // Generated from SQL expression
* fullName: text('full_name')
* .generatedAlwaysAs(
* sql`first_name || ' ' || last_name`
* ),
*
* email: text('email').notNull(),
*
* // Generated from callback (for type safety)
* emailDomain: text('email_domain')
* .generatedAlwaysAs((): SQL =>
* sql`split_part(${users.email}, '@', 2)`
* ),
* });
*
* const products = pgTable('products', {
* id: serial('id').primaryKey(),
* price: numeric('price', { precision: 10, scale: 2 }).notNull(),
* taxRate: numeric('tax_rate', { precision: 4, scale: 2 }).notNull(),
*
* // Calculated price with tax
* priceWithTax: numeric('price_with_tax', { precision: 10, scale: 2 })
* .generatedAlwaysAs(
* sql`price * (1 + tax_rate / 100)`
* ),
* });
*
* const rectangles = pgTable('rectangles', {
* id: serial('id').primaryKey(),
* width: integer('width').notNull(),
* height: integer('height').notNull(),
*
* // Computed area
* area: integer('area')
* .generatedAlwaysAs(
* sql`width * height`
* ),
* });
*
* // Insert - generated columns are computed automatically
* await db.insert(users).values({
* firstName: 'John',
* lastName: 'Doe',
* email: 'john@example.com',
* // fullName and emailDomain are computed automatically
* });
*
* // Query - can use generated columns
* const result = await db
* .select()
* .from(users)
* .where(eq(users.fullName, 'John Doe'));
*
* // Index generated columns
* const indexedUsers = pgTable('indexed_users', {
* id: serial('id').primaryKey(),
* firstName: text('first_name'),
* lastName: text('last_name'),
* fullName: text('full_name')
* .generatedAlwaysAs(sql`first_name || ' ' || last_name`),
* }, (table) => ({
* fullNameIndex: index('full_name_idx').on(table.fullName),
* }));
* ```
*/
interface PgColumnBuilder {
generatedAlwaysAs(
expression: SQL | (() => SQL)
): this;
}import { primaryKey } from 'drizzle-orm/pg-core';
/**
* Creates a composite primary key constraint.
*
* @remarks
* - Primary key across multiple columns
* - Commonly used for junction/join tables
* - Ensures unique combination of columns
*
* @example
* ```typescript
* // Many-to-many relationship
* const userGroups = pgTable('user_groups', {
* userId: integer('user_id')
* .notNull()
* .references(() => users.id),
* groupId: integer('group_id')
* .notNull()
* .references(() => groups.id),
* joinedAt: timestamp('joined_at').defaultNow(),
* }, (table) => ({
* // Composite primary key
* pk: primaryKey({ columns: [table.userId, table.groupId] }),
*
* // Named composite primary key
* namedPk: primaryKey({
* name: 'user_groups_pk',
* columns: [table.userId, table.groupId]
* }),
* }));
*
* // Three-way join table
* const permissions = pgTable('permissions', {
* userId: integer('user_id').notNull(),
* resourceId: integer('resource_id').notNull(),
* action: text('action').notNull(),
* }, (table) => ({
* pk: primaryKey({
* columns: [table.userId, table.resourceId, table.action]
* }),
* }));
* ```
*/
function primaryKey<TColumns extends PgColumn[]>(config: {
columns: TColumns;
name?: string;
}): PrimaryKeyBuilder;import { foreignKey } from 'drizzle-orm/pg-core';
/**
* Creates a composite foreign key constraint.
*
* @remarks
* - Foreign key across multiple columns
* - References composite primary key in another table
* - Supports ON UPDATE and ON DELETE actions
*
* @example
* ```typescript
* const orderItems = pgTable('order_items', {
* orderId: integer('order_id').notNull(),
* productId: integer('product_id').notNull(),
* quantity: integer('quantity').notNull(),
* }, (table) => ({
* // Composite foreign key
* orderProductFk: foreignKey({
* columns: [table.orderId, table.productId],
* foreignColumns: [orders.id, products.id],
* }),
*
* // With referential actions
* fkWithActions: foreignKey({
* columns: [table.orderId, table.productId],
* foreignColumns: [orders.id, products.id],
* })
* .onUpdate('cascade')
* .onDelete('restrict'),
* }));
*
* // Single column foreign key alternative
* const comments = pgTable('comments', {
* id: serial('id').primaryKey(),
* postId: integer('post_id')
* .notNull()
* .references(() => posts.id, {
* onDelete: 'cascade',
* onUpdate: 'cascade'
* }),
* });
* ```
*/
function foreignKey<
TColumns extends [PgColumn, ...PgColumn[]],
TForeignColumns extends [PgColumn, ...PgColumn[]]
>(config: {
columns: TColumns;
foreignColumns: TForeignColumns;
name?: string;
}): ForeignKeyBuilder;
interface ForeignKeyBuilder {
onUpdate(action: 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'): this;
onDelete(action: 'cascade' | 'restrict' | 'no action' | 'set null' | 'set default'): this;
}import { unique } from 'drizzle-orm/pg-core';
/**
* Creates a composite unique constraint.
*
* @remarks
* - Ensures unique combination of multiple columns
* - Can specify NULL handling (NULLS DISTINCT/NOT DISTINCT)
* - PostgreSQL 15+ supports NULLS NOT DISTINCT
*
* @example
* ```typescript
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* email: text('email').notNull(),
* provider: text('provider').notNull(),
* providerId: text('provider_id').notNull(),
* }, (table) => ({
* // Unique combination
* providerUnique: unique()
* .on(table.provider, table.providerId),
*
* // Named constraint
* namedUnique: unique('provider_id_unique')
* .on(table.provider, table.providerId),
*
* // With NULL handling (PostgreSQL 15+)
* nullsNotDistinct: unique()
* .on(table.email, table.provider)
* .nullsNotDistinct(),
* }));
*
* // Single column unique constraint
* const products = pgTable('products', {
* id: serial('id').primaryKey(),
* sku: text('sku').notNull().unique(),
* name: text('name').notNull(),
* });
* ```
*/
function unique(name?: string): UniqueConstraintBuilder;
interface UniqueConstraintBuilder {
on(...columns: PgColumn[]): this;
nullsNotDistinct(): this;
}import { check } from 'drizzle-orm/pg-core';
/**
* Creates a check constraint.
*
* @remarks
* - Validates data based on boolean expression
* - Can reference multiple columns
* - Evaluated on INSERT and UPDATE
*
* @example
* ```typescript
* import { sql } from 'drizzle-orm';
*
* const products = pgTable('products', {
* id: serial('id').primaryKey(),
* price: numeric('price', { precision: 10, scale: 2 }).notNull(),
* discountPrice: numeric('discount_price', { precision: 10, scale: 2 }),
* stock: integer('stock').notNull(),
* }, (table) => ({
* // Price must be positive
* positivePrice: check(
* 'positive_price',
* sql`${table.price} > 0`
* ),
*
* // Discount price must be less than price
* validDiscount: check(
* 'valid_discount',
* sql`${table.discountPrice} < ${table.price}`
* ),
*
* // Stock cannot be negative
* nonNegativeStock: check(
* 'non_negative_stock',
* sql`${table.stock} >= 0`
* ),
* }));
*
* const users = pgTable('users', {
* id: serial('id').primaryKey(),
* age: integer('age'),
* email: text('email'),
* }, (table) => ({
* // Age between 18 and 120
* ageRange: check(
* 'age_range',
* sql`${table.age} BETWEEN 18 AND 120`
* ),
*
* // Email format
* emailFormat: check(
* 'email_format',
* sql`${table.email} LIKE '%@%.%'`
* ),
* }));
* ```
*/
function check(name: string, condition: SQL): CheckBuilder;This document covered all PostgreSQL-specific features in Drizzle ORM:
All features include comprehensive examples demonstrating real-world usage patterns with full type safety.