or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mddatabase-connections.mdindex.mdquery-building.mdrelational-queries.mdschema-definition.md

advanced-features.mddocs/

0

# Advanced Features

1

2

Drizzle ORM provides advanced capabilities including schema migrations, query result caching, batch operations, OpenTelemetry tracing, and custom SQL expressions for sophisticated database operations.

3

4

## Schema Migrations

5

6

### Migration Configuration

7

8

```typescript { .api }

9

interface MigrationConfig {

10

migrationsFolder: string;

11

migrationsTable?: string;

12

migrationsSchema?: string;

13

}

14

15

interface MigrationMeta {

16

sql: string[];

17

folderMillis: number;

18

hash: string;

19

bps: boolean;

20

}

21

```

22

23

### Migration Functions

24

25

```typescript { .api }

26

function readMigrationFiles(config: MigrationConfig): MigrationMeta[];

27

28

function migrate<TDatabase extends Database>(

29

db: TDatabase,

30

config: MigrationConfig

31

): Promise<void>;

32

```

33

34

### Database-Specific Migration Support

35

36

```typescript { .api }

37

// PostgreSQL

38

function migrate(db: NodePgDatabase, config: MigrationConfig): Promise<void>;

39

function migrate(db: PostgresJsDatabase, config: MigrationConfig): Promise<void>;

40

41

// MySQL

42

function migrate(db: MySql2Database, config: MigrationConfig): Promise<void>;

43

function migrate(db: PlanetScaleDatabase, config: MigrationConfig): Promise<void>;

44

45

// SQLite

46

function migrate(db: BetterSQLite3Database, config: MigrationConfig): Promise<void>;

47

function migrate(db: LibSQLDatabase, config: MigrationConfig): Promise<void>;

48

```

49

50

## Query Result Caching

51

52

### Cache Interface

53

54

```typescript { .api }

55

interface Cache {

56

get<T>(key: string): Promise<T | null>;

57

set(key: string, value: unknown, ttl?: number): Promise<void>;

58

delete(key: string): Promise<void>;

59

clear(): Promise<void>;

60

onMutate?: () => Promise<void>;

61

}

62

```

63

64

### Upstash Redis Cache Implementation

65

66

```typescript { .api }

67

function createCache(config: {

68

redis: Redis;

69

keyPrefix?: string;

70

ttl?: number;

71

}): Cache;

72

```

73

74

### Cache Configuration

75

76

```typescript

77

import { createCache } from "drizzle-orm/cache/upstash";

78

import { Redis } from "@upstash/redis";

79

80

const redis = new Redis({

81

url: "UPSTASH_REDIS_REST_URL",

82

token: "UPSTASH_REDIS_REST_TOKEN"

83

});

84

85

const cache = createCache({

86

redis,

87

keyPrefix: "drizzle-cache:",

88

ttl: 300 // 5 minutes

89

});

90

91

const db = drizzle(client, { cache });

92

```

93

94

## Batch Operations

95

96

### Batch Interface

97

98

```typescript { .api }

99

interface Database {

100

batch<T extends readonly [...QueryBuilder[]]>(

101

queries: T

102

): Promise<BatchResult<T>>;

103

}

104

105

type BatchResult<T extends readonly QueryBuilder[]> = {

106

[K in keyof T]: T[K] extends QueryBuilder<infer U> ? U : never;

107

};

108

```

109

110

### Batch Execution

111

112

```typescript

113

const batchResult = await db.batch([

114

db.select().from(users).where(eq(users.id, 1)),

115

db.insert(posts).values({ title: "New Post", authorId: 1 }),

116

db.update(users).set({ lastLogin: new Date() }).where(eq(users.id, 1))

117

]);

118

119

const [selectedUser, insertResult, updateResult] = batchResult;

120

```

121

122

## OpenTelemetry Tracing

123

124

### Tracing Configuration

125

126

```typescript { .api }

127

interface TracingConfig {

128

enabled?: boolean;

129

spanName?: string;

130

attributes?: Record<string, string | number | boolean>;

131

}

132

```

133

134

### Automatic Tracing

135

136

When OpenTelemetry is configured in your application, Drizzle ORM automatically creates spans for database operations:

137

138

```typescript

139

import { trace } from "@opentelemetry/api";

140

import { drizzle } from "drizzle-orm/node-postgres";

141

142

// Tracing is automatically enabled when OpenTelemetry is present

143

const db = drizzle(client);

144

145

// Each query will create a span

146

const users = await db.select().from(usersTable); // Creates span "drizzle:select"

147

```

148

149

## Custom Column Types

150

151

### Creating Custom Types

152

153

```typescript { .api }

154

function customType<T>(config: {

155

dataType: () => string;

156

toDriver: (value: T) => unknown;

157

fromDriver?: (value: unknown) => T;

158

}): CustomTypeBuilder<T>;

159

160

interface CustomTypeBuilder<T> {

161

(name: string): CustomColumn<T>;

162

}

163

164

interface CustomColumn<T> extends Column<ColumnBaseConfig<T, 'custom'>> {

165

getSQLType(): string;

166

}

167

```

168

169

### Custom Type Examples

170

171

```typescript

172

// PostgreSQL Point type

173

const point = customType<{ x: number; y: number }>({

174

dataType() {

175

return 'point';

176

},

177

toDriver(value) {

178

return `(${value.x},${value.y})`;

179

},

180

fromDriver(value) {

181

const [x, y] = (value as string).slice(1, -1).split(',').map(Number);

182

return { x, y };

183

},

184

});

185

186

// UUID type with validation

187

const uuid = customType<string>({

188

dataType() {

189

return 'uuid';

190

},

191

toDriver(value) {

192

if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {

193

throw new Error('Invalid UUID format');

194

}

195

return value;

196

},

197

});

198

```

199

200

## Database Introspection

201

202

### Schema Introspection

203

204

```typescript { .api }

205

interface Database {

206

getTableData(): Promise<TableData[]>;

207

getTableStructure(tableName: string): Promise<TableStructure>;

208

}

209

210

interface TableData {

211

name: string;

212

schema?: string;

213

columns: ColumnData[];

214

indexes: IndexData[];

215

foreignKeys: ForeignKeyData[];

216

}

217

```

218

219

## Connection Management

220

221

### Connection Pooling Configuration

222

223

```typescript

224

// PostgreSQL with advanced pool configuration

225

const pool = new Pool({

226

connectionString: "postgresql://...",

227

max: 20,

228

idleTimeoutMillis: 30000,

229

connectionTimeoutMillis: 2000,

230

allowExitOnIdle: true,

231

});

232

233

// MySQL with connection limits

234

const pool = mysql.createPool({

235

host: "localhost",

236

user: "root",

237

password: "password",

238

database: "test",

239

connectionLimit: 10,

240

acquireTimeout: 60000,

241

timeout: 60000,

242

});

243

```

244

245

### Connection Events and Monitoring

246

247

```typescript

248

// Monitor connection events

249

pool.on('connect', (client) => {

250

console.log('New client connected');

251

});

252

253

pool.on('error', (err) => {

254

console.error('Database pool error:', err);

255

});

256

```

257

258

## Performance Optimization

259

260

### Query Performance

261

262

```typescript { .api }

263

interface SelectBuilder {

264

// Query hints for optimization

265

$dynamic(): DynamicSelectBuilder;

266

}

267

268

interface DynamicSelectBuilder {

269

where(condition: SQL | undefined): this;

270

orderBy(...columns: (AnyColumn | SQL)[]): this;

271

}

272

```

273

274

### Prepared Statement Caching

275

276

```typescript

277

// Named prepared statements for reuse

278

const getUserById = db

279

.select()

280

.from(users)

281

.where(eq(users.id, placeholder("id")))

282

.prepare("getUserById");

283

284

// Reuse across multiple executions

285

const user1 = await getUserById.execute({ id: 1 });

286

const user2 = await getUserById.execute({ id: 2 });

287

```

288

289

## Error Handling

290

291

### Custom Error Classes

292

293

```typescript { .api }

294

class DrizzleError extends Error {

295

readonly cause?: unknown;

296

constructor(message: string, cause?: unknown);

297

}

298

299

class TransactionRollbackError extends DrizzleError {

300

constructor(cause?: unknown);

301

}

302

303

class DriverError extends DrizzleError {

304

constructor(message: string, cause?: unknown);

305

}

306

```

307

308

### Error Handling Patterns

309

310

```typescript

311

import { DrizzleError, TransactionRollbackError } from "drizzle-orm";

312

313

try {

314

await db.transaction(async (tx) => {

315

await tx.insert(users).values({ name: "John" });

316

await tx.insert(posts).values({ title: "Post", authorId: 1 });

317

});

318

} catch (error) {

319

if (error instanceof TransactionRollbackError) {

320

console.error("Transaction failed:", error.cause);

321

} else if (error instanceof DrizzleError) {

322

console.error("Database error:", error.message);

323

} else {

324

throw error;

325

}

326

}

327

```

328

329

## Development and Debugging

330

331

### Logging and Debugging

332

333

```typescript { .api }

334

class DefaultLogger implements Logger {

335

logQuery(query: string, params: unknown[]): void;

336

}

337

338

interface Logger {

339

logQuery(query: string, params: unknown[]): void;

340

}

341

```

342

343

### Custom Logger Implementation

344

345

```typescript

346

class CustomLogger implements Logger {

347

logQuery(query: string, params: unknown[]) {

348

console.log(`[${new Date().toISOString()}] Query:`, query);

349

if (params.length > 0) {

350

console.log("Parameters:", params);

351

}

352

}

353

}

354

355

const db = drizzle(client, {

356

logger: new CustomLogger()

357

});

358

```

359

360

### Query Debugging

361

362

```typescript

363

// Log all queries in development

364

const db = drizzle(client, {

365

logger: process.env.NODE_ENV === 'development'

366

});

367

368

// Custom query inspection

369

const query = db.select().from(users).getSQL();

370

console.log("Generated SQL:", query.sql);

371

console.log("Parameters:", query.params);

372

```

373

374

## Database-Specific Advanced Features

375

376

### PostgreSQL Extensions

377

378

```typescript

379

// PostGIS geometry support

380

import { geometry } from "drizzle-orm/pg-core";

381

382

const locations = pgTable("locations", {

383

id: serial("id").primaryKey(),

384

name: text("name"),

385

coordinates: geometry("coordinates", {

386

type: "point",

387

srid: 4326

388

}),

389

});

390

391

// Vector search support

392

import { vector } from "drizzle-orm/pg-core";

393

394

const documents = pgTable("documents", {

395

id: serial("id").primaryKey(),

396

content: text("content"),

397

embedding: vector("embedding", { dimensions: 1536 }),

398

});

399

```

400

401

### MySQL Full-Text Search

402

403

```typescript

404

// Full-text search indexes

405

const articles = mysqlTable("articles", {

406

id: int("id").primaryKey().autoincrement(),

407

title: varchar("title", { length: 255 }),

408

content: text("content"),

409

}, (table) => ({

410

titleContentFts: index("title_content_fts")

411

.on(table.title, table.content)

412

.fulltext(),

413

}));

414

415

// Full-text search queries

416

const results = await db

417

.select()

418

.from(articles)

419

.where(sql`MATCH(${articles.title}, ${articles.content}) AGAINST(${searchTerm} IN BOOLEAN MODE)`);

420

```

421

422

### SQLite Extensions

423

424

```typescript

425

// JSON operations in SQLite

426

const settings = sqliteTable("settings", {

427

id: integer("id").primaryKey(),

428

config: text("config", { mode: "json" }),

429

});

430

431

const result = await db

432

.select()

433

.from(settings)

434

.where(sql`json_extract(${settings.config}, '$.theme') = ${'dark'}`);

435

```

436

437

## Usage Examples

438

439

### Complete Migration Setup

440

441

```typescript

442

import { migrate } from "drizzle-orm/node-postgres/migrator";

443

444

await migrate(db, {

445

migrationsFolder: "./migrations",

446

migrationsTable: "__drizzle_migrations__",

447

migrationsSchema: "public"

448

});

449

```

450

451

### Advanced Caching Strategy

452

453

```typescript

454

const cache = createCache({

455

redis,

456

keyPrefix: "app-cache:",

457

ttl: 300

458

});

459

460

const db = drizzle(client, {

461

cache,

462

schema: { users, posts, usersRelations, postsRelations }

463

});

464

465

// Cached queries are automatically handled

466

const users = await db.query.users.findMany({

467

with: { posts: true }

468

});

469

```

470

471

### Complex Custom Type

472

473

```typescript

474

const money = customType<{ amount: number; currency: string }>({

475

dataType() {

476

return 'decimal(10,2)';

477

},

478

toDriver(value) {

479

return value.amount;

480

},

481

fromDriver(value) {

482

return {

483

amount: Number(value),

484

currency: 'USD' // Could be stored separately or configured

485

};

486

},

487

});

488

489

const products = pgTable("products", {

490

id: serial("id").primaryKey(),

491

name: text("name"),

492

price: money("price"),

493

});

494

```

495

496

## Types

497

498

```typescript { .api }

499

interface MigrationConfig {

500

migrationsFolder: string;

501

migrationsTable?: string;

502

migrationsSchema?: string;

503

}

504

505

interface MigrationMeta {

506

sql: string[];

507

folderMillis: number;

508

hash: string;

509

bps: boolean;

510

}

511

512

interface Cache {

513

get<T>(key: string): Promise<T | null>;

514

set(key: string, value: unknown, ttl?: number): Promise<void>;

515

delete(key: string): Promise<void>;

516

clear(): Promise<void>;

517

onMutate?: () => Promise<void>;

518

}

519

520

interface Logger {

521

logQuery(query: string, params: unknown[]): void;

522

}

523

524

type BatchResult<T extends readonly QueryBuilder[]> = {

525

[K in keyof T]: T[K] extends QueryBuilder<infer U> ? U : never;

526

};

527

528

interface CustomTypeParams<T> {

529

dataType: () => string;

530

toDriver: (value: T) => unknown;

531

fromDriver?: (value: unknown) => T;

532

}

533

```