forge-sql-orm 2.1.11 → 2.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +800 -541
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.js +257 -0
- package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -0
- package/dist/core/ForgeSQLCacheOperations.js +172 -0
- package/dist/core/ForgeSQLCacheOperations.js.map +1 -0
- package/dist/core/ForgeSQLCrudOperations.js +349 -0
- package/dist/core/ForgeSQLCrudOperations.js.map +1 -0
- package/dist/core/ForgeSQLORM.d.ts +1 -1
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.js +1191 -0
- package/dist/core/ForgeSQLORM.js.map +1 -0
- package/dist/core/ForgeSQLQueryBuilder.js +77 -0
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -0
- package/dist/core/ForgeSQLSelectOperations.js +81 -0
- package/dist/core/ForgeSQLSelectOperations.js.map +1 -0
- package/dist/core/SystemTables.js +258 -0
- package/dist/core/SystemTables.js.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.js +527 -0
- package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -0
- package/dist/utils/cacheContextUtils.d.ts.map +1 -1
- package/dist/utils/cacheContextUtils.js +198 -0
- package/dist/utils/cacheContextUtils.js.map +1 -0
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.js +383 -0
- package/dist/utils/cacheUtils.js.map +1 -0
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriver.js +139 -0
- package/dist/utils/forgeDriver.js.map +1 -0
- package/dist/utils/forgeDriverProxy.js +68 -0
- package/dist/utils/forgeDriverProxy.js.map +1 -0
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +28 -0
- package/dist/utils/metadataContextUtils.js.map +1 -0
- package/dist/utils/requestTypeContextUtils.js +10 -0
- package/dist/utils/requestTypeContextUtils.js.map +1 -0
- package/dist/utils/sqlHints.js +52 -0
- package/dist/utils/sqlHints.js.map +1 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.js +590 -0
- package/dist/utils/sqlUtils.js.map +1 -0
- package/dist/webtriggers/applyMigrationsWebTrigger.js +77 -0
- package/dist/webtriggers/applyMigrationsWebTrigger.js.map +1 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.js +83 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.js.map +1 -0
- package/dist/webtriggers/dropMigrationWebTrigger.js +54 -0
- package/dist/webtriggers/dropMigrationWebTrigger.js.map +1 -0
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js +54 -0
- package/dist/webtriggers/dropTablesMigrationWebTrigger.js.map +1 -0
- package/dist/webtriggers/fetchSchemaWebTrigger.js +82 -0
- package/dist/webtriggers/fetchSchemaWebTrigger.js.map +1 -0
- package/dist/webtriggers/index.js +40 -0
- package/dist/webtriggers/index.js.map +1 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -1
- package/dist/webtriggers/slowQuerySchedulerTrigger.js +80 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.js.map +1 -0
- package/package.json +28 -23
- package/src/core/ForgeSQLAnalyseOperations.ts +3 -2
- package/src/core/ForgeSQLORM.ts +33 -27
- package/src/lib/drizzle/extensions/additionalActions.ts +11 -0
- package/src/utils/cacheContextUtils.ts +9 -6
- package/src/utils/cacheUtils.ts +28 -5
- package/src/utils/forgeDriver.ts +10 -6
- package/src/utils/metadataContextUtils.ts +1 -4
- package/src/utils/sqlUtils.ts +136 -125
- package/src/webtriggers/slowQuerySchedulerTrigger.ts +40 -33
- package/dist/ForgeSQLORM.js +0 -3896
- package/dist/ForgeSQLORM.js.map +0 -1
- package/dist/ForgeSQLORM.mjs +0 -3879
- package/dist/ForgeSQLORM.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
[](https://coveralls.io/github/vzakharchenko/forge-sql-orm?branch=master)
|
|
12
12
|
[](https://deepscan.io/dashboard#view=project&tid=26652&pid=29272&bid=940614)
|
|
13
13
|
|
|
14
|
-
|
|
15
14
|
**Forge-SQL-ORM** is an ORM designed for working with [@forge/sql](https://developer.atlassian.com/platform/forge/storage-reference/sql-tutorial/) in **Atlassian Forge**. It is built on top of [Drizzle ORM](https://orm.drizzle.team) and provides advanced capabilities for working with relational databases inside Forge.
|
|
16
15
|
|
|
17
16
|
## Key Features
|
|
17
|
+
|
|
18
18
|
- ✅ **Custom Drizzle Driver** for direct integration with @forge/sql
|
|
19
19
|
- ✅ **Local Cache System (Level 1)** for in-memory query optimization within single resolver invocation scope
|
|
20
20
|
- ✅ **Global Cache System (Level 2)** with cross-invocation caching, automatic cache invalidation and context-aware operations (using [@forge/kvs](https://developer.atlassian.com/platform/forge/storage-reference/storage-api-custom-entities/) )
|
|
21
|
-
- ✅ **Performance Monitoring**: Query execution metrics and analysis capabilities with automatic error analysis for timeout and OOM errors
|
|
21
|
+
- ✅ **Performance Monitoring**: Query execution metrics and analysis capabilities with automatic error analysis for timeout and OOM errors, plus scheduled slow query monitoring with execution plans
|
|
22
22
|
- ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
|
|
23
23
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
24
24
|
- ✅ **Advanced Query Methods**: `selectFrom()`, `selectDistinctFrom()`, `selectCacheableFrom()`, `selectDistinctCacheableFrom()` for all-column queries with field aliasing
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
## Table of Contents
|
|
38
38
|
|
|
39
39
|
### 🚀 Getting Started
|
|
40
|
+
|
|
40
41
|
- [Key Features](#key-features)
|
|
41
42
|
- [Usage Approaches](#usage-approaches)
|
|
42
43
|
- [Installation](#installation)
|
|
@@ -44,16 +45,19 @@
|
|
|
44
45
|
- [Quick Start](#quick-start)
|
|
45
46
|
|
|
46
47
|
### 📖 Core Features
|
|
48
|
+
|
|
47
49
|
- [Field Name Collision Prevention](#field-name-collision-prevention-in-complex-queries)
|
|
48
50
|
- [Drizzle Usage with forge-sql-orm](#drizzle-usage-with-forge-sql-orm)
|
|
49
51
|
- [Direct Drizzle Usage with Custom Driver](#direct-drizzle-usage-with-custom-driver)
|
|
50
52
|
|
|
51
53
|
### 🗄️ Database Operations
|
|
54
|
+
|
|
52
55
|
- [Fetch Data](#fetch-data)
|
|
53
56
|
- [Modify Operations](#modify-operations)
|
|
54
57
|
- [SQL Utilities](#sql-utilities)
|
|
55
58
|
|
|
56
59
|
### ⚡ Caching System
|
|
60
|
+
|
|
57
61
|
- [Setting Up Caching with @forge/kvs](#setting-up-caching-with-forgekvs-optional)
|
|
58
62
|
- [Global Cache System (Level 2)](#global-cache-system-level-2)
|
|
59
63
|
- [Cache Context Operations](#cache-context-operations)
|
|
@@ -62,18 +66,22 @@
|
|
|
62
66
|
- [Manual Cache Management](#manual-cache-management)
|
|
63
67
|
|
|
64
68
|
### 🔒 Advanced Features
|
|
69
|
+
|
|
65
70
|
- [Optimistic Locking](#optimistic-locking)
|
|
66
71
|
- [Query Analysis and Performance Optimization](#query-analysis-and-performance-optimization)
|
|
67
|
-
- [
|
|
72
|
+
- [Automatic Error Analysis](#automatic-error-analysis) - Automatic timeout and OOM error detection with execution plans
|
|
73
|
+
- [Slow Query Monitoring](#slow-query-monitoring) - Scheduled monitoring of slow queries with execution plans
|
|
68
74
|
- [Date and Time Types](#date-and-time-types)
|
|
69
75
|
|
|
70
76
|
### 🛠️ Development Tools
|
|
77
|
+
|
|
71
78
|
- [CLI Commands](#cli-commands) | [CLI Documentation](forge-sql-orm-cli/README.md)
|
|
72
79
|
- [Web Triggers for Migrations](#web-triggers-for-migrations)
|
|
73
80
|
- [Step-by-Step Migration Workflow](#step-by-step-migration-workflow)
|
|
74
81
|
- [Drop Migrations](#drop-migrations)
|
|
75
82
|
|
|
76
83
|
### 📚 Examples
|
|
84
|
+
|
|
77
85
|
- [Simple Example](examples/forge-sql-orm-example-simple)
|
|
78
86
|
- [Drizzle Driver Example](examples/forge-sql-orm-example-drizzle-driver-simple)
|
|
79
87
|
- [Optimistic Locking Example](examples/forge-sql-orm-example-optimistic-locking)
|
|
@@ -84,17 +92,20 @@
|
|
|
84
92
|
- [Cache Example](examples/forge-sql-orm-example-cache) - Advanced caching capabilities with performance monitoring
|
|
85
93
|
|
|
86
94
|
### 📚 Reference
|
|
95
|
+
|
|
87
96
|
- [ForgeSqlOrmOptions](#forgesqlormoptions)
|
|
88
97
|
- [Migration Guide](#migration-guide)
|
|
89
98
|
|
|
90
99
|
## 🚀 Quick Navigation
|
|
91
100
|
|
|
92
101
|
**New to Forge-SQL-ORM?** Start here:
|
|
102
|
+
|
|
93
103
|
- [Quick Start](#quick-start) - Get up and running in 5 minutes
|
|
94
104
|
- [Installation](#installation) - Complete setup guide
|
|
95
105
|
- [Basic Usage Examples](#fetch-data) - Simple query examples
|
|
96
106
|
|
|
97
107
|
**Looking for specific features?**
|
|
108
|
+
|
|
98
109
|
- [Global Cache System (Level 2)](#global-cache-system-level-2) - Cross-invocation persistent caching
|
|
99
110
|
- [Local Cache System (Level 1)](#local-cache-operations-level-1) - In-memory invocation caching
|
|
100
111
|
- [Optimistic Locking](#optimistic-locking) - Data consistency
|
|
@@ -102,6 +113,7 @@
|
|
|
102
113
|
- [Query Analysis](#query-analysis-and-performance-optimization) - Performance optimization
|
|
103
114
|
|
|
104
115
|
**Looking for practical examples?**
|
|
116
|
+
|
|
105
117
|
- [Simple Example](examples/forge-sql-orm-example-simple) - Basic ORM usage
|
|
106
118
|
- [Optimistic Locking Example](examples/forge-sql-orm-example-optimistic-locking) - Real-world conflict handling
|
|
107
119
|
- [Organization Tracker Example](examples/forge-sql-orm-example-org-tracker) - Complex relationships
|
|
@@ -110,24 +122,27 @@
|
|
|
110
122
|
|
|
111
123
|
## Usage Approaches
|
|
112
124
|
|
|
113
|
-
|
|
114
125
|
### 1. Full Forge-SQL-ORM Usage
|
|
126
|
+
|
|
115
127
|
```typescript
|
|
116
128
|
import ForgeSQL from "forge-sql-orm";
|
|
117
129
|
const forgeSQL = new ForgeSQL();
|
|
118
130
|
```
|
|
131
|
+
|
|
119
132
|
Best for: Advanced features like optimistic locking, automatic versioning, and automatic field name collision prevention in complex queries.
|
|
120
133
|
|
|
121
134
|
### 2. Direct Drizzle Usage
|
|
135
|
+
|
|
122
136
|
```typescript
|
|
123
137
|
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
124
138
|
import { forgeDriver } from "forge-sql-orm";
|
|
125
139
|
const db = drizzle(forgeDriver);
|
|
126
140
|
```
|
|
127
|
-
Best for: Simple Modify operations without optimistic locking. Note that you need to manually patch drizzle `patchDbWithSelectAliased` for select fields to prevent field name collisions in Atlassian Forge SQL.
|
|
128
141
|
|
|
142
|
+
Best for: Simple Modify operations without optimistic locking. Note that you need to manually patch drizzle `patchDbWithSelectAliased` for select fields to prevent field name collisions in Atlassian Forge SQL.
|
|
129
143
|
|
|
130
144
|
### 3. Local Cache Optimization
|
|
145
|
+
|
|
131
146
|
```typescript
|
|
132
147
|
import ForgeSQL from "forge-sql-orm";
|
|
133
148
|
const forgeSQL = new ForgeSQL();
|
|
@@ -135,28 +150,28 @@ const forgeSQL = new ForgeSQL();
|
|
|
135
150
|
// Optimize repeated queries within a single invocation
|
|
136
151
|
await forgeSQL.executeWithLocalContext(async () => {
|
|
137
152
|
// Multiple queries here will benefit from local caching
|
|
138
|
-
const users = await forgeSQL
|
|
139
|
-
.
|
|
140
|
-
|
|
153
|
+
const users = await forgeSQL
|
|
154
|
+
.select({ id: users.id, name: users.name })
|
|
155
|
+
.from(users)
|
|
156
|
+
.where(eq(users.active, true));
|
|
157
|
+
|
|
141
158
|
// This query will use local cache (no database call)
|
|
142
|
-
const cachedUsers = await forgeSQL
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
// Using new methods for better performance
|
|
146
|
-
const usersFrom = await forgeSQL.selectFrom(users)
|
|
159
|
+
const cachedUsers = await forgeSQL
|
|
160
|
+
.select({ id: users.id, name: users.name })
|
|
161
|
+
.from(users)
|
|
147
162
|
.where(eq(users.active, true));
|
|
148
|
-
|
|
163
|
+
|
|
164
|
+
// Using new methods for better performance
|
|
165
|
+
const usersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
166
|
+
|
|
149
167
|
// This will use local cache (no database call)
|
|
150
|
-
const cachedUsersFrom = await forgeSQL.selectFrom(users)
|
|
151
|
-
|
|
152
|
-
|
|
168
|
+
const cachedUsersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
169
|
+
|
|
153
170
|
// Raw SQL with local caching
|
|
154
|
-
const rawUsers = await forgeSQL.execute(
|
|
155
|
-
"SELECT id, name FROM users WHERE active = ?",
|
|
156
|
-
[true]
|
|
157
|
-
);
|
|
171
|
+
const rawUsers = await forgeSQL.execute("SELECT id, name FROM users WHERE active = ?", [true]);
|
|
158
172
|
});
|
|
159
173
|
```
|
|
174
|
+
|
|
160
175
|
Best for: Performance optimization of repeated queries within resolvers or single invocation contexts.
|
|
161
176
|
|
|
162
177
|
## Field Name Collision Prevention in Complex Queries
|
|
@@ -166,6 +181,7 @@ When working with complex queries involving multiple tables (joins, inner joins,
|
|
|
166
181
|
Forge-SQL-ORM provides two ways to handle this:
|
|
167
182
|
|
|
168
183
|
### Using Forge-SQL-ORM
|
|
184
|
+
|
|
169
185
|
```typescript
|
|
170
186
|
import ForgeSQL from "forge-sql-orm";
|
|
171
187
|
|
|
@@ -173,12 +189,13 @@ const forgeSQL = new ForgeSQL();
|
|
|
173
189
|
|
|
174
190
|
// Automatic field name collision prevention
|
|
175
191
|
await forgeSQL
|
|
176
|
-
.select({user: users, order: orders})
|
|
192
|
+
.select({ user: users, order: orders })
|
|
177
193
|
.from(orders)
|
|
178
194
|
.innerJoin(users, eq(orders.userId, users.id));
|
|
179
195
|
```
|
|
180
196
|
|
|
181
197
|
### Using Direct Drizzle
|
|
198
|
+
|
|
182
199
|
```typescript
|
|
183
200
|
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
184
201
|
import { forgeDriver, patchDbWithSelectAliased } from "forge-sql-orm";
|
|
@@ -187,18 +204,18 @@ const db = patchDbWithSelectAliased(drizzle(forgeDriver));
|
|
|
187
204
|
|
|
188
205
|
// Manual field name collision prevention
|
|
189
206
|
await db
|
|
190
|
-
.selectAliased({user: users, order: orders})
|
|
207
|
+
.selectAliased({ user: users, order: orders })
|
|
191
208
|
.from(orders)
|
|
192
209
|
.innerJoin(users, eq(orders.userId, users.id));
|
|
193
210
|
```
|
|
194
211
|
|
|
195
212
|
### Important Notes
|
|
213
|
+
|
|
196
214
|
- This is a specific behavior of Atlassian Forge SQL, not Drizzle ORM
|
|
197
215
|
- For complex queries involving multiple tables, it's recommended to always specify select fields and avoid using `select()` without field selection
|
|
198
216
|
- The solution automatically creates unique aliases for each field by prefixing them with the table name
|
|
199
217
|
- This ensures that fields with the same name from different tables remain distinct in the query results
|
|
200
218
|
|
|
201
|
-
|
|
202
219
|
## Installation
|
|
203
220
|
|
|
204
221
|
Forge-SQL-ORM is designed to work with @forge/sql and requires some additional setup to ensure compatibility within Atlassian Forge.
|
|
@@ -206,16 +223,35 @@ Forge-SQL-ORM is designed to work with @forge/sql and requires some additional s
|
|
|
206
223
|
✅ Step 1: Install Dependencies
|
|
207
224
|
|
|
208
225
|
**Basic installation (without caching):**
|
|
226
|
+
|
|
209
227
|
```sh
|
|
210
228
|
npm install forge-sql-orm @forge/sql drizzle-orm -S
|
|
211
229
|
```
|
|
212
230
|
|
|
213
231
|
**With caching support:**
|
|
232
|
+
|
|
214
233
|
```sh
|
|
215
234
|
npm install forge-sql-orm @forge/sql @forge/kvs drizzle-orm -S
|
|
216
235
|
```
|
|
217
236
|
|
|
237
|
+
**⚠️ Important for UI-Kit projects:**
|
|
238
|
+
|
|
239
|
+
If you're installing `forge-sql-orm` in a UI-Kit project (projects using `@forge/react`), you may encounter peer dependency conflicts with `@types/react`. This is due to a conflict between `@types/react@18` (required by `@forge/react`) and `@types/react@19` (optional peer dependency from `drizzle-orm` via `bun-types`).
|
|
240
|
+
|
|
241
|
+
To resolve this, use the `--legacy-peer-deps` flag:
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
# Basic installation for UI-Kit projects
|
|
245
|
+
npm install forge-sql-orm @forge/sql drizzle-orm -S --legacy-peer-deps
|
|
246
|
+
|
|
247
|
+
# With caching support for UI-Kit projects
|
|
248
|
+
npm install forge-sql-orm @forge/sql @forge/kvs drizzle-orm -S --legacy-peer-deps
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Note:** The `--legacy-peer-deps` flag tells npm to ignore peer dependency conflicts. This is safe in this case because `bun-types` is an optional peer dependency and doesn't affect the functionality of `forge-sql-orm` in Forge environments.
|
|
252
|
+
|
|
218
253
|
This will:
|
|
254
|
+
|
|
219
255
|
- Install Forge-SQL-ORM (the ORM for @forge/sql)
|
|
220
256
|
- Install @forge/sql, the Forge database layer
|
|
221
257
|
- Install @forge/kvs, the Forge Key-Value Store for caching (optional, only needed for caching features)
|
|
@@ -226,6 +262,7 @@ This will:
|
|
|
226
262
|
## Quick Start
|
|
227
263
|
|
|
228
264
|
### 1. Basic Setup
|
|
265
|
+
|
|
229
266
|
```typescript
|
|
230
267
|
import ForgeSQL from "forge-sql-orm";
|
|
231
268
|
|
|
@@ -237,44 +274,49 @@ const users = await forgeSQL.select().from(users);
|
|
|
237
274
|
```
|
|
238
275
|
|
|
239
276
|
### 2. With Caching (Optional)
|
|
277
|
+
|
|
240
278
|
```typescript
|
|
241
279
|
import ForgeSQL from "forge-sql-orm";
|
|
242
280
|
|
|
243
281
|
// Initialize with caching
|
|
244
282
|
const forgeSQL = new ForgeSQL({
|
|
245
283
|
cacheEntityName: "cache",
|
|
246
|
-
cacheTTL: 300
|
|
284
|
+
cacheTTL: 300,
|
|
247
285
|
});
|
|
248
286
|
|
|
249
287
|
// Cached query
|
|
250
|
-
const users = await forgeSQL
|
|
251
|
-
.
|
|
288
|
+
const users = await forgeSQL
|
|
289
|
+
.selectCacheable({ id: users.id, name: users.name })
|
|
290
|
+
.from(users)
|
|
291
|
+
.where(eq(users.active, true));
|
|
252
292
|
```
|
|
253
293
|
|
|
254
294
|
### 3. Local Cache Optimization
|
|
295
|
+
|
|
255
296
|
```typescript
|
|
256
297
|
// Optimize repeated queries within a single invocation
|
|
257
298
|
await forgeSQL.executeWithLocalContext(async () => {
|
|
258
|
-
const users = await forgeSQL
|
|
259
|
-
.
|
|
260
|
-
|
|
299
|
+
const users = await forgeSQL
|
|
300
|
+
.select({ id: users.id, name: users.name })
|
|
301
|
+
.from(users)
|
|
302
|
+
.where(eq(users.active, true));
|
|
303
|
+
|
|
261
304
|
// This query will use local cache (no database call)
|
|
262
|
-
const cachedUsers = await forgeSQL
|
|
263
|
-
.
|
|
264
|
-
|
|
265
|
-
// Using new methods for better performance
|
|
266
|
-
const usersFrom = await forgeSQL.selectFrom(users)
|
|
305
|
+
const cachedUsers = await forgeSQL
|
|
306
|
+
.select({ id: users.id, name: users.name })
|
|
307
|
+
.from(users)
|
|
267
308
|
.where(eq(users.active, true));
|
|
268
|
-
|
|
309
|
+
|
|
310
|
+
// Using new methods for better performance
|
|
311
|
+
const usersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
312
|
+
|
|
269
313
|
// Raw SQL with local caching
|
|
270
|
-
const rawUsers = await forgeSQL.execute(
|
|
271
|
-
"SELECT id, name FROM users WHERE active = ?",
|
|
272
|
-
[true]
|
|
273
|
-
);
|
|
314
|
+
const rawUsers = await forgeSQL.execute("SELECT id, name FROM users WHERE active = ?", [true]);
|
|
274
315
|
});
|
|
275
316
|
```
|
|
276
317
|
|
|
277
318
|
### 4. Resolver Performance Monitoring
|
|
319
|
+
|
|
278
320
|
```typescript
|
|
279
321
|
// Resolver with performance monitoring
|
|
280
322
|
resolver.define("fetch", async (req: Request) => {
|
|
@@ -283,22 +325,23 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
283
325
|
async () => {
|
|
284
326
|
// Resolver logic with multiple queries
|
|
285
327
|
const users = await forgeSQL.selectFrom(demoUsers);
|
|
286
|
-
const orders = await forgeSQL
|
|
328
|
+
const orders = await forgeSQL
|
|
329
|
+
.selectFrom(demoOrders)
|
|
287
330
|
.where(eq(demoOrders.userId, demoUsers.id));
|
|
288
331
|
return { users, orders };
|
|
289
332
|
},
|
|
290
333
|
async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
291
334
|
const threshold = 500; // ms baseline for this resolver
|
|
292
|
-
|
|
335
|
+
|
|
293
336
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
294
|
-
console.warn(
|
|
337
|
+
console.warn(
|
|
338
|
+
`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`,
|
|
339
|
+
);
|
|
295
340
|
await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
296
341
|
} else if (totalDbExecutionTime > threshold) {
|
|
297
|
-
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
342
|
+
console.debug(`[Performance Debug fetch] High DB time: ${totalDbExecutionTime} ms`);
|
|
298
343
|
}
|
|
299
|
-
|
|
300
|
-
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
301
|
-
}
|
|
344
|
+
},
|
|
302
345
|
);
|
|
303
346
|
} catch (e) {
|
|
304
347
|
const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
@@ -309,6 +352,7 @@ resolver.define("fetch", async (req: Request) => {
|
|
|
309
352
|
```
|
|
310
353
|
|
|
311
354
|
### 5. Next Steps
|
|
355
|
+
|
|
312
356
|
- [Full Installation Guide](#installation) - Complete setup instructions
|
|
313
357
|
- [Core Features](#core-features) - Learn about key capabilities
|
|
314
358
|
- [Global Cache System (Level 2)](#global-cache-system-level-2) - Cross-invocation caching features
|
|
@@ -344,47 +388,44 @@ const db = forgeSQL.getDrizzleQueryBuilder();
|
|
|
344
388
|
const users = await db.select().from(users);
|
|
345
389
|
|
|
346
390
|
// Using new methods for enhanced functionality
|
|
347
|
-
const usersFrom = await forgeSQL.selectFrom(users)
|
|
348
|
-
.where(eq(users.active, true));
|
|
391
|
+
const usersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
349
392
|
|
|
350
|
-
const usersDistinct = await forgeSQL.selectDistinctFrom(users)
|
|
351
|
-
.where(eq(users.active, true));
|
|
393
|
+
const usersDistinct = await forgeSQL.selectDistinctFrom(users).where(eq(users.active, true));
|
|
352
394
|
|
|
353
|
-
const usersCacheable = await forgeSQL.selectCacheableFrom(users)
|
|
354
|
-
.where(eq(users.active, true));
|
|
395
|
+
const usersCacheable = await forgeSQL.selectCacheableFrom(users).where(eq(users.active, true));
|
|
355
396
|
|
|
356
397
|
// Raw SQL execution
|
|
357
|
-
const rawUsers = await forgeSQL.execute(
|
|
358
|
-
"SELECT * FROM users WHERE active = ?",
|
|
359
|
-
[true]
|
|
360
|
-
);
|
|
398
|
+
const rawUsers = await forgeSQL.execute("SELECT * FROM users WHERE active = ?", [true]);
|
|
361
399
|
|
|
362
400
|
// Raw SQL with caching
|
|
401
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
363
402
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
364
|
-
"SELECT * FROM users WHERE active = ?",
|
|
365
|
-
[true],
|
|
366
|
-
300
|
|
403
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
404
|
+
[true],
|
|
405
|
+
300,
|
|
367
406
|
);
|
|
368
407
|
|
|
369
408
|
// Raw SQL with execution metadata and performance monitoring
|
|
370
409
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
371
410
|
async () => {
|
|
372
411
|
const users = await forgeSQL.selectFrom(usersTable);
|
|
373
|
-
const orders = await forgeSQL
|
|
412
|
+
const orders = await forgeSQL
|
|
413
|
+
.selectFrom(ordersTable)
|
|
414
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
374
415
|
return { users, orders };
|
|
375
416
|
},
|
|
376
417
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
377
418
|
const threshold = 500; // ms baseline for this resolver
|
|
378
|
-
|
|
419
|
+
|
|
379
420
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
380
421
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
381
422
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
382
423
|
} else if (totalDbExecutionTime > threshold) {
|
|
383
424
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
384
425
|
}
|
|
385
|
-
|
|
426
|
+
|
|
386
427
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
387
|
-
}
|
|
428
|
+
},
|
|
388
429
|
);
|
|
389
430
|
|
|
390
431
|
// DDL operations for schema modifications
|
|
@@ -403,25 +444,25 @@ await forgeSQL.executeDDLActions(async () => {
|
|
|
403
444
|
SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
|
|
404
445
|
WHERE AVG_LATENCY > 1000000
|
|
405
446
|
`);
|
|
406
|
-
|
|
447
|
+
|
|
407
448
|
// Execute complex analysis queries in DDL context
|
|
408
449
|
const performanceData = await forgeSQL.execute(`
|
|
409
450
|
SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
|
|
410
451
|
WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
|
|
411
452
|
`);
|
|
412
|
-
|
|
453
|
+
|
|
413
454
|
return { slowQueries, performanceData };
|
|
414
455
|
});
|
|
415
456
|
|
|
416
457
|
// Common Table Expressions (CTEs)
|
|
417
458
|
const userStats = await forgeSQL
|
|
418
459
|
.with(
|
|
419
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true)).as(
|
|
420
|
-
forgeSQL.selectFrom(orders).where(eq(orders.status,
|
|
460
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)).as("activeUsers"),
|
|
461
|
+
forgeSQL.selectFrom(orders).where(eq(orders.status, "completed")).as("completedOrders"),
|
|
421
462
|
)
|
|
422
463
|
.select({
|
|
423
464
|
totalActiveUsers: sql`COUNT(au.id)`,
|
|
424
|
-
totalCompletedOrders: sql`COUNT(co.id)
|
|
465
|
+
totalCompletedOrders: sql`COUNT(co.id)`,
|
|
425
466
|
})
|
|
426
467
|
.from(sql`activeUsers au`)
|
|
427
468
|
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
|
|
@@ -460,7 +501,7 @@ await forgeSQL.executeWithCacheContext(async () => {
|
|
|
460
501
|
await db.updateWithCacheContext(users)...;
|
|
461
502
|
await db.deleteWithCacheContext(users)...;
|
|
462
503
|
// invoke without cache
|
|
463
|
-
const users = await db.selectAliasedCacheable(getTableColumns(users)).from(users);
|
|
504
|
+
const users = await db.selectAliasedCacheable(getTableColumns(users)).from(users);
|
|
464
505
|
// Cache is cleared only once at the end for all affected tables
|
|
465
506
|
});
|
|
466
507
|
|
|
@@ -476,14 +517,15 @@ const usersCacheable = await forgeSQL.selectCacheableFrom(users)
|
|
|
476
517
|
|
|
477
518
|
// Raw SQL execution
|
|
478
519
|
const rawUsers = await forgeSQL.execute(
|
|
479
|
-
"SELECT * FROM users WHERE active = ?",
|
|
520
|
+
"SELECT * FROM users WHERE active = ?",
|
|
480
521
|
[true]
|
|
481
522
|
);
|
|
482
523
|
|
|
483
524
|
// Raw SQL with caching
|
|
525
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
484
526
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
485
|
-
"SELECT * FROM users WHERE active = ?",
|
|
486
|
-
[true],
|
|
527
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
528
|
+
[true],
|
|
487
529
|
300
|
|
488
530
|
);
|
|
489
531
|
|
|
@@ -496,14 +538,14 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
496
538
|
},
|
|
497
539
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
498
540
|
const threshold = 500; // ms baseline for this resolver
|
|
499
|
-
|
|
541
|
+
|
|
500
542
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
501
543
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
502
544
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
503
545
|
} else if (totalDbExecutionTime > threshold) {
|
|
504
546
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
505
547
|
}
|
|
506
|
-
|
|
548
|
+
|
|
507
549
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
508
550
|
}
|
|
509
551
|
);
|
|
@@ -518,6 +560,7 @@ The caching system is optional and only needed if you want to use cache-related
|
|
|
518
560
|
To use caching, you need to use Forge-SQL-ORM methods that support cache management:
|
|
519
561
|
|
|
520
562
|
**Methods that perform cache eviction after execution and in cache context (batch eviction):**
|
|
563
|
+
|
|
521
564
|
- `forgeSQL.insertAndEvictCache()`
|
|
522
565
|
- `forgeSQL.updateAndEvictCache()`
|
|
523
566
|
- `forgeSQL.deleteAndEvictCache()`
|
|
@@ -527,6 +570,7 @@ To use caching, you need to use Forge-SQL-ORM methods that support cache managem
|
|
|
527
570
|
- `forgeSQL.getDrizzleQueryBuilder().deleteAndEvictCache()`
|
|
528
571
|
|
|
529
572
|
**Methods that participate in cache context only (batch eviction):**
|
|
573
|
+
|
|
530
574
|
- All methods except the default Drizzle methods:
|
|
531
575
|
- `forgeSQL.insert()`
|
|
532
576
|
- `forgeSQL.update()`
|
|
@@ -537,17 +581,20 @@ To use caching, you need to use Forge-SQL-ORM methods that support cache managem
|
|
|
537
581
|
- `forgeSQL.getDrizzleQueryBuilder().deleteWithCacheContext()`
|
|
538
582
|
|
|
539
583
|
**Methods do not do evict cache, better do not use with cache feature:**
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
584
|
+
|
|
585
|
+
- `forgeSQL.getDrizzleQueryBuilder().insert()`
|
|
586
|
+
- `forgeSQL.getDrizzleQueryBuilder().update()`
|
|
587
|
+
- `forgeSQL.getDrizzleQueryBuilder().delete()`
|
|
543
588
|
|
|
544
589
|
**Cacheable methods:**
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
590
|
+
|
|
591
|
+
- `forgeSQL.selectCacheable()`
|
|
592
|
+
- `forgeSQL.selectDistinctCacheable()`
|
|
593
|
+
- `forgeSQL.getDrizzleQueryBuilder().selectAliasedCacheable()`
|
|
594
|
+
- `forgeSQL.getDrizzleQueryBuilder().selectAliasedDistinctCacheable()`
|
|
549
595
|
|
|
550
596
|
**Cache context example:**
|
|
597
|
+
|
|
551
598
|
```typescript
|
|
552
599
|
await forgeSQL.executeWithCacheContext(async () => {
|
|
553
600
|
// These methods participate in batch cache clearing
|
|
@@ -558,7 +605,6 @@ await forgeSQL.executeWithCacheContext(async () => {
|
|
|
558
605
|
});
|
|
559
606
|
```
|
|
560
607
|
|
|
561
|
-
|
|
562
608
|
The diagram below shows the lifecycle of a cacheable query in Forge-SQL-ORM:
|
|
563
609
|
|
|
564
610
|
1. Resolver calls forge-sql-orm with a SQL query and parameters.
|
|
@@ -570,7 +616,6 @@ The diagram below shows the lifecycle of a cacheable query in Forge-SQL-ORM:
|
|
|
570
616
|
|
|
571
617
|

|
|
572
618
|
|
|
573
|
-
|
|
574
619
|
The diagram below shows how Evict Cache works in Forge-SQL-ORM:
|
|
575
620
|
|
|
576
621
|
1. **Data modification** is executed through `@forge/sql` (e.g., `UPDATE users ...`).
|
|
@@ -604,19 +649,23 @@ The diagram below shows how Cache Context works:
|
|
|
604
649
|
|
|
605
650
|

|
|
606
651
|
|
|
607
|
-
|
|
608
652
|
### Important Considerations
|
|
609
653
|
|
|
610
654
|
**@forge/kvs Limits:**
|
|
611
655
|
Please review the [official @forge/kvs quotas and limits](https://developer.atlassian.com/platform/forge/platform-quotas-and-limits/#kvs-and-custom-entity-store-quotas) before implementing caching.
|
|
612
656
|
|
|
613
657
|
**Caching Guidelines:**
|
|
658
|
+
|
|
614
659
|
- Don't cache everything - be selective about what to cache
|
|
615
660
|
- Don't cache simple and fast queries - sometimes direct query is faster than cache
|
|
616
661
|
- Consider data size and frequency of changes
|
|
617
662
|
- Monitor cache usage to stay within quotas
|
|
618
663
|
- Use appropriate TTL values
|
|
619
664
|
|
|
665
|
+
**⚠️ Important Cache Limitations:**
|
|
666
|
+
|
|
667
|
+
- **Table names starting with `a_`**: Tables whose names start with `a_` (case-insensitive) are automatically ignored in cache operations. KVS Cache will not work with such tables, and they will be excluded from cache invalidation and cache key generation.
|
|
668
|
+
|
|
620
669
|
### Step 1: Install Dependencies
|
|
621
670
|
|
|
622
671
|
```bash
|
|
@@ -653,18 +702,18 @@ modules:
|
|
|
653
702
|
- key: clearCache
|
|
654
703
|
handler: index.clearCache
|
|
655
704
|
```
|
|
705
|
+
|
|
656
706
|
```typescript
|
|
657
707
|
// Example usage in your Forge app
|
|
658
708
|
import { clearCacheSchedulerTrigger } from "forge-sql-orm";
|
|
659
709
|
|
|
660
710
|
export const clearCache = () => {
|
|
661
|
-
return clearCacheSchedulerTrigger({
|
|
711
|
+
return clearCacheSchedulerTrigger({
|
|
662
712
|
cacheEntityName: "cache",
|
|
663
713
|
});
|
|
664
714
|
};
|
|
665
715
|
```
|
|
666
716
|
|
|
667
|
-
|
|
668
717
|
### Step 3: Configure ORM Options
|
|
669
718
|
|
|
670
719
|
Set the cache entity name in your ForgeSQL configuration:
|
|
@@ -681,6 +730,7 @@ const forgeSQL = new ForgeSQL(options);
|
|
|
681
730
|
```
|
|
682
731
|
|
|
683
732
|
**Important Notes:**
|
|
733
|
+
|
|
684
734
|
- The `cacheEntityName` must exactly match the `name` in your manifest storage entities
|
|
685
735
|
- The entity attributes (`sql`, `expiration`, `data`) are required for proper cache functionality
|
|
686
736
|
- Indexes on `sql` and `expiration` improve cache lookup performance
|
|
@@ -692,11 +742,14 @@ const forgeSQL = new ForgeSQL(options);
|
|
|
692
742
|
**Basic setup (without caching):**
|
|
693
743
|
|
|
694
744
|
**package.json:**
|
|
745
|
+
|
|
695
746
|
```shell
|
|
696
747
|
npm install forge-sql-orm @forge/sql drizzle-orm -S
|
|
748
|
+
# For UI-Kit projects, use: npm install forge-sql-orm @forge/sql drizzle-orm -S --legacy-peer-deps
|
|
697
749
|
```
|
|
698
750
|
|
|
699
751
|
**manifest.yml:**
|
|
752
|
+
|
|
700
753
|
```yaml
|
|
701
754
|
modules:
|
|
702
755
|
sql:
|
|
@@ -705,6 +758,7 @@ modules:
|
|
|
705
758
|
```
|
|
706
759
|
|
|
707
760
|
**index.ts:**
|
|
761
|
+
|
|
708
762
|
```typescript
|
|
709
763
|
import ForgeSQL from "forge-sql-orm";
|
|
710
764
|
|
|
@@ -714,17 +768,18 @@ const forgeSQL = new ForgeSQL();
|
|
|
714
768
|
await forgeSQL.insert(Users, [userData]);
|
|
715
769
|
// Use versioned operations without caching
|
|
716
770
|
await forgeSQL.modifyWithVersioning().insert(Users, [userData]);
|
|
717
|
-
const users = await forgeSQL.select({id: Users.id});
|
|
718
|
-
|
|
719
|
-
|
|
771
|
+
const users = await forgeSQL.select({ id: Users.id });
|
|
720
772
|
```
|
|
721
773
|
|
|
722
774
|
**With caching support:**
|
|
775
|
+
|
|
723
776
|
```shell
|
|
724
777
|
npm install forge-sql-orm @forge/sql @forge/kvs drizzle-orm -S
|
|
778
|
+
# For UI-Kit projects, use: npm install forge-sql-orm @forge/sql @forge/kvs drizzle-orm -S --legacy-peer-deps
|
|
725
779
|
```
|
|
726
780
|
|
|
727
781
|
**manifest.yml:**
|
|
782
|
+
|
|
728
783
|
```yaml
|
|
729
784
|
modules:
|
|
730
785
|
scheduledTrigger:
|
|
@@ -758,21 +813,23 @@ modules:
|
|
|
758
813
|
import ForgeSQL from "forge-sql-orm";
|
|
759
814
|
|
|
760
815
|
const forgeSQL = new ForgeSQL({
|
|
761
|
-
|
|
816
|
+
cacheEntityName: "cache",
|
|
762
817
|
});
|
|
763
818
|
|
|
764
|
-
import {clearCacheSchedulerTrigger} from "forge-sql-orm";
|
|
765
|
-
import {getTableColumns} from "drizzle-orm";
|
|
819
|
+
import { clearCacheSchedulerTrigger } from "forge-sql-orm";
|
|
820
|
+
import { getTableColumns } from "drizzle-orm";
|
|
766
821
|
|
|
767
822
|
export const clearCache = () => {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
823
|
+
return clearCacheSchedulerTrigger({
|
|
824
|
+
cacheEntityName: "cache",
|
|
825
|
+
});
|
|
771
826
|
};
|
|
772
827
|
|
|
773
|
-
|
|
774
828
|
// Now you can use caching features
|
|
775
|
-
const usersData = await forgeSQL
|
|
829
|
+
const usersData = await forgeSQL
|
|
830
|
+
.selectCacheable(getTableColumns(users))
|
|
831
|
+
.from(users)
|
|
832
|
+
.where(eq(users.active, true));
|
|
776
833
|
|
|
777
834
|
// simple insert
|
|
778
835
|
await forgeSQL.insertAndEvictCache(users, [userData]);
|
|
@@ -781,159 +838,194 @@ await forgeSQL.modifyWithVersioningAndEvictCache().insert(users, [userData]);
|
|
|
781
838
|
|
|
782
839
|
// use Cache Context
|
|
783
840
|
const data = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
841
|
+
// after insert mark users to evict
|
|
842
|
+
await forgeSQL.insert(users, [userData]);
|
|
843
|
+
// after insertAndEvictCache mark orders to evict
|
|
844
|
+
await forgeSQL.insertAndEvictCache(orders, [order1, order2]);
|
|
845
|
+
// execute query and put result to local cache
|
|
846
|
+
await forgeSQL
|
|
847
|
+
.selectCacheable({
|
|
848
|
+
userId: users.id,
|
|
849
|
+
userName: users.name,
|
|
850
|
+
orderId: orders.id,
|
|
851
|
+
orderName: orders.name,
|
|
852
|
+
})
|
|
853
|
+
.from(users)
|
|
854
|
+
.innerJoin(orders, eq(orders.userId, users.id))
|
|
855
|
+
.where(eq(users.active, true));
|
|
856
|
+
// use local cache without @forge/kvs and @forge/sql
|
|
857
|
+
return await forgeSQL
|
|
858
|
+
.selectCacheable({
|
|
859
|
+
userId: users.id,
|
|
860
|
+
userName: users.name,
|
|
861
|
+
orderId: orders.id,
|
|
862
|
+
orderName: orders.name,
|
|
863
|
+
})
|
|
864
|
+
.from(users)
|
|
865
|
+
.innerJoin(orders, eq(orders.userId, users.id))
|
|
866
|
+
.where(eq(users.active, true));
|
|
867
|
+
});
|
|
797
868
|
// execute query and put result to kvs cache
|
|
798
|
-
await forgeSQL
|
|
799
|
-
|
|
800
|
-
|
|
869
|
+
await forgeSQL
|
|
870
|
+
.selectCacheable({
|
|
871
|
+
userId: users.id,
|
|
872
|
+
userName: users.name,
|
|
873
|
+
orderId: orders.id,
|
|
874
|
+
orderName: orders.name,
|
|
875
|
+
})
|
|
876
|
+
.from(users)
|
|
877
|
+
.innerJoin(orders, eq(orders.userId, users.id))
|
|
878
|
+
.where(eq(users.active, true));
|
|
801
879
|
|
|
802
880
|
// get result from @foge/kvs cache without real @forge/sql call
|
|
803
|
-
await forgeSQL
|
|
804
|
-
|
|
805
|
-
|
|
881
|
+
await forgeSQL
|
|
882
|
+
.selectCacheable({
|
|
883
|
+
userId: users.id,
|
|
884
|
+
userName: users.name,
|
|
885
|
+
orderId: orders.id,
|
|
886
|
+
orderName: orders.name,
|
|
887
|
+
})
|
|
888
|
+
.from(users)
|
|
889
|
+
.innerJoin(orders, eq(orders.userId, users.id))
|
|
890
|
+
.where(eq(users.active, true));
|
|
806
891
|
|
|
807
892
|
// use Local Cache for performance optimization
|
|
808
893
|
const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(async () => {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return { users, cachedUsers, updatedUsers, usersFrom, cachedUsersFrom, rawUsers };
|
|
839
|
-
});
|
|
894
|
+
// First query - hits database and caches result
|
|
895
|
+
const users = await forgeSQL
|
|
896
|
+
.select({ id: users.id, name: users.name })
|
|
897
|
+
.from(users)
|
|
898
|
+
.where(eq(users.active, true));
|
|
899
|
+
|
|
900
|
+
// Second query - uses local cache (no database call)
|
|
901
|
+
const cachedUsers = await forgeSQL
|
|
902
|
+
.select({ id: users.id, name: users.name })
|
|
903
|
+
.from(users)
|
|
904
|
+
.where(eq(users.active, true));
|
|
905
|
+
|
|
906
|
+
// Using new methods for better performance
|
|
907
|
+
const usersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
908
|
+
|
|
909
|
+
// This will use local cache (no database call)
|
|
910
|
+
const cachedUsersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
911
|
+
|
|
912
|
+
// Raw SQL with local caching
|
|
913
|
+
const rawUsers = await forgeSQL.execute("SELECT id, name FROM users WHERE active = ?", [true]);
|
|
914
|
+
|
|
915
|
+
// Insert operation - evicts local cache
|
|
916
|
+
await forgeSQL.insert(users).values({ name: "New User", active: true });
|
|
917
|
+
|
|
918
|
+
// Third query - hits database again and caches new result
|
|
919
|
+
const updatedUsers = await forgeSQL
|
|
920
|
+
.select({ id: users.id, name: users.name })
|
|
921
|
+
.from(users)
|
|
922
|
+
.where(eq(users.active, true));
|
|
840
923
|
|
|
924
|
+
return { users, cachedUsers, updatedUsers, usersFrom, cachedUsersFrom, rawUsers };
|
|
925
|
+
});
|
|
841
926
|
```
|
|
842
927
|
|
|
843
928
|
## Choosing the Right Method - ForgeSQL ORM
|
|
844
929
|
|
|
845
930
|
### When to Use Each Approach
|
|
846
931
|
|
|
847
|
-
| Method
|
|
848
|
-
|
|
849
|
-
| `modifyWithVersioningAndEvictCache()` | High-concurrency scenarios with Cache support| ✅ Yes
|
|
850
|
-
| `modifyWithVersioning()`
|
|
851
|
-
| `insertAndEvictCache()`
|
|
852
|
-
| `updateAndEvictCache()`
|
|
853
|
-
| `deleteAndEvictCache()`
|
|
854
|
-
| `insert/update/delete`
|
|
855
|
-
| `selectFrom()`
|
|
856
|
-
| `selectDistinctFrom()`
|
|
857
|
-
| `selectCacheableFrom()`
|
|
858
|
-
| `selectDistinctCacheableFrom()`
|
|
859
|
-
| `execute()`
|
|
860
|
-
| `executeCacheable()`
|
|
861
|
-
| `executeDDL()`
|
|
862
|
-
| `executeDDLActions()`
|
|
863
|
-
| `with()`
|
|
864
|
-
|
|
932
|
+
| Method | Use Case | Versioning | Cache Management |
|
|
933
|
+
| ------------------------------------- | ----------------------------------------------------------- | ---------- | -------------------- |
|
|
934
|
+
| `modifyWithVersioningAndEvictCache()` | High-concurrency scenarios with Cache support | ✅ Yes | ✅ Yes |
|
|
935
|
+
| `modifyWithVersioning()` | High-concurrency scenarios | ✅ Yes | Cache Context |
|
|
936
|
+
| `insertAndEvictCache()` | Simple inserts | ❌ No | ✅ Yes |
|
|
937
|
+
| `updateAndEvictCache()` | Simple updates | ❌ No | ✅ Yes |
|
|
938
|
+
| `deleteAndEvictCache()` | Simple deletes | ❌ No | ✅ Yes |
|
|
939
|
+
| `insert/update/delete` | Basic Drizzle operations | ❌ No | Cache Context |
|
|
940
|
+
| `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
|
|
941
|
+
| `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
|
|
942
|
+
| `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
943
|
+
| `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
944
|
+
| `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
|
|
945
|
+
| `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
|
|
946
|
+
| `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.) | ❌ No | No Caching |
|
|
947
|
+
| `executeDDLActions()` | Execute regular SQL queries in DDL operation context | ❌ No | No Caching |
|
|
948
|
+
| `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
|
|
865
949
|
|
|
866
950
|
## Choosing the Right Method - Direct Drizzle
|
|
867
951
|
|
|
868
952
|
### When to Use Each Approach
|
|
869
953
|
|
|
870
|
-
| Method
|
|
871
|
-
|
|
872
|
-
| `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations
|
|
873
|
-
| `insertAndEvictCache()`
|
|
874
|
-
| `updateAndEvictCache()`
|
|
875
|
-
| `deleteAndEvictCache()`
|
|
876
|
-
| `insert/update/delete`
|
|
877
|
-
| `selectFrom()`
|
|
878
|
-
| `selectDistinctFrom()`
|
|
879
|
-
| `selectCacheableFrom()`
|
|
880
|
-
| `selectDistinctCacheableFrom()`
|
|
881
|
-
| `execute()`
|
|
882
|
-
| `executeCacheable()`
|
|
883
|
-
| `executeWithMetadata()`
|
|
884
|
-
| `executeDDL()`
|
|
885
|
-
| `executeDDLActions()`
|
|
886
|
-
| `with()`
|
|
887
|
-
where Cache context - allows you to batch cache invalidation events and bypass cache reads for affected tables.
|
|
954
|
+
| Method | Use Case | Versioning | Cache Management |
|
|
955
|
+
| ---------------------------------------------------------------------- | ----------------------------------------------------------- | ---------- | -------------------- |
|
|
956
|
+
| `insertWithCacheContext/insertWithCacheContext/updateWithCacheContext` | Basic Drizzle operations | ❌ No | Cache Context |
|
|
957
|
+
| `insertAndEvictCache()` | Simple inserts without conflicts | ❌ No | ✅ Yes |
|
|
958
|
+
| `updateAndEvictCache()` | Simple updates without conflicts | ❌ No | ✅ Yes |
|
|
959
|
+
| `deleteAndEvictCache()` | Simple deletes without conflicts | ❌ No | ✅ Yes |
|
|
960
|
+
| `insert/update/delete` | Basic Drizzle operations | ❌ No | ❌ No |
|
|
961
|
+
| `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
|
|
962
|
+
| `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
|
|
963
|
+
| `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
964
|
+
| `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
|
|
965
|
+
| `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
|
|
966
|
+
| `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
|
|
967
|
+
| `executeWithMetadata()` | Raw SQL queries with execution metrics capture | ❌ No | Local Cache |
|
|
968
|
+
| `executeDDL()` | DDL operations (CREATE, ALTER, DROP, etc.) | ❌ No | No Caching |
|
|
969
|
+
| `executeDDLActions()` | Execute regular SQL queries in DDL operation context | ❌ No | No Caching |
|
|
970
|
+
| `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
|
|
888
971
|
|
|
972
|
+
where Cache context - allows you to batch cache invalidation events and bypass cache reads for affected tables.
|
|
889
973
|
|
|
890
974
|
## Step-by-Step Migration Workflow
|
|
891
975
|
|
|
892
|
-
1. **
|
|
976
|
+
1. **Install CLI and setup scripts**
|
|
893
977
|
|
|
894
|
-
```
|
|
895
|
-
|
|
978
|
+
```bash
|
|
979
|
+
npm install forge-sql-orm-cli -D
|
|
980
|
+
npm pkg set scripts.models:create="forge-sql-orm-cli generate:model --output src/entities --saveEnv"
|
|
981
|
+
npm pkg set scripts.migration:create="forge-sql-orm-cli migrations:create --force --output src/migration --entitiesPath src/entities"
|
|
982
|
+
npm pkg set scripts.migration:update="forge-sql-orm-cli migrations:update --entitiesPath src/entities --output src/migration"
|
|
896
983
|
```
|
|
897
984
|
|
|
898
985
|
_(This is done only once when setting up the project)_
|
|
899
986
|
|
|
900
|
-
2. **
|
|
987
|
+
2. **Generate initial schema from an existing database**
|
|
901
988
|
|
|
902
989
|
```sh
|
|
903
|
-
|
|
990
|
+
npm run models:create
|
|
904
991
|
```
|
|
905
992
|
|
|
906
|
-
_(This
|
|
993
|
+
_(This will prompt for database credentials on first run and save them to `.env` file)_
|
|
994
|
+
|
|
995
|
+
3. **Create the first migration**
|
|
996
|
+
|
|
997
|
+
```sh
|
|
998
|
+
npm run migration:create
|
|
999
|
+
```
|
|
907
1000
|
|
|
908
|
-
|
|
1001
|
+
_(This initializes the database migration structure, also done once)_
|
|
909
1002
|
|
|
1003
|
+
4. **Deploy to Forge and verify that migrations work**
|
|
910
1004
|
- Deploy your **Forge app** with migrations.
|
|
911
1005
|
- Run migrations using a **Forge web trigger** or **Forge scheduler**.
|
|
912
1006
|
|
|
913
|
-
|
|
914
|
-
|
|
1007
|
+
5. **Modify the database (e.g., add a new column, index, etc.)**
|
|
915
1008
|
- Use **DbSchema** or manually alter the database schema.
|
|
916
1009
|
|
|
917
|
-
|
|
1010
|
+
6. **Update the migration**
|
|
918
1011
|
|
|
919
1012
|
```sh
|
|
920
|
-
|
|
1013
|
+
npm run migration:update
|
|
921
1014
|
```
|
|
922
1015
|
|
|
923
1016
|
- ⚠️ **Do NOT update schema before this step!**
|
|
924
1017
|
- If schema is updated first, the migration will be empty!
|
|
925
1018
|
|
|
926
|
-
|
|
927
|
-
|
|
1019
|
+
7. **Deploy to Forge and verify that the migration runs without issues**
|
|
928
1020
|
- Run the updated migration on Forge.
|
|
929
1021
|
|
|
930
|
-
|
|
1022
|
+
8. **Update the schema**
|
|
931
1023
|
|
|
932
1024
|
```sh
|
|
933
|
-
|
|
1025
|
+
npm run models:create
|
|
934
1026
|
```
|
|
935
1027
|
|
|
936
|
-
|
|
1028
|
+
9. **Repeat steps 5-8 as needed**
|
|
937
1029
|
|
|
938
1030
|
**⚠️ WARNING:**
|
|
939
1031
|
|
|
@@ -943,6 +1035,7 @@ where Cache context - allows you to batch cache invalidation events and bypass c
|
|
|
943
1035
|
## Drop Migrations
|
|
944
1036
|
|
|
945
1037
|
The Drop Migrations feature allows you to completely reset your database schema in Atlassian Forge SQL. This is useful when you need to:
|
|
1038
|
+
|
|
946
1039
|
- Start fresh with a new schema
|
|
947
1040
|
- Reset all tables and their data
|
|
948
1041
|
- Clear migration history
|
|
@@ -951,6 +1044,7 @@ The Drop Migrations feature allows you to completely reset your database schema
|
|
|
951
1044
|
### Important Requirements
|
|
952
1045
|
|
|
953
1046
|
Before using Drop Migrations, ensure that:
|
|
1047
|
+
|
|
954
1048
|
1. Your local schema exactly matches the current database schema deployed in Atlassian Forge SQL
|
|
955
1049
|
2. You have a backup of your data if needed
|
|
956
1050
|
3. You understand that this operation will delete all tables and data
|
|
@@ -958,16 +1052,21 @@ Before using Drop Migrations, ensure that:
|
|
|
958
1052
|
### Usage
|
|
959
1053
|
|
|
960
1054
|
1. First, ensure your local schema matches the deployed database:
|
|
1055
|
+
|
|
961
1056
|
```bash
|
|
962
|
-
|
|
1057
|
+
npm run models:create
|
|
963
1058
|
```
|
|
964
1059
|
|
|
965
1060
|
2. Generate the drop migration:
|
|
1061
|
+
|
|
966
1062
|
```bash
|
|
967
|
-
|
|
1063
|
+
npm run migration:drop
|
|
968
1064
|
```
|
|
969
1065
|
|
|
1066
|
+
_(Add this script to your package.json: `npm pkg set scripts.migration:drop="forge-sql-orm-cli migrations:drop --entitiesPath src/entities --output src/migration"`)_
|
|
1067
|
+
|
|
970
1068
|
3. Deploy and run the migration in your Forge app:
|
|
1069
|
+
|
|
971
1070
|
```js
|
|
972
1071
|
import migrationRunner from "./database/migration";
|
|
973
1072
|
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
@@ -979,13 +1078,14 @@ Before using Drop Migrations, ensure that:
|
|
|
979
1078
|
|
|
980
1079
|
4. After dropping all tables, you can create a new migration to recreate the schema:
|
|
981
1080
|
```bash
|
|
982
|
-
|
|
1081
|
+
npm run migration:create
|
|
983
1082
|
```
|
|
984
|
-
The `--force` parameter is
|
|
1083
|
+
The `--force` parameter is already included in the script to allow creating migrations after dropping all tables.
|
|
985
1084
|
|
|
986
1085
|
### Example Migration Output
|
|
987
1086
|
|
|
988
1087
|
The generated drop migration will look like this:
|
|
1088
|
+
|
|
989
1089
|
```js
|
|
990
1090
|
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
991
1091
|
|
|
@@ -1013,31 +1113,36 @@ export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
|
1013
1113
|
|
|
1014
1114
|
When working with date and time fields in your models, you should use the custom types provided by Forge-SQL-ORM to ensure proper handling of date/time values. This is necessary because Forge SQL has specific format requirements for date/time values:
|
|
1015
1115
|
|
|
1016
|
-
| Date type | Required Format
|
|
1017
|
-
|
|
1018
|
-
| DATE
|
|
1019
|
-
| TIME
|
|
1116
|
+
| Date type | Required Format | Example |
|
|
1117
|
+
| --------- | ------------------------------ | -------------------------- |
|
|
1118
|
+
| DATE | YYYY-MM-DD | 2024-09-19 |
|
|
1119
|
+
| TIME | HH:MM:SS[.fraction] | 06:40:34 |
|
|
1020
1120
|
| TIMESTAMP | YYYY-MM-DD HH:MM:SS[.fraction] | 2024-09-19 06:40:34.999999 |
|
|
1021
1121
|
|
|
1022
1122
|
```typescript
|
|
1023
1123
|
// ❌ Don't use standard Drizzle date/time types
|
|
1024
|
-
export const testEntityTimeStampVersion = mysqlTable(
|
|
1025
|
-
id: int(
|
|
1026
|
-
time_stamp: timestamp(
|
|
1027
|
-
date_time: datetime(
|
|
1028
|
-
time: time(
|
|
1029
|
-
date: date(
|
|
1124
|
+
export const testEntityTimeStampVersion = mysqlTable("test_entity", {
|
|
1125
|
+
id: int("id").primaryKey().autoincrement(),
|
|
1126
|
+
time_stamp: timestamp("times_tamp").notNull(),
|
|
1127
|
+
date_time: datetime("date_time").notNull(),
|
|
1128
|
+
time: time("time").notNull(),
|
|
1129
|
+
date: date("date").notNull(),
|
|
1030
1130
|
});
|
|
1031
1131
|
|
|
1032
1132
|
// ✅ Use Forge-SQL-ORM custom types instead
|
|
1033
|
-
import {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1133
|
+
import {
|
|
1134
|
+
forgeDateTimeString,
|
|
1135
|
+
forgeDateString,
|
|
1136
|
+
forgeTimestampString,
|
|
1137
|
+
forgeTimeString,
|
|
1138
|
+
} from "forge-sql-orm";
|
|
1139
|
+
|
|
1140
|
+
export const testEntityTimeStampVersion = mysqlTable("test_entity", {
|
|
1141
|
+
id: int("id").primaryKey().autoincrement(),
|
|
1142
|
+
time_stamp: forgeTimestampString("times_tamp").notNull(),
|
|
1143
|
+
date_time: forgeDateTimeString("date_time").notNull(),
|
|
1144
|
+
time: forgeTimeString("time").notNull(),
|
|
1145
|
+
date: forgeDateString("date").notNull(),
|
|
1041
1146
|
});
|
|
1042
1147
|
```
|
|
1043
1148
|
|
|
@@ -1053,6 +1158,7 @@ const timestamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS");
|
|
|
1053
1158
|
```
|
|
1054
1159
|
|
|
1055
1160
|
Our custom types provide:
|
|
1161
|
+
|
|
1056
1162
|
- Automatic conversion between JavaScript Date objects and Forge SQL's required string formats
|
|
1057
1163
|
- Consistent date/time handling across your application
|
|
1058
1164
|
- Type safety for date/time fields
|
|
@@ -1068,9 +1174,6 @@ Our custom types provide:
|
|
|
1068
1174
|
|
|
1069
1175
|
Each type ensures that the data is properly formatted according to Forge SQL's requirements while providing a clean, type-safe interface for your application code.
|
|
1070
1176
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
1177
|
# Connection to ORM
|
|
1075
1178
|
|
|
1076
1179
|
```js
|
|
@@ -1078,7 +1181,8 @@ import ForgeSQL from "forge-sql-orm";
|
|
|
1078
1181
|
|
|
1079
1182
|
const forgeSQL = new ForgeSQL();
|
|
1080
1183
|
```
|
|
1081
|
-
|
|
1184
|
+
|
|
1185
|
+
or
|
|
1082
1186
|
|
|
1083
1187
|
```typescript
|
|
1084
1188
|
import { drizzle } from "drizzle-orm/mysql-proxy";
|
|
@@ -1097,69 +1201,47 @@ const users = await db.select().from(users);
|
|
|
1097
1201
|
|
|
1098
1202
|
```js
|
|
1099
1203
|
// Using forgeSQL.select()
|
|
1100
|
-
const user = await forgeSQL
|
|
1101
|
-
.select({user: users})
|
|
1102
|
-
.from(users);
|
|
1204
|
+
const user = await forgeSQL.select({ user: users }).from(users);
|
|
1103
1205
|
|
|
1104
1206
|
// Using forgeSQL.selectDistinct()
|
|
1105
|
-
const user = await forgeSQL
|
|
1106
|
-
.selectDistinct({user: users})
|
|
1107
|
-
.from(users);
|
|
1207
|
+
const user = await forgeSQL.selectDistinct({ user: users }).from(users);
|
|
1108
1208
|
|
|
1109
1209
|
// Using forgeSQL.selectCacheable()
|
|
1110
|
-
const user = await forgeSQL
|
|
1111
|
-
.selectCacheable({user: users})
|
|
1112
|
-
.from(users);
|
|
1210
|
+
const user = await forgeSQL.selectCacheable({ user: users }).from(users);
|
|
1113
1211
|
|
|
1114
1212
|
// Using forgeSQL.selectFrom() - Select all columns with field aliasing
|
|
1115
|
-
const user = await forgeSQL
|
|
1116
|
-
.selectFrom(users)
|
|
1117
|
-
.where(eq(users.id, 1));
|
|
1213
|
+
const user = await forgeSQL.selectFrom(users).where(eq(users.id, 1));
|
|
1118
1214
|
|
|
1119
1215
|
// Using forgeSQL.selectDistinctFrom() - Select distinct all columns with field aliasing
|
|
1120
|
-
const user = await forgeSQL
|
|
1121
|
-
.selectDistinctFrom(users)
|
|
1122
|
-
.where(eq(users.id, 1));
|
|
1216
|
+
const user = await forgeSQL.selectDistinctFrom(users).where(eq(users.id, 1));
|
|
1123
1217
|
|
|
1124
1218
|
// Using forgeSQL.selectCacheableFrom() - Select all columns with field aliasing and caching
|
|
1125
|
-
const user = await forgeSQL
|
|
1126
|
-
.selectCacheableFrom(users)
|
|
1127
|
-
.where(eq(users.id, 1));
|
|
1219
|
+
const user = await forgeSQL.selectCacheableFrom(users).where(eq(users.id, 1));
|
|
1128
1220
|
|
|
1129
1221
|
// Using forgeSQL.selectDistinctCacheableFrom() - Select distinct all columns with field aliasing and caching
|
|
1130
|
-
const user = await forgeSQL
|
|
1131
|
-
.selectDistinctCacheableFrom(users)
|
|
1132
|
-
.where(eq(users.id, 1));
|
|
1222
|
+
const user = await forgeSQL.selectDistinctCacheableFrom(users).where(eq(users.id, 1));
|
|
1133
1223
|
|
|
1134
1224
|
// Using forgeSQL.execute() - Execute raw SQL with local caching
|
|
1135
|
-
const user = await forgeSQL
|
|
1136
|
-
.execute("SELECT * FROM users WHERE id = ?", [1]);
|
|
1225
|
+
const user = await forgeSQL.execute("SELECT * FROM users WHERE id = ?", [1]);
|
|
1137
1226
|
|
|
1138
1227
|
// Using forgeSQL.executeCacheable() - Execute raw SQL with local and global caching
|
|
1139
|
-
|
|
1140
|
-
|
|
1228
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names in SQL queries must be wrapped with backticks (`)
|
|
1229
|
+
// Example: SELECT * FROM `users` WHERE id = ? (NOT: SELECT * FROM users WHERE id = ?)
|
|
1230
|
+
const user = await forgeSQL.executeCacheable("SELECT * FROM `users` WHERE id = ?", [1], 300);
|
|
1141
1231
|
|
|
1142
1232
|
// Using forgeSQL.getDrizzleQueryBuilder()
|
|
1143
|
-
const user = await forgeSQL
|
|
1144
|
-
.getDrizzleQueryBuilder()
|
|
1145
|
-
.select().from(Users)
|
|
1146
|
-
.where(eq(Users.id, 1));
|
|
1233
|
+
const user = await forgeSQL.getDrizzleQueryBuilder().select().from(Users).where(eq(Users.id, 1));
|
|
1147
1234
|
|
|
1148
1235
|
// OR using direct drizzle with custom driver
|
|
1149
1236
|
const db = drizzle(forgeDriver);
|
|
1150
|
-
const user = await db
|
|
1151
|
-
.select().from(Users)
|
|
1152
|
-
.where(eq(Users.id, 1));
|
|
1237
|
+
const user = await db.select().from(Users).where(eq(Users.id, 1));
|
|
1153
1238
|
// Returns: { id: 1, name: "John Doe" }
|
|
1154
1239
|
|
|
1155
1240
|
// Using executeQueryOnlyOne for single result with error handling
|
|
1156
1241
|
const user = await forgeSQL
|
|
1157
1242
|
.fetch()
|
|
1158
1243
|
.executeQueryOnlyOne(
|
|
1159
|
-
forgeSQL
|
|
1160
|
-
.getDrizzleQueryBuilder()
|
|
1161
|
-
.select().from(Users)
|
|
1162
|
-
.where(eq(Users.id, 1))
|
|
1244
|
+
forgeSQL.getDrizzleQueryBuilder().select().from(Users).where(eq(Users.id, 1)),
|
|
1163
1245
|
);
|
|
1164
1246
|
// Returns: { id: 1, name: "John Doe" }
|
|
1165
1247
|
// Throws error if multiple records found
|
|
@@ -1171,26 +1253,29 @@ const usersAlias = alias(Users, "u");
|
|
|
1171
1253
|
const result = await forgeSQL
|
|
1172
1254
|
.getDrizzleQueryBuilder()
|
|
1173
1255
|
.select({
|
|
1174
|
-
userId: sql<string
|
|
1175
|
-
userName: sql<string
|
|
1176
|
-
})
|
|
1256
|
+
userId: sql < string > `${usersAlias.id} as \`userId\``,
|
|
1257
|
+
userName: sql < string > `${usersAlias.name} as \`userName\``,
|
|
1258
|
+
})
|
|
1259
|
+
.from(usersAlias);
|
|
1177
1260
|
|
|
1178
1261
|
// OR with direct drizzle
|
|
1179
1262
|
const db = drizzle(forgeDriver);
|
|
1180
1263
|
const result = await db
|
|
1181
1264
|
.select({
|
|
1182
|
-
userId: sql<string
|
|
1183
|
-
userName: sql<string
|
|
1184
|
-
})
|
|
1265
|
+
userId: sql < string > `${usersAlias.id} as \`userId\``,
|
|
1266
|
+
userName: sql < string > `${usersAlias.name} as \`userName\``,
|
|
1267
|
+
})
|
|
1268
|
+
.from(usersAlias);
|
|
1185
1269
|
// Returns: { userId: 1, userName: "John Doe" }
|
|
1186
1270
|
```
|
|
1187
1271
|
|
|
1188
1272
|
### Complex Queries
|
|
1273
|
+
|
|
1189
1274
|
```js
|
|
1190
1275
|
// Using joins with automatic field name collision prevention
|
|
1191
1276
|
// With forgeSQL
|
|
1192
1277
|
const orderWithUser = await forgeSQL
|
|
1193
|
-
.select({user: users, order: orders})
|
|
1278
|
+
.select({ user: users, order: orders })
|
|
1194
1279
|
.from(orders)
|
|
1195
1280
|
.innerJoin(users, eq(orders.userId, users.id));
|
|
1196
1281
|
|
|
@@ -1209,12 +1294,12 @@ const orderWithUser = await forgeSQL
|
|
|
1209
1294
|
// Using with() for Common Table Expressions (CTEs)
|
|
1210
1295
|
const userStats = await forgeSQL
|
|
1211
1296
|
.with(
|
|
1212
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true)).as(
|
|
1213
|
-
forgeSQL.selectFrom(orders).where(eq(orders.status,
|
|
1297
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)).as("activeUsers"),
|
|
1298
|
+
forgeSQL.selectFrom(orders).where(eq(orders.status, "completed")).as("completedOrders"),
|
|
1214
1299
|
)
|
|
1215
1300
|
.select({
|
|
1216
1301
|
totalActiveUsers: sql`COUNT(au.id)`,
|
|
1217
|
-
totalCompletedOrders: sql`COUNT(co.id)
|
|
1302
|
+
totalCompletedOrders: sql`COUNT(co.id)`,
|
|
1218
1303
|
})
|
|
1219
1304
|
.from(sql`activeUsers au`)
|
|
1220
1305
|
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
|
|
@@ -1222,33 +1307,30 @@ const userStats = await forgeSQL
|
|
|
1222
1307
|
// OR with direct drizzle
|
|
1223
1308
|
const db = patchDbWithSelectAliased(drizzle(forgeDriver));
|
|
1224
1309
|
const orderWithUser = await db
|
|
1225
|
-
.selectAliased({user: users, order: orders})
|
|
1310
|
+
.selectAliased({ user: users, order: orders })
|
|
1226
1311
|
.from(orders)
|
|
1227
1312
|
.innerJoin(users, eq(orders.userId, users.id));
|
|
1228
|
-
// Returns: {
|
|
1229
|
-
// user_id: 1,
|
|
1313
|
+
// Returns: {
|
|
1314
|
+
// user_id: 1,
|
|
1230
1315
|
// user_name: "John Doe",
|
|
1231
1316
|
// order_id: 1,
|
|
1232
1317
|
// order_product: "Product 1"
|
|
1233
1318
|
// }
|
|
1234
1319
|
|
|
1235
1320
|
// Using distinct with aliases
|
|
1236
|
-
const uniqueUsers = await db
|
|
1237
|
-
.selectAliasedDistinct({user: users})
|
|
1238
|
-
.from(users);
|
|
1321
|
+
const uniqueUsers = await db.selectAliasedDistinct({ user: users }).from(users);
|
|
1239
1322
|
// Returns unique users with aliased fields
|
|
1240
1323
|
|
|
1241
1324
|
// Using executeQueryOnlyOne for unique results
|
|
1242
|
-
const userStats = await forgeSQL
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
);
|
|
1325
|
+
const userStats = await forgeSQL.fetch().executeQueryOnlyOne(
|
|
1326
|
+
forgeSQL
|
|
1327
|
+
.getDrizzleQueryBuilder()
|
|
1328
|
+
.select({
|
|
1329
|
+
totalUsers: sql`COUNT(*) as \`totalUsers\``,
|
|
1330
|
+
uniqueNames: sql`COUNT(DISTINCT name) as \`uniqueNames\``,
|
|
1331
|
+
})
|
|
1332
|
+
.from(Users),
|
|
1333
|
+
);
|
|
1252
1334
|
// Returns: { totalUsers: 100, uniqueNames: 80 }
|
|
1253
1335
|
// Throws error if multiple records found
|
|
1254
1336
|
```
|
|
@@ -1266,8 +1348,10 @@ const users = await forgeSQL
|
|
|
1266
1348
|
.execute("SELECT * FROM users WHERE active = ?", [true]);
|
|
1267
1349
|
|
|
1268
1350
|
// Using executeCacheable() for raw SQL with local and global caching
|
|
1351
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names in SQL queries must be wrapped with backticks (`)
|
|
1352
|
+
// Example: SELECT * FROM `users` WHERE active = ? (NOT: SELECT * FROM users WHERE active = ?)
|
|
1269
1353
|
const users = await forgeSQL
|
|
1270
|
-
.executeCacheable("SELECT * FROM users WHERE active = ?", [true], 300);
|
|
1354
|
+
.executeCacheable("SELECT * FROM `users` WHERE active = ?", [true], 300);
|
|
1271
1355
|
|
|
1272
1356
|
// Using executeWithMetadata() for capturing execution metrics and performance monitoring
|
|
1273
1357
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
@@ -1278,14 +1362,14 @@ const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
|
1278
1362
|
},
|
|
1279
1363
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1280
1364
|
const threshold = 500; // ms baseline for this resolver
|
|
1281
|
-
|
|
1365
|
+
|
|
1282
1366
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1283
1367
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1284
1368
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1285
1369
|
} else if (totalDbExecutionTime > threshold) {
|
|
1286
1370
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1287
1371
|
}
|
|
1288
|
-
|
|
1372
|
+
|
|
1289
1373
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1290
1374
|
}
|
|
1291
1375
|
);
|
|
@@ -1300,7 +1384,7 @@ await forgeSQL.executeDDL(`
|
|
|
1300
1384
|
`);
|
|
1301
1385
|
|
|
1302
1386
|
await forgeSQL.executeDDL(sql`
|
|
1303
|
-
ALTER TABLE users
|
|
1387
|
+
ALTER TABLE users
|
|
1304
1388
|
ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
1305
1389
|
`);
|
|
1306
1390
|
|
|
@@ -1311,23 +1395,23 @@ await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
|
|
|
1311
1395
|
await forgeSQL.executeDDLActions(async () => {
|
|
1312
1396
|
// Execute regular SQL queries in DDL context for performance monitoring
|
|
1313
1397
|
const slowQueries = await forgeSQL.execute(`
|
|
1314
|
-
SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
|
|
1398
|
+
SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
|
|
1315
1399
|
WHERE AVG_LATENCY > 1000000
|
|
1316
1400
|
`);
|
|
1317
|
-
|
|
1401
|
+
|
|
1318
1402
|
// Execute complex analysis queries in DDL context
|
|
1319
1403
|
const performanceData = await forgeSQL.execute(`
|
|
1320
1404
|
SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
|
|
1321
1405
|
WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
|
|
1322
1406
|
`);
|
|
1323
|
-
|
|
1407
|
+
|
|
1324
1408
|
return { slowQueries, performanceData };
|
|
1325
1409
|
});
|
|
1326
1410
|
|
|
1327
1411
|
// Using execute() with complex queries
|
|
1328
1412
|
const userStats = await forgeSQL
|
|
1329
1413
|
.execute(`
|
|
1330
|
-
SELECT
|
|
1414
|
+
SELECT
|
|
1331
1415
|
u.id,
|
|
1332
1416
|
u.name,
|
|
1333
1417
|
COUNT(o.id) as order_count,
|
|
@@ -1352,13 +1436,10 @@ These operations work like standard Drizzle methods but participate in cache con
|
|
|
1352
1436
|
await forgeSQL.insert(Users).values({ id: 1, name: "Smith" });
|
|
1353
1437
|
|
|
1354
1438
|
// Basic update (participates in cache context when used within executeWithCacheContext)
|
|
1355
|
-
await forgeSQL.update(Users)
|
|
1356
|
-
.set({ name: "Smith Updated" })
|
|
1357
|
-
.where(eq(Users.id, 1));
|
|
1439
|
+
await forgeSQL.update(Users).set({ name: "Smith Updated" }).where(eq(Users.id, 1));
|
|
1358
1440
|
|
|
1359
1441
|
// Basic delete (participates in cache context when used within executeWithCacheContext)
|
|
1360
|
-
await forgeSQL.delete(Users)
|
|
1361
|
-
.where(eq(Users.id, 1));
|
|
1442
|
+
await forgeSQL.delete(Users).where(eq(Users.id, 1));
|
|
1362
1443
|
```
|
|
1363
1444
|
|
|
1364
1445
|
### 2. Non-Versioned Operations with Cache Management
|
|
@@ -1370,13 +1451,10 @@ These operations don't use optimistic locking but provide cache invalidation:
|
|
|
1370
1451
|
await forgeSQL.insertAndEvictCache(Users).values({ id: 1, name: "Smith" });
|
|
1371
1452
|
|
|
1372
1453
|
// Update without versioning but with cache invalidation
|
|
1373
|
-
await forgeSQL.updateAndEvictCache(Users)
|
|
1374
|
-
.set({ name: "Smith Updated" })
|
|
1375
|
-
.where(eq(Users.id, 1));
|
|
1454
|
+
await forgeSQL.updateAndEvictCache(Users).set({ name: "Smith Updated" }).where(eq(Users.id, 1));
|
|
1376
1455
|
|
|
1377
1456
|
// Delete without versioning but with cache invalidation
|
|
1378
|
-
await forgeSQL.deleteAndEvictCache(Users)
|
|
1379
|
-
.where(eq(Users.id, 1));
|
|
1457
|
+
await forgeSQL.deleteAndEvictCache(Users).where(eq(Users.id, 1));
|
|
1380
1458
|
```
|
|
1381
1459
|
|
|
1382
1460
|
### 3. Versioned Operations with Cache Management (Recommended)
|
|
@@ -1385,16 +1463,20 @@ These operations use optimistic locking and automatic cache invalidation:
|
|
|
1385
1463
|
|
|
1386
1464
|
```js
|
|
1387
1465
|
// Insert with versioning and cache management
|
|
1388
|
-
const userId = await forgeSQL
|
|
1466
|
+
const userId = await forgeSQL
|
|
1467
|
+
.modifyWithVersioningAndEvictCache()
|
|
1468
|
+
.insert(Users, [{ id: 1, name: "Smith" }]);
|
|
1389
1469
|
|
|
1390
1470
|
// Bulk insert with versioning
|
|
1391
1471
|
await forgeSQL.modifyWithVersioningAndEvictCache().insert(Users, [
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1472
|
+
{ id: 2, name: "Smith" },
|
|
1473
|
+
{ id: 3, name: "Vasyl" },
|
|
1474
|
+
]);
|
|
1395
1475
|
|
|
1396
1476
|
// Update by ID with optimistic locking and cache invalidation
|
|
1397
|
-
await forgeSQL
|
|
1477
|
+
await forgeSQL
|
|
1478
|
+
.modifyWithVersioningAndEvictCache()
|
|
1479
|
+
.updateById({ id: 1, name: "Smith Updated" }, Users);
|
|
1398
1480
|
|
|
1399
1481
|
// Delete by ID with versioning and cache invalidation
|
|
1400
1482
|
await forgeSQL.modifyWithVersioningAndEvictCache().deleteById(1, Users);
|
|
@@ -1438,18 +1520,16 @@ await forgeSQL.modifyWithVersioning().deleteById(1, Users);
|
|
|
1438
1520
|
import { nextVal } from "forge-sql-orm";
|
|
1439
1521
|
|
|
1440
1522
|
const user = {
|
|
1441
|
-
id: nextVal(
|
|
1523
|
+
id: nextVal("user_id_seq"),
|
|
1442
1524
|
name: "user test",
|
|
1443
|
-
organization_id: 1
|
|
1525
|
+
organization_id: 1,
|
|
1444
1526
|
};
|
|
1445
1527
|
const id = await forgeSQL.modifyWithVersioning().insert(appUser, [user]);
|
|
1446
1528
|
|
|
1447
1529
|
// Update with custom WHERE condition
|
|
1448
|
-
await forgeSQL
|
|
1449
|
-
|
|
1450
|
-
Users,
|
|
1451
|
-
eq(Users.email, "smith@example.com")
|
|
1452
|
-
);
|
|
1530
|
+
await forgeSQL
|
|
1531
|
+
.modifyWithVersioning()
|
|
1532
|
+
.updateFields({ name: "New Name", age: 35 }, Users, eq(Users.email, "smith@example.com"));
|
|
1453
1533
|
|
|
1454
1534
|
// Insert with duplicate handling
|
|
1455
1535
|
await forgeSQL.modifyWithVersioning().insert(
|
|
@@ -1458,7 +1538,7 @@ await forgeSQL.modifyWithVersioning().insert(
|
|
|
1458
1538
|
{ id: 4, name: "Smith" },
|
|
1459
1539
|
{ id: 4, name: "Vasyl" },
|
|
1460
1540
|
],
|
|
1461
|
-
true
|
|
1541
|
+
true,
|
|
1462
1542
|
);
|
|
1463
1543
|
```
|
|
1464
1544
|
|
|
@@ -1480,19 +1560,21 @@ const result = await forgeSQL
|
|
|
1480
1560
|
.offset(formatLimitOffset(350000));
|
|
1481
1561
|
|
|
1482
1562
|
// The generated SQL will be:
|
|
1483
|
-
// SELECT * FROM order_item
|
|
1484
|
-
// ORDER BY created_at ASC
|
|
1485
|
-
// LIMIT 10
|
|
1563
|
+
// SELECT * FROM order_item
|
|
1564
|
+
// ORDER BY created_at ASC
|
|
1565
|
+
// LIMIT 10
|
|
1486
1566
|
// OFFSET 350000
|
|
1487
1567
|
```
|
|
1488
1568
|
|
|
1489
1569
|
**Important Notes:**
|
|
1570
|
+
|
|
1490
1571
|
- The function performs type checking to prevent SQL injection
|
|
1491
1572
|
- It throws an error if the input is not a valid number
|
|
1492
1573
|
- Use this function instead of direct parameter binding for LIMIT and OFFSET clauses
|
|
1493
1574
|
- The function is specifically designed to work with Atlassian Forge SQL's limitations
|
|
1494
1575
|
|
|
1495
1576
|
**Security Considerations:**
|
|
1577
|
+
|
|
1496
1578
|
- The function includes validation to ensure the input is a valid number
|
|
1497
1579
|
- This prevents SQL injection by ensuring only numeric values are inserted
|
|
1498
1580
|
- Always use this function instead of string concatenation for LIMIT and OFFSET values
|
|
@@ -1526,9 +1608,9 @@ const options = {
|
|
|
1526
1608
|
tableName: "users",
|
|
1527
1609
|
versionField: {
|
|
1528
1610
|
fieldName: "updatedAt",
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1532
1614
|
};
|
|
1533
1615
|
|
|
1534
1616
|
const forgeSQL = new ForgeSQL(options);
|
|
@@ -1551,7 +1633,6 @@ The caching system leverages Forge's Custom entity store to provide:
|
|
|
1551
1633
|
// Value: { data: [...], expiration: 1234567890, sql: "select * from 1" }
|
|
1552
1634
|
```
|
|
1553
1635
|
|
|
1554
|
-
|
|
1555
1636
|
### Cache Context Operations
|
|
1556
1637
|
|
|
1557
1638
|
The cache context allows you to batch cache invalidation events and bypass cache reads for affected tables:
|
|
@@ -1612,72 +1693,78 @@ Local cache is an in-memory caching layer that operates within a single resolver
|
|
|
1612
1693
|
// Execute operations within a local cache context
|
|
1613
1694
|
await forgeSQL.executeWithLocalContext(async () => {
|
|
1614
1695
|
// First call - executes query and caches result
|
|
1615
|
-
const users = await forgeSQL
|
|
1616
|
-
.
|
|
1617
|
-
|
|
1696
|
+
const users = await forgeSQL
|
|
1697
|
+
.select({ id: users.id, name: users.name })
|
|
1698
|
+
.from(users)
|
|
1699
|
+
.where(eq(users.active, true));
|
|
1700
|
+
|
|
1618
1701
|
// Second call - gets result from local cache (no database query)
|
|
1619
|
-
const cachedUsers = await forgeSQL
|
|
1620
|
-
.
|
|
1621
|
-
|
|
1622
|
-
// Using new selectFrom methods with local caching
|
|
1623
|
-
const usersFrom = await forgeSQL.selectFrom(users)
|
|
1702
|
+
const cachedUsers = await forgeSQL
|
|
1703
|
+
.select({ id: users.id, name: users.name })
|
|
1704
|
+
.from(users)
|
|
1624
1705
|
.where(eq(users.active, true));
|
|
1625
|
-
|
|
1706
|
+
|
|
1707
|
+
// Using new selectFrom methods with local caching
|
|
1708
|
+
const usersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
1709
|
+
|
|
1626
1710
|
// This will use local cache (no database call)
|
|
1627
|
-
const cachedUsersFrom = await forgeSQL.selectFrom(users)
|
|
1628
|
-
|
|
1629
|
-
|
|
1711
|
+
const cachedUsersFrom = await forgeSQL.selectFrom(users).where(eq(users.active, true));
|
|
1712
|
+
|
|
1630
1713
|
// Using execute() with local caching
|
|
1631
|
-
const rawUsers = await forgeSQL.execute(
|
|
1632
|
-
|
|
1633
|
-
[true]
|
|
1634
|
-
);
|
|
1635
|
-
|
|
1714
|
+
const rawUsers = await forgeSQL.execute("SELECT id, name FROM users WHERE active = ?", [true]);
|
|
1715
|
+
|
|
1636
1716
|
// This will use local cache (no database call)
|
|
1637
|
-
const cachedRawUsers = await forgeSQL.execute(
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1717
|
+
const cachedRawUsers = await forgeSQL.execute("SELECT id, name FROM users WHERE active = ?", [
|
|
1718
|
+
true,
|
|
1719
|
+
]);
|
|
1720
|
+
|
|
1642
1721
|
// Raw SQL with execution metadata and performance monitoring
|
|
1643
1722
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
1644
1723
|
async () => {
|
|
1645
1724
|
const users = await forgeSQL.selectFrom(usersTable);
|
|
1646
|
-
const orders = await forgeSQL
|
|
1725
|
+
const orders = await forgeSQL
|
|
1726
|
+
.selectFrom(ordersTable)
|
|
1727
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
1647
1728
|
return { users, orders };
|
|
1648
1729
|
},
|
|
1649
1730
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1650
1731
|
const threshold = 500; // ms baseline for this resolver
|
|
1651
|
-
|
|
1732
|
+
|
|
1652
1733
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1653
1734
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1654
1735
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1655
1736
|
} else if (totalDbExecutionTime > threshold) {
|
|
1656
1737
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1657
1738
|
}
|
|
1658
|
-
|
|
1739
|
+
|
|
1659
1740
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1660
|
-
}
|
|
1741
|
+
},
|
|
1661
1742
|
);
|
|
1662
|
-
|
|
1743
|
+
|
|
1663
1744
|
// Insert operation - evicts local cache for users table
|
|
1664
|
-
await forgeSQL.insert(users).values({ name:
|
|
1665
|
-
|
|
1745
|
+
await forgeSQL.insert(users).values({ name: "New User", active: true });
|
|
1746
|
+
|
|
1666
1747
|
// Third call - executes query again and caches new result
|
|
1667
|
-
const updatedUsers = await forgeSQL
|
|
1668
|
-
.
|
|
1748
|
+
const updatedUsers = await forgeSQL
|
|
1749
|
+
.select({ id: users.id, name: users.name })
|
|
1750
|
+
.from(users)
|
|
1751
|
+
.where(eq(users.active, true));
|
|
1669
1752
|
});
|
|
1670
1753
|
|
|
1671
1754
|
// Execute with return value
|
|
1672
1755
|
const result = await forgeSQL.executeWithLocalCacheContextAndReturnValue(async () => {
|
|
1673
1756
|
// First call - executes query and caches result
|
|
1674
|
-
const users = await forgeSQL
|
|
1675
|
-
.
|
|
1676
|
-
|
|
1757
|
+
const users = await forgeSQL
|
|
1758
|
+
.select({ id: users.id, name: users.name })
|
|
1759
|
+
.from(users)
|
|
1760
|
+
.where(eq(users.active, true));
|
|
1761
|
+
|
|
1677
1762
|
// Second call - gets result from local cache (no database query)
|
|
1678
|
-
const cachedUsers = await forgeSQL
|
|
1679
|
-
.
|
|
1680
|
-
|
|
1763
|
+
const cachedUsers = await forgeSQL
|
|
1764
|
+
.select({ id: users.id, name: users.name })
|
|
1765
|
+
.from(users)
|
|
1766
|
+
.where(eq(users.active, true));
|
|
1767
|
+
|
|
1681
1768
|
return { users, cachedUsers };
|
|
1682
1769
|
});
|
|
1683
1770
|
```
|
|
@@ -1689,57 +1776,57 @@ const result = await forgeSQL.executeWithLocalCacheContextAndReturnValue(async (
|
|
|
1689
1776
|
const userResolver = async (req) => {
|
|
1690
1777
|
return await forgeSQL.executeWithLocalCacheContextAndReturnValue(async () => {
|
|
1691
1778
|
// Get user details using selectFrom (all columns with field aliasing)
|
|
1692
|
-
const user = await forgeSQL.selectFrom(users)
|
|
1693
|
-
|
|
1694
|
-
|
|
1779
|
+
const user = await forgeSQL.selectFrom(users).where(eq(users.id, args.userId));
|
|
1780
|
+
|
|
1695
1781
|
// Get user's orders using selectCacheableFrom (with caching)
|
|
1696
|
-
const orders = await forgeSQL.selectCacheableFrom(orders)
|
|
1697
|
-
|
|
1698
|
-
|
|
1782
|
+
const orders = await forgeSQL.selectCacheableFrom(orders).where(eq(orders.userId, args.userId));
|
|
1783
|
+
|
|
1699
1784
|
// Get user's profile using raw SQL with execute()
|
|
1700
1785
|
const profile = await forgeSQL.execute(
|
|
1701
|
-
"SELECT id, bio, avatar FROM profiles WHERE user_id = ?",
|
|
1702
|
-
[args.userId]
|
|
1786
|
+
"SELECT id, bio, avatar FROM profiles WHERE user_id = ?",
|
|
1787
|
+
[args.userId],
|
|
1703
1788
|
);
|
|
1704
|
-
|
|
1789
|
+
|
|
1705
1790
|
// Get user statistics using complex raw SQL
|
|
1706
|
-
const stats = await forgeSQL.execute(
|
|
1791
|
+
const stats = await forgeSQL.execute(
|
|
1792
|
+
`
|
|
1707
1793
|
SELECT
|
|
1708
1794
|
COUNT(o.id) as total_orders,
|
|
1709
1795
|
SUM(o.amount) as total_spent,
|
|
1710
1796
|
AVG(o.amount) as avg_order_value
|
|
1711
1797
|
FROM orders o
|
|
1712
1798
|
WHERE o.user_id = ? AND o.status = 'completed'
|
|
1713
|
-
`,
|
|
1714
|
-
|
|
1799
|
+
`,
|
|
1800
|
+
[args.userId],
|
|
1801
|
+
);
|
|
1802
|
+
|
|
1715
1803
|
// If any of these queries are repeated within the same resolver,
|
|
1716
1804
|
// they will use the local cache instead of hitting the database
|
|
1717
|
-
|
|
1805
|
+
|
|
1718
1806
|
return {
|
|
1719
1807
|
...user[0],
|
|
1720
1808
|
orders,
|
|
1721
1809
|
profile: profile[0],
|
|
1722
|
-
stats: stats[0]
|
|
1810
|
+
stats: stats[0],
|
|
1723
1811
|
};
|
|
1724
1812
|
});
|
|
1725
1813
|
};
|
|
1726
1814
|
```
|
|
1727
1815
|
|
|
1728
|
-
|
|
1729
1816
|
#### Local Cache (Level 1) vs Global Cache (Level 2)
|
|
1730
1817
|
|
|
1731
|
-
| Feature
|
|
1732
|
-
|
|
1733
|
-
| **Storage**
|
|
1734
|
-
| **Scope**
|
|
1735
|
-
| **Persistence**
|
|
1736
|
-
| **Performance**
|
|
1737
|
-
| **Memory Usage**
|
|
1738
|
-
| **Use Case**
|
|
1739
|
-
| **Configuration**
|
|
1740
|
-
| **TTL Support**
|
|
1741
|
-
| **Cache Eviction** | Automatic on DML operations
|
|
1742
|
-
| **Best For**
|
|
1818
|
+
| Feature | Local Cache (Level 1) | Global Cache (Level 2) |
|
|
1819
|
+
| ------------------ | ------------------------------------- | ------------------------------------------- |
|
|
1820
|
+
| **Storage** | In-memory (Node.js process) | Persistent (KVS Custom Entities) |
|
|
1821
|
+
| **Scope** | Single forge invocation | Cross-invocation (between calls) |
|
|
1822
|
+
| **Persistence** | No (cleared on invocation end) | Yes (survives app redeploy) |
|
|
1823
|
+
| **Performance** | Very fast (memory access) | Fast (KVS optimized storage) |
|
|
1824
|
+
| **Memory Usage** | Low (invocation-scoped) | Higher (persistent storage) |
|
|
1825
|
+
| **Use Case** | Invocation optimization | Cross-invocation data sharing |
|
|
1826
|
+
| **Configuration** | None required | Requires KVS setup |
|
|
1827
|
+
| **TTL Support** | No (invocation-scoped) | Yes (automatic expiration) |
|
|
1828
|
+
| **Cache Eviction** | Automatic on DML operations | Manual or scheduled cleanup |
|
|
1829
|
+
| **Best For** | Repeated queries in single invocation | Frequently accessed data across invocations |
|
|
1743
1830
|
|
|
1744
1831
|
#### Integration with Global Cache (Level 2)
|
|
1745
1832
|
|
|
@@ -1752,18 +1839,20 @@ await forgeSQL.executeWithLocalContext(async () => {
|
|
|
1752
1839
|
// 1. Local cache (Level 1 - in-memory)
|
|
1753
1840
|
// 2. Global cache (Level 2 - KVS)
|
|
1754
1841
|
// 3. Database query
|
|
1755
|
-
const users = await forgeSQL
|
|
1756
|
-
.
|
|
1757
|
-
|
|
1758
|
-
// Using new methods with multi-level caching
|
|
1759
|
-
const usersFrom = await forgeSQL.selectCacheableFrom(users)
|
|
1842
|
+
const users = await forgeSQL
|
|
1843
|
+
.selectCacheable({ id: users.id, name: users.name })
|
|
1844
|
+
.from(users)
|
|
1760
1845
|
.where(eq(users.active, true));
|
|
1761
|
-
|
|
1846
|
+
|
|
1847
|
+
// Using new methods with multi-level caching
|
|
1848
|
+
const usersFrom = await forgeSQL.selectCacheableFrom(users).where(eq(users.active, true));
|
|
1849
|
+
|
|
1762
1850
|
// Raw SQL with multi-level caching
|
|
1851
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
1763
1852
|
const rawUsers = await forgeSQL.executeCacheable(
|
|
1764
|
-
"SELECT id, name FROM users WHERE active = ?",
|
|
1765
|
-
[true],
|
|
1766
|
-
300 // TTL in seconds
|
|
1853
|
+
"SELECT id, name FROM `users` WHERE active = ?",
|
|
1854
|
+
[true],
|
|
1855
|
+
300, // TTL in seconds
|
|
1767
1856
|
);
|
|
1768
1857
|
});
|
|
1769
1858
|
```
|
|
@@ -1787,44 +1876,45 @@ The diagram below shows how local cache works in Forge-SQL-ORM:
|
|
|
1787
1876
|
// Execute queries with caching
|
|
1788
1877
|
const users = await forgeSQL.modifyWithVersioningAndEvictCache().executeQuery(
|
|
1789
1878
|
forgeSQL.select().from(Users).where(eq(Users.active, true)),
|
|
1790
|
-
600 // Custom TTL in seconds
|
|
1879
|
+
600, // Custom TTL in seconds
|
|
1791
1880
|
);
|
|
1792
1881
|
|
|
1793
1882
|
// Execute single result queries with caching
|
|
1794
|
-
const user = await forgeSQL
|
|
1795
|
-
|
|
1796
|
-
);
|
|
1883
|
+
const user = await forgeSQL
|
|
1884
|
+
.modifyWithVersioningAndEvictCache()
|
|
1885
|
+
.executeQueryOnlyOne(forgeSQL.select().from(Users).where(eq(Users.id, 1)));
|
|
1797
1886
|
|
|
1798
1887
|
// Execute raw SQL with caching
|
|
1799
1888
|
const results = await forgeSQL.modifyWithVersioningAndEvictCache().executeRawSQL(
|
|
1800
1889
|
"SELECT * FROM users WHERE active = ?",
|
|
1801
1890
|
[true],
|
|
1802
|
-
300 // TTL in seconds
|
|
1891
|
+
300, // TTL in seconds
|
|
1803
1892
|
);
|
|
1804
1893
|
|
|
1805
1894
|
// Using new methods for cache-aware operations
|
|
1806
|
-
const usersFrom = await forgeSQL.selectCacheableFrom(Users)
|
|
1807
|
-
.where(eq(Users.active, true));
|
|
1895
|
+
const usersFrom = await forgeSQL.selectCacheableFrom(Users).where(eq(Users.active, true));
|
|
1808
1896
|
|
|
1809
|
-
const usersDistinct = await forgeSQL
|
|
1897
|
+
const usersDistinct = await forgeSQL
|
|
1898
|
+
.selectDistinctCacheableFrom(Users)
|
|
1810
1899
|
.where(eq(Users.active, true));
|
|
1811
1900
|
|
|
1812
1901
|
// Raw SQL with local and global caching
|
|
1902
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
1813
1903
|
const rawUsers = await forgeSQL.executeCacheable(
|
|
1814
|
-
"SELECT * FROM users WHERE active = ?",
|
|
1904
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
1815
1905
|
[true],
|
|
1816
|
-
300 // TTL in seconds
|
|
1906
|
+
300, // TTL in seconds
|
|
1817
1907
|
);
|
|
1818
1908
|
|
|
1819
1909
|
// Using with() for Common Table Expressions with caching
|
|
1820
1910
|
const userStats = await forgeSQL
|
|
1821
1911
|
.with(
|
|
1822
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true)).as(
|
|
1823
|
-
forgeSQL.selectFrom(orders).where(eq(orders.status,
|
|
1912
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)).as("activeUsers"),
|
|
1913
|
+
forgeSQL.selectFrom(orders).where(eq(orders.status, "completed")).as("completedOrders"),
|
|
1824
1914
|
)
|
|
1825
1915
|
.select({
|
|
1826
1916
|
totalActiveUsers: sql`COUNT(au.id)`,
|
|
1827
|
-
totalCompletedOrders: sql`COUNT(co.id)
|
|
1917
|
+
totalCompletedOrders: sql`COUNT(co.id)`,
|
|
1828
1918
|
})
|
|
1829
1919
|
.from(sql`activeUsers au`)
|
|
1830
1920
|
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
|
|
@@ -1833,21 +1923,23 @@ const userStats = await forgeSQL
|
|
|
1833
1923
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
1834
1924
|
async () => {
|
|
1835
1925
|
const users = await forgeSQL.selectFrom(usersTable);
|
|
1836
|
-
const orders = await forgeSQL
|
|
1926
|
+
const orders = await forgeSQL
|
|
1927
|
+
.selectFrom(ordersTable)
|
|
1928
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
1837
1929
|
return { users, orders };
|
|
1838
1930
|
},
|
|
1839
1931
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
1840
1932
|
const threshold = 500; // ms baseline for this resolver
|
|
1841
|
-
|
|
1933
|
+
|
|
1842
1934
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
1843
1935
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
1844
1936
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
1845
1937
|
} else if (totalDbExecutionTime > threshold) {
|
|
1846
1938
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
1847
1939
|
}
|
|
1848
|
-
|
|
1940
|
+
|
|
1849
1941
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
1850
|
-
}
|
|
1942
|
+
},
|
|
1851
1943
|
);
|
|
1852
1944
|
```
|
|
1853
1945
|
|
|
@@ -1883,9 +1975,9 @@ const options = {
|
|
|
1883
1975
|
tableName: "users",
|
|
1884
1976
|
versionField: {
|
|
1885
1977
|
fieldName: "updatedAt",
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1978
|
+
},
|
|
1979
|
+
},
|
|
1980
|
+
},
|
|
1889
1981
|
};
|
|
1890
1982
|
|
|
1891
1983
|
const forgeSQL = new ForgeSQL(options);
|
|
@@ -1896,24 +1988,26 @@ const forgeSQL = new ForgeSQL(options);
|
|
|
1896
1988
|
```typescript
|
|
1897
1989
|
// The version field will be automatically handled
|
|
1898
1990
|
await forgeSQL.modifyWithVersioning().updateById(
|
|
1899
|
-
{
|
|
1900
|
-
id: 1,
|
|
1991
|
+
{
|
|
1992
|
+
id: 1,
|
|
1901
1993
|
name: "Updated Name",
|
|
1902
|
-
updatedAt: new Date() // Will be automatically set if not provided
|
|
1903
|
-
},
|
|
1904
|
-
Users
|
|
1994
|
+
updatedAt: new Date(), // Will be automatically set if not provided
|
|
1995
|
+
},
|
|
1996
|
+
Users,
|
|
1905
1997
|
);
|
|
1906
1998
|
```
|
|
1999
|
+
|
|
1907
2000
|
or with cache support
|
|
2001
|
+
|
|
1908
2002
|
```typescript
|
|
1909
2003
|
// The version field will be automatically handled
|
|
1910
2004
|
await forgeSQL.modifyWithVersioningAndEvictCache().updateById(
|
|
1911
|
-
{
|
|
1912
|
-
id: 1,
|
|
2005
|
+
{
|
|
2006
|
+
id: 1,
|
|
1913
2007
|
name: "Updated Name",
|
|
1914
|
-
updatedAt: new Date() // Will be automatically set if not provided
|
|
1915
|
-
},
|
|
1916
|
-
Users
|
|
2008
|
+
updatedAt: new Date(), // Will be automatically set if not provided
|
|
2009
|
+
},
|
|
2010
|
+
Users,
|
|
1917
2011
|
);
|
|
1918
2012
|
```
|
|
1919
2013
|
|
|
@@ -1921,16 +2015,16 @@ await forgeSQL.modifyWithVersioningAndEvictCache().updateById(
|
|
|
1921
2015
|
|
|
1922
2016
|
The `ForgeSqlOrmOptions` object allows customization of ORM behavior:
|
|
1923
2017
|
|
|
1924
|
-
| Option | Type | Description
|
|
1925
|
-
| -------------------------- | --------- |
|
|
1926
|
-
| `logRawSqlQuery` | `boolean` | Enables logging of raw SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`.
|
|
1927
|
-
| `logCache` | `boolean` | Enables logging of cache operations (hits, misses, evictions) in the Atlassian Forge Developer Console. Useful for debugging caching issues. Defaults to `false`.
|
|
2018
|
+
| Option | Type | Description |
|
|
2019
|
+
| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
2020
|
+
| `logRawSqlQuery` | `boolean` | Enables logging of raw SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`. |
|
|
2021
|
+
| `logCache` | `boolean` | Enables logging of cache operations (hits, misses, evictions) in the Atlassian Forge Developer Console. Useful for debugging caching issues. Defaults to `false`. |
|
|
1928
2022
|
| `disableOptimisticLocking` | `boolean` | Disables optimistic locking. When set to `true`, no additional condition (e.g., a version check) is added during record updates, which can improve performance. However, this may lead to conflicts when multiple transactions attempt to update the same record concurrently. |
|
|
1929
|
-
| `additionalMetadata` | `object` | Allows adding custom metadata to all entities. This is useful for tracking common fields across all tables (e.g., `createdAt`, `updatedAt`, `createdBy`, etc.). The metadata will be automatically added to all generated entities.
|
|
1930
|
-
| `cacheEntityName` | `string` | KVS Custom entity name for cache storage. Must match the `name` in your `manifest.yml` storage entities configuration. Required for caching functionality. Defaults to `"cache"`.
|
|
1931
|
-
| `cacheTTL` | `number` | Default cache TTL in seconds. Defaults to `120` (2 minutes).
|
|
1932
|
-
| `cacheWrapTable` | `boolean` | Whether to wrap table names with backticks in cache keys. Defaults to `true`.
|
|
1933
|
-
| `hints` | `object` | SQL hints for query optimization. Optional configuration for advanced query tuning.
|
|
2023
|
+
| `additionalMetadata` | `object` | Allows adding custom metadata to all entities. This is useful for tracking common fields across all tables (e.g., `createdAt`, `updatedAt`, `createdBy`, etc.). The metadata will be automatically added to all generated entities. |
|
|
2024
|
+
| `cacheEntityName` | `string` | KVS Custom entity name for cache storage. Must match the `name` in your `manifest.yml` storage entities configuration. Required for caching functionality. Defaults to `"cache"`. |
|
|
2025
|
+
| `cacheTTL` | `number` | Default cache TTL in seconds. Defaults to `120` (2 minutes). |
|
|
2026
|
+
| `cacheWrapTable` | `boolean` | Whether to wrap table names with backticks in cache keys. Defaults to `true`. |
|
|
2027
|
+
| `hints` | `object` | SQL hints for query optimization. Optional configuration for advanced query tuning. |
|
|
1934
2028
|
|
|
1935
2029
|
## CLI Commands
|
|
1936
2030
|
|
|
@@ -1949,23 +2043,39 @@ The CLI tool provides the following main commands:
|
|
|
1949
2043
|
|
|
1950
2044
|
### Installation
|
|
1951
2045
|
|
|
2046
|
+
The CLI tool must be installed as a local dependency and used via npm scripts in your `package.json`:
|
|
2047
|
+
|
|
2048
|
+
```bash
|
|
2049
|
+
npm install forge-sql-orm-cli -D
|
|
2050
|
+
```
|
|
2051
|
+
|
|
2052
|
+
### Setup npm Scripts
|
|
2053
|
+
|
|
2054
|
+
Add the following scripts to your `package.json`:
|
|
2055
|
+
|
|
1952
2056
|
```bash
|
|
1953
|
-
npm
|
|
2057
|
+
npm pkg set scripts.models:create="forge-sql-orm-cli generate:model --output src/entities --saveEnv"
|
|
2058
|
+
npm pkg set scripts.migration:create="forge-sql-orm-cli migrations:create --force --output src/migration --entitiesPath src/entities"
|
|
2059
|
+
npm pkg set scripts.migration:update="forge-sql-orm-cli migrations:update --entitiesPath src/entities --output src/migration"
|
|
1954
2060
|
```
|
|
1955
2061
|
|
|
1956
2062
|
### Basic Usage
|
|
1957
2063
|
|
|
2064
|
+
After setting up the scripts, use them via npm:
|
|
2065
|
+
|
|
1958
2066
|
```bash
|
|
1959
2067
|
# Generate models from database
|
|
1960
|
-
|
|
2068
|
+
npm run models:create
|
|
1961
2069
|
|
|
1962
2070
|
# Create migration
|
|
1963
|
-
|
|
2071
|
+
npm run migration:create
|
|
1964
2072
|
|
|
1965
2073
|
# Update migration
|
|
1966
|
-
|
|
2074
|
+
npm run migration:update
|
|
1967
2075
|
```
|
|
1968
2076
|
|
|
2077
|
+
**Note:** The CLI tool is designed to work as a local dependency through npm scripts. Configuration is saved to `.env` file using the `--saveEnv` flag, so you only need to provide database credentials once.
|
|
2078
|
+
|
|
1969
2079
|
For detailed information about all available options and advanced usage, see the [Full CLI Documentation](forge-sql-orm-cli/README.md).
|
|
1970
2080
|
|
|
1971
2081
|
## Web Triggers for Migrations
|
|
@@ -1975,7 +2085,8 @@ Forge-SQL-ORM provides web triggers for managing database migrations in Atlassia
|
|
|
1975
2085
|
### 1. Apply Migrations Trigger
|
|
1976
2086
|
|
|
1977
2087
|
This trigger allows you to apply database migrations through a web endpoint. It's useful for:
|
|
1978
|
-
|
|
2088
|
+
|
|
2089
|
+
- Manually triggering migrations
|
|
1979
2090
|
- Running migrations as part of your deployment process
|
|
1980
2091
|
- Testing migrations in different environments
|
|
1981
2092
|
|
|
@@ -1990,22 +2101,23 @@ export const handlerMigration = async () => {
|
|
|
1990
2101
|
```
|
|
1991
2102
|
|
|
1992
2103
|
Configure in `manifest.yml`:
|
|
2104
|
+
|
|
1993
2105
|
```yaml
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2106
|
+
webtrigger:
|
|
2107
|
+
- key: invoke-schema-migration
|
|
2108
|
+
function: runSchemaMigration
|
|
2109
|
+
security:
|
|
2110
|
+
egress:
|
|
2111
|
+
allowDataEgress: false
|
|
2112
|
+
allowedResponses:
|
|
2113
|
+
- statusCode: 200
|
|
2114
|
+
body: '{"body": "Migrations successfully executed"}'
|
|
2115
|
+
sql:
|
|
2116
|
+
- key: main
|
|
2117
|
+
engine: mysql
|
|
2118
|
+
function:
|
|
2119
|
+
- key: runSchemaMigration
|
|
2120
|
+
handler: index.handlerMigration
|
|
2009
2121
|
```
|
|
2010
2122
|
|
|
2011
2123
|
### 2. Drop Migrations Trigger
|
|
@@ -2013,11 +2125,12 @@ Configure in `manifest.yml`:
|
|
|
2013
2125
|
⚠️ **WARNING**: This trigger will permanently delete all data in the specified tables and clear the migrations history. This operation cannot be undone!
|
|
2014
2126
|
|
|
2015
2127
|
This trigger allows you to completely reset your database schema. It's useful for:
|
|
2128
|
+
|
|
2016
2129
|
- Development environments where you need to start fresh
|
|
2017
2130
|
- Testing scenarios requiring a clean database
|
|
2018
2131
|
- Resetting the database before applying new migrations
|
|
2019
2132
|
|
|
2020
|
-
**Important**: The trigger will
|
|
2133
|
+
**Important**: The trigger will drop all tables including migration.
|
|
2021
2134
|
|
|
2022
2135
|
```typescript
|
|
2023
2136
|
// Example usage in your Forge app
|
|
@@ -2029,16 +2142,17 @@ export const dropMigrations = () => {
|
|
|
2029
2142
|
```
|
|
2030
2143
|
|
|
2031
2144
|
Configure in `manifest.yml`:
|
|
2145
|
+
|
|
2032
2146
|
```yaml
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2147
|
+
webtrigger:
|
|
2148
|
+
- key: drop-schema-migration
|
|
2149
|
+
function: dropMigrations
|
|
2150
|
+
sql:
|
|
2151
|
+
- key: main
|
|
2152
|
+
engine: mysql
|
|
2153
|
+
function:
|
|
2154
|
+
- key: dropMigrations
|
|
2155
|
+
handler: index.dropMigrations
|
|
2042
2156
|
```
|
|
2043
2157
|
|
|
2044
2158
|
### 3. Fetch Schema Trigger
|
|
@@ -2046,12 +2160,14 @@ Configure in `manifest.yml`:
|
|
|
2046
2160
|
⚠️ **DEVELOPMENT ONLY**: This trigger is designed for development environments only and should not be used in production.
|
|
2047
2161
|
|
|
2048
2162
|
This trigger retrieves the current database schema from Atlassian Forge SQL and generates SQL statements that can be used to recreate the database structure. It's useful for:
|
|
2163
|
+
|
|
2049
2164
|
- Development environment setup
|
|
2050
2165
|
- Schema documentation
|
|
2051
2166
|
- Database structure verification
|
|
2052
2167
|
- Creating backup scripts
|
|
2053
2168
|
|
|
2054
2169
|
**Security Considerations**:
|
|
2170
|
+
|
|
2055
2171
|
- This trigger exposes your database structure
|
|
2056
2172
|
- It temporarily disables foreign key checks
|
|
2057
2173
|
- It may expose sensitive table names and structures
|
|
@@ -2067,19 +2183,21 @@ export const fetchSchema = async () => {
|
|
|
2067
2183
|
```
|
|
2068
2184
|
|
|
2069
2185
|
Configure in `manifest.yml`:
|
|
2186
|
+
|
|
2070
2187
|
```yaml
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2188
|
+
webtrigger:
|
|
2189
|
+
- key: fetch-schema
|
|
2190
|
+
function: fetchSchema
|
|
2191
|
+
sql:
|
|
2192
|
+
- key: main
|
|
2193
|
+
engine: mysql
|
|
2194
|
+
function:
|
|
2195
|
+
- key: fetchSchema
|
|
2196
|
+
handler: index.fetchSchema
|
|
2080
2197
|
```
|
|
2081
2198
|
|
|
2082
2199
|
The response will contain SQL statements like:
|
|
2200
|
+
|
|
2083
2201
|
```sql
|
|
2084
2202
|
SET foreign_key_checks = 0;
|
|
2085
2203
|
CREATE TABLE IF NOT EXISTS users (...);
|
|
@@ -2090,6 +2208,7 @@ SET foreign_key_checks = 1;
|
|
|
2090
2208
|
### 4. Clear Cache Scheduler Trigger
|
|
2091
2209
|
|
|
2092
2210
|
This trigger automatically cleans up expired cache entries based on their TTL (Time To Live). It's useful for:
|
|
2211
|
+
|
|
2093
2212
|
- Automatic cache maintenance
|
|
2094
2213
|
- Preventing cache storage from growing indefinitely
|
|
2095
2214
|
- Ensuring optimal cache performance
|
|
@@ -2100,40 +2219,73 @@ This trigger automatically cleans up expired cache entries based on their TTL (T
|
|
|
2100
2219
|
import { clearCacheSchedulerTrigger } from "forge-sql-orm";
|
|
2101
2220
|
|
|
2102
2221
|
export const clearCache = () => {
|
|
2103
|
-
return clearCacheSchedulerTrigger({
|
|
2222
|
+
return clearCacheSchedulerTrigger({
|
|
2104
2223
|
cacheEntityName: "cache",
|
|
2105
2224
|
});
|
|
2106
2225
|
};
|
|
2107
2226
|
```
|
|
2108
2227
|
|
|
2109
2228
|
Configure in `manifest.yml`:
|
|
2229
|
+
|
|
2110
2230
|
```yaml
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2231
|
+
scheduledTrigger:
|
|
2232
|
+
- key: clear-cache-trigger
|
|
2233
|
+
function: clearCache
|
|
2234
|
+
interval: fiveMinute
|
|
2235
|
+
function:
|
|
2236
|
+
- key: clearCache
|
|
2237
|
+
handler: index.clearCache
|
|
2118
2238
|
```
|
|
2119
2239
|
|
|
2120
2240
|
**Available Intervals**:
|
|
2241
|
+
|
|
2121
2242
|
- `fiveMinute` - Every 5 minutes
|
|
2122
2243
|
- `hour` - Every hour
|
|
2123
2244
|
- `day` - Every day
|
|
2124
2245
|
|
|
2246
|
+
### 5. Slow Query Scheduler Trigger
|
|
2247
|
+
|
|
2248
|
+
This scheduler trigger automatically monitors and analyzes slow queries on a scheduled basis. For detailed information, see the [Slow Query Monitoring](#slow-query-monitoring) section.
|
|
2249
|
+
|
|
2250
|
+
**Quick Setup:**
|
|
2251
|
+
|
|
2252
|
+
```typescript
|
|
2253
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2254
|
+
|
|
2255
|
+
const forgeSQL = new ForgeSQL();
|
|
2256
|
+
|
|
2257
|
+
export const slowQueryTrigger = () =>
|
|
2258
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 1, timeout: 3000 });
|
|
2259
|
+
```
|
|
2260
|
+
|
|
2261
|
+
Configure in `manifest.yml`:
|
|
2262
|
+
|
|
2263
|
+
```yaml
|
|
2264
|
+
scheduledTrigger:
|
|
2265
|
+
- key: slow-query-trigger
|
|
2266
|
+
function: slowQueryTrigger
|
|
2267
|
+
interval: hour
|
|
2268
|
+
function:
|
|
2269
|
+
- key: slowQueryTrigger
|
|
2270
|
+
handler: index.slowQueryTrigger
|
|
2271
|
+
```
|
|
2272
|
+
|
|
2273
|
+
> **💡 Note**: For complete documentation, examples, and configuration options, see the [Slow Query Monitoring](#slow-query-monitoring) section.
|
|
2274
|
+
|
|
2125
2275
|
### Important Notes
|
|
2126
2276
|
|
|
2127
2277
|
**Security Considerations**:
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2278
|
+
|
|
2279
|
+
- The drop migrations trigger should be restricted to development environments
|
|
2280
|
+
- The fetch schema trigger should only be used in development
|
|
2281
|
+
- Consider implementing additional authentication for these endpoints
|
|
2131
2282
|
|
|
2132
2283
|
**Best Practices**:
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2284
|
+
|
|
2285
|
+
- Always backup your data before using the drop migrations trigger
|
|
2286
|
+
- Test migrations in a development environment first
|
|
2287
|
+
- Use these triggers as part of your deployment pipeline
|
|
2288
|
+
- Monitor the execution logs in the Forge Developer Console
|
|
2137
2289
|
|
|
2138
2290
|
## Query Analysis and Performance Optimization
|
|
2139
2291
|
|
|
@@ -2144,6 +2296,7 @@ Forge-SQL-ORM provides comprehensive query analysis tools to help you optimize y
|
|
|
2144
2296
|
### About Atlassian's Built-in Analysis Tools
|
|
2145
2297
|
|
|
2146
2298
|
Atlassian provides comprehensive query analysis tools in the development console, including:
|
|
2299
|
+
|
|
2147
2300
|
- Basic query performance metrics
|
|
2148
2301
|
- Slow query tracking (queries over 500ms)
|
|
2149
2302
|
- Basic execution statistics
|
|
@@ -2210,6 +2363,112 @@ The error analysis mechanism:
|
|
|
2210
2363
|
|
|
2211
2364
|
> **💡 Tip**: The automatic error analysis only triggers for timeout and OOM errors. Other errors are logged normally without plan analysis.
|
|
2212
2365
|
|
|
2366
|
+
### Slow Query Monitoring
|
|
2367
|
+
|
|
2368
|
+
Forge-SQL-ORM provides a scheduler trigger (`slowQuerySchedulerTrigger`) that automatically monitors and analyzes slow queries on an hourly basis. This trigger queries TiDB's slow query log system table and provides detailed performance information including SQL query text, memory usage, execution time, and execution plans.
|
|
2369
|
+
|
|
2370
|
+
#### Key Features
|
|
2371
|
+
|
|
2372
|
+
- **Automatic Monitoring**: Runs on a scheduled interval (recommended: hourly)
|
|
2373
|
+
- **Detailed Performance Metrics**: Memory usage, execution time, and execution plans
|
|
2374
|
+
- **Console Logging**: Results are automatically logged to the Forge Developer Console
|
|
2375
|
+
- **Configurable Time Window**: Analyze queries from the last N hours (default: 1 hour)
|
|
2376
|
+
- **Automatic Plan Retrieval**: Execution plans are included for all slow queries
|
|
2377
|
+
|
|
2378
|
+
#### Basic Setup
|
|
2379
|
+
|
|
2380
|
+
**1. Create the trigger function:**
|
|
2381
|
+
|
|
2382
|
+
```typescript
|
|
2383
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2384
|
+
|
|
2385
|
+
const forgeSQL = new ForgeSQL();
|
|
2386
|
+
|
|
2387
|
+
// Monitor slow queries from the last hour (recommended for hourly schedule)
|
|
2388
|
+
export const slowQueryTrigger = () =>
|
|
2389
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 1, timeout: 3000 });
|
|
2390
|
+
```
|
|
2391
|
+
|
|
2392
|
+
**2. Configure in `manifest.yml`:**
|
|
2393
|
+
|
|
2394
|
+
```yaml
|
|
2395
|
+
modules:
|
|
2396
|
+
scheduledTrigger:
|
|
2397
|
+
- key: slow-query-trigger
|
|
2398
|
+
function: slowQueryTrigger
|
|
2399
|
+
interval: hour # Run every hour
|
|
2400
|
+
|
|
2401
|
+
function:
|
|
2402
|
+
- key: slowQueryTrigger
|
|
2403
|
+
handler: index.slowQueryTrigger
|
|
2404
|
+
```
|
|
2405
|
+
|
|
2406
|
+
#### Configuration Options
|
|
2407
|
+
|
|
2408
|
+
| Option | Type | Default | Description |
|
|
2409
|
+
| --------- | -------- | ------- | ---------------------------------------------------------- |
|
|
2410
|
+
| `hours` | `number` | `1` | Number of hours to look back for slow queries |
|
|
2411
|
+
| `timeout` | `number` | `3000` | Timeout in milliseconds for the diagnostic query execution |
|
|
2412
|
+
|
|
2413
|
+
#### Example Console Output
|
|
2414
|
+
|
|
2415
|
+
When slow queries are detected, you'll see output like this in the Forge Developer Console:
|
|
2416
|
+
|
|
2417
|
+
```
|
|
2418
|
+
Found SlowQuery SQL: SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE u.active = ? | Memory: 8.50 MB | Time: 2500.00 ms
|
|
2419
|
+
Plan:
|
|
2420
|
+
id task estRows operator info actRows execution info memory disk
|
|
2421
|
+
Projection_7 root 1000.00 forge_38dd1c6156b94bb59c2c9a45582bbfc7.users.id, ... 1000 time:2.5s, loops:1 8.50 MB N/A
|
|
2422
|
+
└─IndexHashJoin_14 root 1000.00 inner join, ... 1000 time:2.2s, loops:1 7.98 MB N/A
|
|
2423
|
+
|
|
2424
|
+
Found SlowQuery SQL: SELECT * FROM products WHERE category = ? ORDER BY created_at DESC | Memory: 6.25 MB | Time: 1800.00 ms
|
|
2425
|
+
Plan:
|
|
2426
|
+
...
|
|
2427
|
+
```
|
|
2428
|
+
|
|
2429
|
+
#### Advanced Configuration
|
|
2430
|
+
|
|
2431
|
+
```typescript
|
|
2432
|
+
import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
|
|
2433
|
+
|
|
2434
|
+
const forgeSQL = new ForgeSQL();
|
|
2435
|
+
|
|
2436
|
+
// Monitor queries from the last 6 hours (for less frequent checks)
|
|
2437
|
+
export const sixHourSlowQueryTrigger = () =>
|
|
2438
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 6, timeout: 5000 });
|
|
2439
|
+
|
|
2440
|
+
// Monitor queries from the last 24 hours (daily monitoring)
|
|
2441
|
+
export const dailySlowQueryTrigger = () =>
|
|
2442
|
+
slowQuerySchedulerTrigger(forgeSQL, { hours: 24, timeout: 3000 });
|
|
2443
|
+
```
|
|
2444
|
+
|
|
2445
|
+
#### How It Works
|
|
2446
|
+
|
|
2447
|
+
1. **Scheduled Execution**: The trigger runs automatically on the configured interval (hourly recommended)
|
|
2448
|
+
2. **Query Analysis**: Queries TiDB's slow query log system table for queries executed within the specified time window
|
|
2449
|
+
3. **Performance Metrics**: Extracts and logs:
|
|
2450
|
+
- SQL query text (sanitized for readability)
|
|
2451
|
+
- Maximum memory usage (in MB)
|
|
2452
|
+
- Query execution time (in ms)
|
|
2453
|
+
- Detailed execution plan
|
|
2454
|
+
4. **Console Logging**: Results are logged to the Forge Developer Console via `console.warn()` for easy monitoring
|
|
2455
|
+
|
|
2456
|
+
#### Best Practices
|
|
2457
|
+
|
|
2458
|
+
- **Hourly Intervals**: Use `interval: hour` for timely detection of slow queries
|
|
2459
|
+
- **Default Time Window**: 1 hour is recommended for hourly schedules to avoid overlap
|
|
2460
|
+
- **Monitor Regularly**: Check console logs regularly to identify patterns in slow queries
|
|
2461
|
+
|
|
2462
|
+
#### Benefits
|
|
2463
|
+
|
|
2464
|
+
- **Proactive Monitoring**: Catch slow queries before they become critical issues
|
|
2465
|
+
- **Performance Trends**: Track query performance over time
|
|
2466
|
+
- **Optimization Insights**: Execution plans help identify optimization opportunities
|
|
2467
|
+
- **Zero Manual Intervention**: Fully automated monitoring with scheduled execution
|
|
2468
|
+
- **Production Safe**: Works silently in the background, only logs when slow queries are found
|
|
2469
|
+
|
|
2470
|
+
> **💡 Tip**: The trigger queries up to 50 slow queries to prevent excessive logging. Transient timeouts are usually fine; repeated timeouts indicate the diagnostic query itself is slow and should be investigated.
|
|
2471
|
+
|
|
2213
2472
|
### Available Analysis Tools
|
|
2214
2473
|
|
|
2215
2474
|
```typescript
|
|
@@ -2230,59 +2489,57 @@ const analyzeForgeSql = forgeSQL.analyze();
|
|
|
2230
2489
|
|
|
2231
2490
|
// Analyze a Drizzle query
|
|
2232
2491
|
const plan = await analyzeForgeSql.explain(
|
|
2233
|
-
forgeSQL
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2492
|
+
forgeSQL
|
|
2493
|
+
.select({
|
|
2494
|
+
table1: testEntityJoin1,
|
|
2495
|
+
table2: { name: testEntityJoin2.name, email: testEntityJoin2.email },
|
|
2496
|
+
count: rawSql<number>`COUNT(*)`,
|
|
2497
|
+
table3: {
|
|
2498
|
+
table12: testEntityJoin1.name,
|
|
2499
|
+
table22: testEntityJoin2.email,
|
|
2500
|
+
table32: testEntity.id,
|
|
2501
|
+
},
|
|
2502
|
+
})
|
|
2503
|
+
.from(testEntityJoin1)
|
|
2504
|
+
.innerJoin(testEntityJoin2, eq(testEntityJoin1.id, testEntityJoin2.id)),
|
|
2245
2505
|
);
|
|
2246
2506
|
|
|
2247
2507
|
// Analyze a raw SQL query
|
|
2248
|
-
const rawPlan = await analyzeForgeSql.explainRaw(
|
|
2249
|
-
"SELECT * FROM users WHERE id = ?",
|
|
2250
|
-
[1]
|
|
2251
|
-
);
|
|
2508
|
+
const rawPlan = await analyzeForgeSql.explainRaw("SELECT * FROM users WHERE id = ?", [1]);
|
|
2252
2509
|
|
|
2253
2510
|
// Analyze new methods
|
|
2254
2511
|
const usersFromPlan = await analyzeForgeSql.explain(
|
|
2255
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true))
|
|
2512
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)),
|
|
2256
2513
|
);
|
|
2257
2514
|
|
|
2258
2515
|
const usersCacheablePlan = await analyzeForgeSql.explain(
|
|
2259
|
-
forgeSQL.selectCacheableFrom(users).where(eq(users.active, true))
|
|
2516
|
+
forgeSQL.selectCacheableFrom(users).where(eq(users.active, true)),
|
|
2260
2517
|
);
|
|
2261
2518
|
|
|
2262
2519
|
// Analyze Common Table Expressions (CTEs)
|
|
2263
2520
|
const ctePlan = await analyzeForgeSql.explain(
|
|
2264
2521
|
forgeSQL
|
|
2265
2522
|
.with(
|
|
2266
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true)).as(
|
|
2267
|
-
forgeSQL.selectFrom(orders).where(eq(orders.status,
|
|
2523
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)).as("activeUsers"),
|
|
2524
|
+
forgeSQL.selectFrom(orders).where(eq(orders.status, "completed")).as("completedOrders"),
|
|
2268
2525
|
)
|
|
2269
2526
|
.select({
|
|
2270
2527
|
totalActiveUsers: sql`COUNT(au.id)`,
|
|
2271
|
-
totalCompletedOrders: sql`COUNT(co.id)
|
|
2528
|
+
totalCompletedOrders: sql`COUNT(co.id)`,
|
|
2272
2529
|
})
|
|
2273
2530
|
.from(sql`activeUsers au`)
|
|
2274
|
-
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`))
|
|
2531
|
+
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`)),
|
|
2275
2532
|
);
|
|
2276
2533
|
```
|
|
2277
2534
|
|
|
2278
2535
|
This analysis provides insights into:
|
|
2536
|
+
|
|
2279
2537
|
- How the database executes your query
|
|
2280
2538
|
- Which indexes are being used
|
|
2281
2539
|
- Estimated vs actual row counts
|
|
2282
2540
|
- Resource usage at each step
|
|
2283
2541
|
- Performance optimization opportunities
|
|
2284
2542
|
|
|
2285
|
-
|
|
2286
2543
|
## Migration Guide
|
|
2287
2544
|
|
|
2288
2545
|
### Migrating from 2.0.x to 2.1.x
|
|
@@ -2292,18 +2549,20 @@ This section covers the breaking changes introduced in version 2.1.x and how to
|
|
|
2292
2549
|
#### 1. Method Renaming (BREAKING CHANGES)
|
|
2293
2550
|
|
|
2294
2551
|
**Removed Methods:**
|
|
2552
|
+
|
|
2295
2553
|
- `forgeSQL.modify()` → **REMOVED** (use `forgeSQL.modifyWithVersioning()`)
|
|
2296
2554
|
- `forgeSQL.crud()` → **REMOVED** (use `forgeSQL.modifyWithVersioning()`)
|
|
2297
2555
|
|
|
2298
2556
|
**Migration Steps:**
|
|
2299
2557
|
|
|
2300
2558
|
1. **Replace `modify()` calls:**
|
|
2559
|
+
|
|
2301
2560
|
```typescript
|
|
2302
2561
|
// ❌ Old (2.0.x) - NO LONGER WORKS
|
|
2303
2562
|
await forgeSQL.modify().insert(Users, [userData]);
|
|
2304
2563
|
await forgeSQL.modify().updateById(updateData, Users);
|
|
2305
2564
|
await forgeSQL.modify().deleteById(1, Users);
|
|
2306
|
-
|
|
2565
|
+
|
|
2307
2566
|
// ✅ New (2.1.x) - REQUIRED
|
|
2308
2567
|
await forgeSQL.modifyWithVersioning().insert(Users, [userData]);
|
|
2309
2568
|
await forgeSQL.modifyWithVersioning().updateById(updateData, Users);
|
|
@@ -2311,12 +2570,13 @@ This section covers the breaking changes introduced in version 2.1.x and how to
|
|
|
2311
2570
|
```
|
|
2312
2571
|
|
|
2313
2572
|
2. **Replace `crud()` calls:**
|
|
2573
|
+
|
|
2314
2574
|
```typescript
|
|
2315
2575
|
// ❌ Old (2.0.x) - NO LONGER WORKS
|
|
2316
2576
|
await forgeSQL.crud().insert(Users, [userData]);
|
|
2317
2577
|
await forgeSQL.crud().updateById(updateData, Users);
|
|
2318
2578
|
await forgeSQL.crud().deleteById(1, Users);
|
|
2319
|
-
|
|
2579
|
+
|
|
2320
2580
|
// ✅ New (2.1.x) - REQUIRED
|
|
2321
2581
|
await forgeSQL.modifyWithVersioning().insert(Users, [userData]);
|
|
2322
2582
|
await forgeSQL.modifyWithVersioning().updateById(updateData, Users);
|
|
@@ -2326,8 +2586,9 @@ This section covers the breaking changes introduced in version 2.1.x and how to
|
|
|
2326
2586
|
#### 2. New API Methods
|
|
2327
2587
|
|
|
2328
2588
|
**New Methods Available:**
|
|
2589
|
+
|
|
2329
2590
|
- `forgeSQL.insert()` - Basic Drizzle operations
|
|
2330
|
-
- `forgeSQL.update()` - Basic Drizzle operations
|
|
2591
|
+
- `forgeSQL.update()` - Basic Drizzle operations
|
|
2331
2592
|
- `forgeSQL.delete()` - Basic Drizzle operations
|
|
2332
2593
|
- `forgeSQL.insertAndEvictCache()` - Basic Drizzle operations with evict cache after execution
|
|
2333
2594
|
- `forgeSQL.updateAndEvictCache()` - Basic Drizzle operations with evict cache after execution
|
|
@@ -2355,46 +2616,43 @@ await forgeSQL.insert(Users).values(userData);
|
|
|
2355
2616
|
await forgeSQL.modifyWithVersioningAndEvictCache().insert(Users, [userData]);
|
|
2356
2617
|
|
|
2357
2618
|
// ✅ New query methods for better performance
|
|
2358
|
-
const users = await forgeSQL.selectFrom(Users)
|
|
2359
|
-
.where(eq(Users.active, true));
|
|
2619
|
+
const users = await forgeSQL.selectFrom(Users).where(eq(Users.active, true));
|
|
2360
2620
|
|
|
2361
|
-
const usersDistinct = await forgeSQL.selectDistinctFrom(Users)
|
|
2362
|
-
.where(eq(Users.active, true));
|
|
2621
|
+
const usersDistinct = await forgeSQL.selectDistinctFrom(Users).where(eq(Users.active, true));
|
|
2363
2622
|
|
|
2364
|
-
const usersCacheable = await forgeSQL.selectCacheableFrom(Users)
|
|
2365
|
-
.where(eq(Users.active, true));
|
|
2623
|
+
const usersCacheable = await forgeSQL.selectCacheableFrom(Users).where(eq(Users.active, true));
|
|
2366
2624
|
|
|
2367
2625
|
// ✅ Raw SQL execution with caching
|
|
2368
|
-
const rawUsers = await forgeSQL.execute(
|
|
2369
|
-
"SELECT * FROM users WHERE active = ?",
|
|
2370
|
-
[true]
|
|
2371
|
-
);
|
|
2626
|
+
const rawUsers = await forgeSQL.execute("SELECT * FROM users WHERE active = ?", [true]);
|
|
2372
2627
|
|
|
2628
|
+
// ⚠️ IMPORTANT: When using executeCacheable(), all table names must be wrapped with backticks (`)
|
|
2373
2629
|
const cachedRawUsers = await forgeSQL.executeCacheable(
|
|
2374
|
-
"SELECT * FROM users WHERE active = ?",
|
|
2375
|
-
[true],
|
|
2376
|
-
300
|
|
2630
|
+
"SELECT * FROM `users` WHERE active = ?",
|
|
2631
|
+
[true],
|
|
2632
|
+
300,
|
|
2377
2633
|
);
|
|
2378
2634
|
|
|
2379
2635
|
// ✅ Raw SQL execution with metadata capture and performance monitoring
|
|
2380
2636
|
const usersWithMetadata = await forgeSQL.executeWithMetadata(
|
|
2381
2637
|
async () => {
|
|
2382
2638
|
const users = await forgeSQL.selectFrom(usersTable);
|
|
2383
|
-
const orders = await forgeSQL
|
|
2639
|
+
const orders = await forgeSQL
|
|
2640
|
+
.selectFrom(ordersTable)
|
|
2641
|
+
.where(eq(ordersTable.userId, usersTable.id));
|
|
2384
2642
|
return { users, orders };
|
|
2385
2643
|
},
|
|
2386
2644
|
(totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
2387
2645
|
const threshold = 500; // ms baseline for this resolver
|
|
2388
|
-
|
|
2646
|
+
|
|
2389
2647
|
if (totalDbExecutionTime > threshold * 1.5) {
|
|
2390
2648
|
console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
2391
2649
|
await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
2392
2650
|
} else if (totalDbExecutionTime > threshold) {
|
|
2393
2651
|
console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
2394
2652
|
}
|
|
2395
|
-
|
|
2653
|
+
|
|
2396
2654
|
console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
2397
|
-
}
|
|
2655
|
+
},
|
|
2398
2656
|
);
|
|
2399
2657
|
|
|
2400
2658
|
// ✅ DDL operations for schema modifications
|
|
@@ -2418,25 +2676,25 @@ await forgeSQL.executeDDLActions(async () => {
|
|
|
2418
2676
|
SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
|
|
2419
2677
|
WHERE AVG_LATENCY > 1000000
|
|
2420
2678
|
`);
|
|
2421
|
-
|
|
2679
|
+
|
|
2422
2680
|
// Execute complex analysis queries in DDL context
|
|
2423
2681
|
const performanceData = await forgeSQL.execute(`
|
|
2424
2682
|
SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
|
|
2425
2683
|
WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
|
|
2426
2684
|
`);
|
|
2427
|
-
|
|
2685
|
+
|
|
2428
2686
|
return { slowQueries, performanceData };
|
|
2429
2687
|
});
|
|
2430
2688
|
|
|
2431
2689
|
// ✅ Common Table Expressions (CTEs)
|
|
2432
2690
|
const userStats = await forgeSQL
|
|
2433
2691
|
.with(
|
|
2434
|
-
forgeSQL.selectFrom(users).where(eq(users.active, true)).as(
|
|
2435
|
-
forgeSQL.selectFrom(orders).where(eq(orders.status,
|
|
2692
|
+
forgeSQL.selectFrom(users).where(eq(users.active, true)).as("activeUsers"),
|
|
2693
|
+
forgeSQL.selectFrom(orders).where(eq(orders.status, "completed")).as("completedOrders"),
|
|
2436
2694
|
)
|
|
2437
2695
|
.select({
|
|
2438
2696
|
totalActiveUsers: sql`COUNT(au.id)`,
|
|
2439
|
-
totalCompletedOrders: sql`COUNT(co.id)
|
|
2697
|
+
totalCompletedOrders: sql`COUNT(co.id)`,
|
|
2440
2698
|
})
|
|
2441
2699
|
.from(sql`activeUsers au`)
|
|
2442
2700
|
.leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
|
|
@@ -2450,7 +2708,7 @@ You can use a simple find-and-replace to migrate your code:
|
|
|
2450
2708
|
# Replace modify() calls
|
|
2451
2709
|
find . -name "*.ts" -o -name "*.js" | xargs sed -i 's/forgeSQL\.modify()/forgeSQL.modifyWithVersioning()/g'
|
|
2452
2710
|
|
|
2453
|
-
# Replace crud() calls
|
|
2711
|
+
# Replace crud() calls
|
|
2454
2712
|
find . -name "*.ts" -o -name "*.js" | xargs sed -i 's/forgeSQL\.crud()/forgeSQL.modifyWithVersioning()/g'
|
|
2455
2713
|
```
|
|
2456
2714
|
|
|
@@ -2462,5 +2720,6 @@ find . -name "*.ts" -o -name "*.js" | xargs sed -i 's/forgeSQL\.crud()/forgeSQL.
|
|
|
2462
2720
|
- ✅ **Migration Required**: You must update your code to use the new methods
|
|
2463
2721
|
|
|
2464
2722
|
## License
|
|
2723
|
+
|
|
2465
2724
|
This project is licensed under the **MIT License**.
|
|
2466
2725
|
Feel free to use it for commercial and personal projects.
|