@mosaic-code/prisma-deadlock-avoidance-tests 0.1.0
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/LICENSE +207 -0
- package/README.md +248 -0
- package/dist/assertions/row-assertion.d.ts +18 -0
- package/dist/assertions/row-assertion.d.ts.map +1 -0
- package/dist/assertions/row-assertion.js +53 -0
- package/dist/assertions/row-assertion.js.map +1 -0
- package/dist/assertions/table-assertion.d.ts +17 -0
- package/dist/assertions/table-assertion.d.ts.map +1 -0
- package/dist/assertions/table-assertion.js +33 -0
- package/dist/assertions/table-assertion.js.map +1 -0
- package/dist/extension.d.ts +60 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +322 -0
- package/dist/extension.js.map +1 -0
- package/dist/graphs/row-graph.d.ts +61 -0
- package/dist/graphs/row-graph.d.ts.map +1 -0
- package/dist/graphs/row-graph.js +231 -0
- package/dist/graphs/row-graph.js.map +1 -0
- package/dist/graphs/table-graph.d.ts +61 -0
- package/dist/graphs/table-graph.d.ts.map +1 -0
- package/dist/graphs/table-graph.js +123 -0
- package/dist/graphs/table-graph.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/caller-extractor.d.ts +27 -0
- package/dist/utils/caller-extractor.d.ts.map +1 -0
- package/dist/utils/caller-extractor.js +144 -0
- package/dist/utils/caller-extractor.js.map +1 -0
- package/dist/utils/primary-key-extractor.d.ts +23 -0
- package/dist/utils/primary-key-extractor.d.ts.map +1 -0
- package/dist/utils/primary-key-extractor.js +128 -0
- package/dist/utils/primary-key-extractor.js.map +1 -0
- package/dist/utils/raw-query-parser.d.ts +17 -0
- package/dist/utils/raw-query-parser.d.ts.map +1 -0
- package/dist/utils/raw-query-parser.js +58 -0
- package/dist/utils/raw-query-parser.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { Prisma } from '@prisma/client';
|
|
2
|
+
import { TableLockGraph } from './graphs/table-graph.js';
|
|
3
|
+
import { RowLockGraphs } from './graphs/row-graph.js';
|
|
4
|
+
import { extractCaller } from './utils/caller-extractor.js';
|
|
5
|
+
import { extractPrimaryKeys } from './utils/primary-key-extractor.js';
|
|
6
|
+
import { inferTableFromSql, isLockingSql } from './utils/raw-query-parser.js';
|
|
7
|
+
import { assertConsistentTableLocking as assertTableLocking, TableLockingAssertionError, } from './assertions/table-assertion.js';
|
|
8
|
+
import { assertConsistentRowLocking as assertRowLocking, RowLockingAssertionError, } from './assertions/row-assertion.js';
|
|
9
|
+
/**
|
|
10
|
+
* Operations that acquire locks on records
|
|
11
|
+
*/
|
|
12
|
+
const LOCKING_OPERATIONS = new Set([
|
|
13
|
+
'update',
|
|
14
|
+
'updateMany',
|
|
15
|
+
'delete',
|
|
16
|
+
'deleteMany',
|
|
17
|
+
'create',
|
|
18
|
+
'createMany',
|
|
19
|
+
'createManyAndReturn',
|
|
20
|
+
'upsert',
|
|
21
|
+
]);
|
|
22
|
+
let globalState = null;
|
|
23
|
+
/**
|
|
24
|
+
* Get or create the global state instance
|
|
25
|
+
*/
|
|
26
|
+
function getGlobalState() {
|
|
27
|
+
if (!globalState) {
|
|
28
|
+
globalState = {
|
|
29
|
+
tableGraph: new TableLockGraph(),
|
|
30
|
+
rowGraphs: new RowLockGraphs(),
|
|
31
|
+
currentOperationTables: [],
|
|
32
|
+
currentLockedRecords: new Map(),
|
|
33
|
+
inBatch: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return globalState;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Start tracking a new transaction or batch of operations.
|
|
40
|
+
* Called automatically when $transaction is invoked.
|
|
41
|
+
*/
|
|
42
|
+
function startOperationBatch() {
|
|
43
|
+
const state = getGlobalState();
|
|
44
|
+
state.currentOperationTables = [];
|
|
45
|
+
state.currentLockedRecords.clear();
|
|
46
|
+
state.inBatch = true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* End the current operation batch.
|
|
50
|
+
* Called automatically when $transaction completes.
|
|
51
|
+
*/
|
|
52
|
+
function endOperationBatch() {
|
|
53
|
+
const state = getGlobalState();
|
|
54
|
+
state.currentOperationTables = [];
|
|
55
|
+
state.currentLockedRecords.clear();
|
|
56
|
+
state.inBatch = false;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Execute a function while tracking table ordering as a single batch.
|
|
60
|
+
* Called automatically by the $transaction wrapper.
|
|
61
|
+
*/
|
|
62
|
+
async function withOperationTracking(fn) {
|
|
63
|
+
startOperationBatch();
|
|
64
|
+
try {
|
|
65
|
+
return await fn();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
endOperationBatch();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Record a table lock, creating edges from previously locked tables in the current batch
|
|
73
|
+
*/
|
|
74
|
+
function recordTableLock(table, state) {
|
|
75
|
+
const caller = extractCaller();
|
|
76
|
+
// Create edges from all previously locked tables to this one
|
|
77
|
+
for (const prevTable of state.currentOperationTables) {
|
|
78
|
+
if (prevTable !== table) {
|
|
79
|
+
state.tableGraph.addEdge(prevTable, table, caller);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Add this table to the current operation's locked tables
|
|
83
|
+
if (!state.currentOperationTables.includes(table)) {
|
|
84
|
+
state.currentOperationTables.push(table);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Record row ordering from query results.
|
|
89
|
+
* Filters out records that have already been locked in the current transaction.
|
|
90
|
+
*/
|
|
91
|
+
function recordRowOrdering(table, result, state) {
|
|
92
|
+
const allKeys = extractPrimaryKeys(table, result);
|
|
93
|
+
if (allKeys.length === 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Get or create the set of locked records for this table
|
|
97
|
+
let lockedRecords = state.currentLockedRecords.get(table);
|
|
98
|
+
if (!lockedRecords) {
|
|
99
|
+
lockedRecords = new Set();
|
|
100
|
+
state.currentLockedRecords.set(table, lockedRecords);
|
|
101
|
+
}
|
|
102
|
+
// Filter out records that have already been locked in this transaction
|
|
103
|
+
const newKeys = state.inBatch
|
|
104
|
+
? allKeys.filter((key) => !lockedRecords.has(key))
|
|
105
|
+
: allKeys;
|
|
106
|
+
// Mark the new records as locked
|
|
107
|
+
for (const key of newKeys) {
|
|
108
|
+
lockedRecords.add(key);
|
|
109
|
+
}
|
|
110
|
+
// Only add to graph if we have multiple new keys (need ordering)
|
|
111
|
+
if (newKeys.length > 1) {
|
|
112
|
+
const caller = extractCaller();
|
|
113
|
+
state.rowGraphs.addRowOrdering(table, newKeys, caller);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Extract SQL string from raw query args
|
|
118
|
+
*/
|
|
119
|
+
function extractSqlFromArgs(args) {
|
|
120
|
+
if (!args)
|
|
121
|
+
return '';
|
|
122
|
+
if (typeof args === 'object' && args !== null) {
|
|
123
|
+
const argsObj = args;
|
|
124
|
+
if ('strings' in argsObj && Array.isArray(argsObj.strings)) {
|
|
125
|
+
return String(argsObj.strings[0] ?? '');
|
|
126
|
+
}
|
|
127
|
+
if ('0' in argsObj) {
|
|
128
|
+
return String(argsObj['0'] ?? '');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (typeof args === 'string') {
|
|
132
|
+
return args;
|
|
133
|
+
}
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Extract SQL from unsafe query args
|
|
138
|
+
*/
|
|
139
|
+
function extractSqlFromUnsafeArgs(args) {
|
|
140
|
+
if (Array.isArray(args) && args.length > 0) {
|
|
141
|
+
return String(args[0] ?? '');
|
|
142
|
+
}
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Handle raw query tracking - extracts SQL, infers table, and records table lock.
|
|
147
|
+
* Logs a warning if table inference fails.
|
|
148
|
+
*/
|
|
149
|
+
function handleRawQuery(args, extractFn, enabled) {
|
|
150
|
+
if (!enabled)
|
|
151
|
+
return;
|
|
152
|
+
const sql = extractFn(args);
|
|
153
|
+
if (!sql)
|
|
154
|
+
return;
|
|
155
|
+
const table = inferTableFromSql(sql);
|
|
156
|
+
if (table && isLockingSql(sql)) {
|
|
157
|
+
const state = getGlobalState();
|
|
158
|
+
recordTableLock(table, state);
|
|
159
|
+
}
|
|
160
|
+
else if (isLockingSql(sql) && !table) {
|
|
161
|
+
// Log warning if this is a locking query but we couldn't infer the table
|
|
162
|
+
console.warn(`[prisma-deadlock-detection] Could not infer table name from raw query. ` +
|
|
163
|
+
`The query will not be tracked for deadlock detection. ` +
|
|
164
|
+
`Query: ${sql.slice(0, 100)}${sql.length > 100 ? '...' : ''}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Create the internal Prisma extension for query interception.
|
|
169
|
+
*/
|
|
170
|
+
function createDeadlockExtension(enabled) {
|
|
171
|
+
return Prisma.defineExtension({
|
|
172
|
+
name: 'prisma-deadlock-detection',
|
|
173
|
+
query: {
|
|
174
|
+
$allModels: {
|
|
175
|
+
async $allOperations({ model, operation, args, query }) {
|
|
176
|
+
if (!enabled) {
|
|
177
|
+
return query(args);
|
|
178
|
+
}
|
|
179
|
+
const state = getGlobalState();
|
|
180
|
+
const isLocking = LOCKING_OPERATIONS.has(operation);
|
|
181
|
+
// Record table lock before query execution
|
|
182
|
+
if (isLocking) {
|
|
183
|
+
recordTableLock(model, state);
|
|
184
|
+
}
|
|
185
|
+
// Execute the query
|
|
186
|
+
const result = await query(args);
|
|
187
|
+
// Record row ordering from result
|
|
188
|
+
if (isLocking && result !== null && result !== undefined) {
|
|
189
|
+
recordRowOrdering(model, result, state);
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
async $queryRaw({ args, query }) {
|
|
195
|
+
handleRawQuery(args, extractSqlFromArgs, enabled);
|
|
196
|
+
return query(args);
|
|
197
|
+
},
|
|
198
|
+
async $queryRawUnsafe({ args, query }) {
|
|
199
|
+
handleRawQuery(args, extractSqlFromUnsafeArgs, enabled);
|
|
200
|
+
return query(args);
|
|
201
|
+
},
|
|
202
|
+
async $executeRaw({ args, query }) {
|
|
203
|
+
handleRawQuery(args, extractSqlFromArgs, enabled);
|
|
204
|
+
return query(args);
|
|
205
|
+
},
|
|
206
|
+
async $executeRawUnsafe({ args, query }) {
|
|
207
|
+
handleRawQuery(args, extractSqlFromUnsafeArgs, enabled);
|
|
208
|
+
return query(args);
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Wrap a Prisma client with deadlock detection.
|
|
215
|
+
* Automatically tracks table and row locking order across all transactions.
|
|
216
|
+
*
|
|
217
|
+
* Usage:
|
|
218
|
+
* ```typescript
|
|
219
|
+
* const prisma = withDeadlockDetection(new PrismaClient())
|
|
220
|
+
* ```
|
|
221
|
+
*
|
|
222
|
+
* @param client The PrismaClient instance to wrap
|
|
223
|
+
* @param config Optional configuration
|
|
224
|
+
* @returns A wrapped client with automatic transaction tracking
|
|
225
|
+
*/
|
|
226
|
+
export function withDeadlockDetection(client, config) {
|
|
227
|
+
if (process.env.NODE_ENV === 'production') {
|
|
228
|
+
console.warn('[@mosaic-code/prisma-deadlock-avoidance-tests] WARNING: This library is intended for test environments only. ' +
|
|
229
|
+
'Running in production may impact performance.');
|
|
230
|
+
}
|
|
231
|
+
const enabled = config?.enabled !== false;
|
|
232
|
+
// Apply the query interception extension
|
|
233
|
+
const extended = client.$extends(createDeadlockExtension(enabled));
|
|
234
|
+
// Wrap with proxy to intercept $transaction calls
|
|
235
|
+
return new Proxy(extended, {
|
|
236
|
+
get(target, prop) {
|
|
237
|
+
if (prop === '$transaction') {
|
|
238
|
+
// Return a wrapped $transaction method
|
|
239
|
+
return function (args) {
|
|
240
|
+
const originalMethod = target.$transaction;
|
|
241
|
+
// Check if this is an interactive transaction (callback-based)
|
|
242
|
+
const isInteractive = typeof args === 'function';
|
|
243
|
+
if (isInteractive && enabled) {
|
|
244
|
+
// Automatically wrap the transaction callback with tracking
|
|
245
|
+
return withOperationTracking(() => originalMethod.call(target, args));
|
|
246
|
+
}
|
|
247
|
+
// Batch transaction, disabled, or raw transaction - pass through
|
|
248
|
+
return originalMethod.call(target, args);
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// For all other properties, return the original value
|
|
252
|
+
const value = target[String(prop)];
|
|
253
|
+
// If it's a function, bind it to the target
|
|
254
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Assert that table locking has been consistent across all tracked operations.
|
|
260
|
+
* Throws TableLockingAssertionError if a cycle is detected.
|
|
261
|
+
*/
|
|
262
|
+
export function assertConsistentTableLocking() {
|
|
263
|
+
const state = getGlobalState();
|
|
264
|
+
assertTableLocking(state.tableGraph);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Assert that row locking has been consistent within tables.
|
|
268
|
+
* Throws RowLockingAssertionError if violations are detected.
|
|
269
|
+
*
|
|
270
|
+
* @param options Options controlling which tables to check and strictness mode
|
|
271
|
+
*/
|
|
272
|
+
export function assertConsistentRowLocking(options) {
|
|
273
|
+
const state = getGlobalState();
|
|
274
|
+
assertRowLocking(state.rowGraphs, options);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Assert no deadlock risk exists by checking both table and row locking consistency.
|
|
278
|
+
* This is a convenience function that calls both assertions.
|
|
279
|
+
*
|
|
280
|
+
* @param rowOptions Options for the row locking assertion
|
|
281
|
+
*/
|
|
282
|
+
export function assertNoDeadlockRisk(rowOptions) {
|
|
283
|
+
assertConsistentTableLocking();
|
|
284
|
+
assertConsistentRowLocking(rowOptions);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Reset all deadlock detection state.
|
|
288
|
+
* Call this between test runs if needed.
|
|
289
|
+
*/
|
|
290
|
+
export function resetDeadlockDetection() {
|
|
291
|
+
globalState = null;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Wrapper function for tracking operations from prisma-lock-for-update.
|
|
295
|
+
* Use this to wrap forUpdate calls so they're tracked in the deadlock detection graph.
|
|
296
|
+
*
|
|
297
|
+
* @param model The model name (e.g., 'User', 'Post')
|
|
298
|
+
* @param fn The async function that performs the forUpdate operation
|
|
299
|
+
* @returns The result of the operation
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const user = await trackForUpdate('User', () =>
|
|
304
|
+
* tx.user.findUniqueForUpdate({ where: { id: 1 } })
|
|
305
|
+
* )
|
|
306
|
+
* ```
|
|
307
|
+
*/
|
|
308
|
+
export async function trackForUpdate(model, fn) {
|
|
309
|
+
const state = getGlobalState();
|
|
310
|
+
// Record the table lock
|
|
311
|
+
recordTableLock(model, state);
|
|
312
|
+
// Execute the operation
|
|
313
|
+
const result = await fn();
|
|
314
|
+
// Extract and record row ordering from result
|
|
315
|
+
if (result !== null && result !== undefined) {
|
|
316
|
+
recordRowOrdering(model, result, state);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
// Re-export error types for consumers
|
|
321
|
+
export { TableLockingAssertionError, RowLockingAssertionError };
|
|
322
|
+
//# sourceMappingURL=extension.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Graph } from '@dagrejs/graphlib';
|
|
2
|
+
import type { CallerInfo, RowCycleInfo, StrictMode } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages directed graphs of row lock ordering, one per table.
|
|
5
|
+
* Nodes represent primary keys, edges represent "row A was locked before row B".
|
|
6
|
+
*/
|
|
7
|
+
export declare class RowLockGraphs {
|
|
8
|
+
private graphs;
|
|
9
|
+
/**
|
|
10
|
+
* Get or create the graph for a specific table
|
|
11
|
+
*/
|
|
12
|
+
private getOrCreateGraph;
|
|
13
|
+
/**
|
|
14
|
+
* Add row ordering edges for a sequence of locked rows.
|
|
15
|
+
* Creates edges between consecutive rows: [a, b, c] -> a->b, b->c
|
|
16
|
+
*/
|
|
17
|
+
addRowOrdering(table: string, orderedKeys: string[], caller: CallerInfo): void;
|
|
18
|
+
/**
|
|
19
|
+
* Merge row graphs from another instance into this one
|
|
20
|
+
*/
|
|
21
|
+
mergeFrom(other: RowLockGraphs): void;
|
|
22
|
+
/**
|
|
23
|
+
* Find cycles in a specific table's row graph
|
|
24
|
+
*/
|
|
25
|
+
findCycles(table: string): string[][];
|
|
26
|
+
/**
|
|
27
|
+
* Check if all edges in a table's graph follow the specified ordering direction.
|
|
28
|
+
* Only makes sense for numeric/comparable primary keys.
|
|
29
|
+
*/
|
|
30
|
+
private checkOrdering;
|
|
31
|
+
/**
|
|
32
|
+
* Check if all edges in a table's graph follow ascending order.
|
|
33
|
+
* Only makes sense for numeric/comparable primary keys.
|
|
34
|
+
*/
|
|
35
|
+
checkAscendingOrder(table: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if all edges in a table's graph follow descending order.
|
|
38
|
+
*/
|
|
39
|
+
checkDescendingOrder(table: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a table's graph satisfies the given strict mode
|
|
42
|
+
*/
|
|
43
|
+
checkStrictOrdering(table: string, mode: StrictMode): RowCycleInfo | null;
|
|
44
|
+
/**
|
|
45
|
+
* Get all callers from a table's graph
|
|
46
|
+
*/
|
|
47
|
+
private getAllCallers;
|
|
48
|
+
/**
|
|
49
|
+
* Get all tables that have row graphs
|
|
50
|
+
*/
|
|
51
|
+
getAllTables(): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Get the graph for a specific table (for testing)
|
|
54
|
+
*/
|
|
55
|
+
getGraph(table: string): Graph | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Clear all data from all graphs
|
|
58
|
+
*/
|
|
59
|
+
reset(): void;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=row-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-graph.d.ts","sourceRoot":"","sources":["../../src/graphs/row-graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAO,MAAM,mBAAmB,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAgB,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGrF;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAgC;IAE9C;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;;OAGG;IACH,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EAAE,EACrB,MAAM,EAAE,UAAU,GACjB,IAAI;IAuCP;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAWrC;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE;IAMrC;;;OAGG;IACH,OAAO,CAAC,aAAa;IA8BrB;;;OAGG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI3C;;OAEG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAI5C;;OAEG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,IAAI;IAqEzE;;OAEG;IACH,OAAO,CAAC,aAAa;IAqBrB;;OAEG;IACH,YAAY,IAAI,MAAM,EAAE;IAIxB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAI1C;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { Graph, alg } from '@dagrejs/graphlib';
|
|
2
|
+
import { addCallerIfUnique, callerKey } from '../utils/caller-extractor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages directed graphs of row lock ordering, one per table.
|
|
5
|
+
* Nodes represent primary keys, edges represent "row A was locked before row B".
|
|
6
|
+
*/
|
|
7
|
+
export class RowLockGraphs {
|
|
8
|
+
graphs = new Map();
|
|
9
|
+
/**
|
|
10
|
+
* Get or create the graph for a specific table
|
|
11
|
+
*/
|
|
12
|
+
getOrCreateGraph(table) {
|
|
13
|
+
let graph = this.graphs.get(table);
|
|
14
|
+
if (!graph) {
|
|
15
|
+
graph = new Graph({ directed: true });
|
|
16
|
+
this.graphs.set(table, graph);
|
|
17
|
+
}
|
|
18
|
+
return graph;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Add row ordering edges for a sequence of locked rows.
|
|
22
|
+
* Creates edges between consecutive rows: [a, b, c] -> a->b, b->c
|
|
23
|
+
*/
|
|
24
|
+
addRowOrdering(table, orderedKeys, caller) {
|
|
25
|
+
if (orderedKeys.length < 2) {
|
|
26
|
+
// Need at least 2 rows to create an ordering edge
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const graph = this.getOrCreateGraph(table);
|
|
30
|
+
// Create edges between consecutive rows
|
|
31
|
+
for (let i = 0; i < orderedKeys.length - 1; i++) {
|
|
32
|
+
const from = orderedKeys[i];
|
|
33
|
+
const to = orderedKeys[i + 1];
|
|
34
|
+
// Skip self-edges (shouldn't happen with unique PKs, but be safe)
|
|
35
|
+
if (from === to)
|
|
36
|
+
continue;
|
|
37
|
+
// Ensure nodes exist
|
|
38
|
+
if (!graph.hasNode(from)) {
|
|
39
|
+
graph.setNode(from);
|
|
40
|
+
}
|
|
41
|
+
if (!graph.hasNode(to)) {
|
|
42
|
+
graph.setNode(to);
|
|
43
|
+
}
|
|
44
|
+
// Get or create edge label
|
|
45
|
+
const existingLabel = graph.edge(from, to);
|
|
46
|
+
if (existingLabel) {
|
|
47
|
+
// Deduplicate callers by file:line
|
|
48
|
+
addCallerIfUnique(existingLabel.callers, caller);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
graph.setEdge(from, to, {
|
|
52
|
+
callers: [caller],
|
|
53
|
+
table,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Merge row graphs from another instance into this one
|
|
60
|
+
*/
|
|
61
|
+
mergeFrom(other) {
|
|
62
|
+
for (const [table, otherGraph] of other.graphs) {
|
|
63
|
+
for (const edge of otherGraph.edges()) {
|
|
64
|
+
const label = otherGraph.edge(edge);
|
|
65
|
+
for (const caller of label.callers) {
|
|
66
|
+
this.addRowOrdering(table, [edge.v, edge.w], caller);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Find cycles in a specific table's row graph
|
|
73
|
+
*/
|
|
74
|
+
findCycles(table) {
|
|
75
|
+
const graph = this.graphs.get(table);
|
|
76
|
+
if (!graph)
|
|
77
|
+
return [];
|
|
78
|
+
return alg.findCycles(graph);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check if all edges in a table's graph follow the specified ordering direction.
|
|
82
|
+
* Only makes sense for numeric/comparable primary keys.
|
|
83
|
+
*/
|
|
84
|
+
checkOrdering(table, direction) {
|
|
85
|
+
const graph = this.graphs.get(table);
|
|
86
|
+
if (!graph)
|
|
87
|
+
return true;
|
|
88
|
+
for (const edge of graph.edges()) {
|
|
89
|
+
// Try to compare as numbers
|
|
90
|
+
const fromNum = Number(edge.v);
|
|
91
|
+
const toNum = Number(edge.w);
|
|
92
|
+
if (!isNaN(fromNum) && !isNaN(toNum)) {
|
|
93
|
+
if (direction === 'ASC' && fromNum > toNum) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (direction === 'DESC' && fromNum < toNum) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// String comparison
|
|
102
|
+
if (direction === 'ASC' && edge.v > edge.w) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
if (direction === 'DESC' && edge.v < edge.w) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Check if all edges in a table's graph follow ascending order.
|
|
114
|
+
* Only makes sense for numeric/comparable primary keys.
|
|
115
|
+
*/
|
|
116
|
+
checkAscendingOrder(table) {
|
|
117
|
+
return this.checkOrdering(table, 'ASC');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if all edges in a table's graph follow descending order.
|
|
121
|
+
*/
|
|
122
|
+
checkDescendingOrder(table) {
|
|
123
|
+
return this.checkOrdering(table, 'DESC');
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if a table's graph satisfies the given strict mode
|
|
127
|
+
*/
|
|
128
|
+
checkStrictOrdering(table, mode) {
|
|
129
|
+
const cycles = this.findCycles(table);
|
|
130
|
+
// Cycles are always a violation
|
|
131
|
+
if (cycles.length > 0) {
|
|
132
|
+
const graph = this.graphs.get(table);
|
|
133
|
+
const cycle = cycles[0];
|
|
134
|
+
const callers = [];
|
|
135
|
+
// Collect callers from cycle edges
|
|
136
|
+
for (let i = 0; i < cycle.length; i++) {
|
|
137
|
+
const from = cycle[i];
|
|
138
|
+
const to = cycle[(i + 1) % cycle.length];
|
|
139
|
+
const label = graph.edge(from, to);
|
|
140
|
+
if (label) {
|
|
141
|
+
callers.push(...label.callers);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
table,
|
|
146
|
+
primaryKeys: cycle.map((k) => (isNaN(Number(k)) ? k : Number(k))),
|
|
147
|
+
callers,
|
|
148
|
+
message: `Cycle detected in row ordering`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// No strict ordering required
|
|
152
|
+
if (mode === false) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// Check specific ordering
|
|
156
|
+
if (mode === 'ASC') {
|
|
157
|
+
if (!this.checkAscendingOrder(table)) {
|
|
158
|
+
return {
|
|
159
|
+
table,
|
|
160
|
+
primaryKeys: [],
|
|
161
|
+
callers: this.getAllCallers(table),
|
|
162
|
+
message: `Row ordering is not consistently ascending`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (mode === 'DESC') {
|
|
167
|
+
if (!this.checkDescendingOrder(table)) {
|
|
168
|
+
return {
|
|
169
|
+
table,
|
|
170
|
+
primaryKeys: [],
|
|
171
|
+
callers: this.getAllCallers(table),
|
|
172
|
+
message: `Row ordering is not consistently descending`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (mode === true) {
|
|
177
|
+
// Must be either all ASC or all DESC
|
|
178
|
+
const isAsc = this.checkAscendingOrder(table);
|
|
179
|
+
const isDesc = this.checkDescendingOrder(table);
|
|
180
|
+
if (!isAsc && !isDesc) {
|
|
181
|
+
return {
|
|
182
|
+
table,
|
|
183
|
+
primaryKeys: [],
|
|
184
|
+
callers: this.getAllCallers(table),
|
|
185
|
+
message: `Row ordering is neither consistently ascending nor descending`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get all callers from a table's graph
|
|
193
|
+
*/
|
|
194
|
+
getAllCallers(table) {
|
|
195
|
+
const graph = this.graphs.get(table);
|
|
196
|
+
if (!graph)
|
|
197
|
+
return [];
|
|
198
|
+
const callers = [];
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
for (const edge of graph.edges()) {
|
|
201
|
+
const label = graph.edge(edge);
|
|
202
|
+
for (const caller of label.callers) {
|
|
203
|
+
const key = callerKey(caller);
|
|
204
|
+
if (!seen.has(key)) {
|
|
205
|
+
seen.add(key);
|
|
206
|
+
callers.push(caller);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return callers;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get all tables that have row graphs
|
|
214
|
+
*/
|
|
215
|
+
getAllTables() {
|
|
216
|
+
return Array.from(this.graphs.keys());
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get the graph for a specific table (for testing)
|
|
220
|
+
*/
|
|
221
|
+
getGraph(table) {
|
|
222
|
+
return this.graphs.get(table);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Clear all data from all graphs
|
|
226
|
+
*/
|
|
227
|
+
reset() {
|
|
228
|
+
this.graphs.clear();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=row-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-graph.js","sourceRoot":"","sources":["../../src/graphs/row-graph.ts"],"names":[],"mappings":"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"}
|