ddlforge 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 +21 -0
- package/README.md +326 -0
- package/dist/bin/ddlforge.d.ts +6 -0
- package/dist/bin/ddlforge.d.ts.map +1 -0
- package/dist/bin/ddlforge.js +14 -0
- package/dist/bin/ddlforge.js.map +1 -0
- package/dist/src/cli.d.ts +21 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +262 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/engine/analyzer.d.ts +30 -0
- package/dist/src/engine/analyzer.d.ts.map +1 -0
- package/dist/src/engine/analyzer.js +76 -0
- package/dist/src/engine/analyzer.js.map +1 -0
- package/dist/src/engine/locks.d.ts +26 -0
- package/dist/src/engine/locks.d.ts.map +1 -0
- package/dist/src/engine/locks.js +136 -0
- package/dist/src/engine/locks.js.map +1 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +17 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lexer/sqlTokenizer.d.ts +40 -0
- package/dist/src/lexer/sqlTokenizer.d.ts.map +1 -0
- package/dist/src/lexer/sqlTokenizer.js +494 -0
- package/dist/src/lexer/sqlTokenizer.js.map +1 -0
- package/dist/src/lexer/tokens.d.ts +35 -0
- package/dist/src/lexer/tokens.d.ts.map +1 -0
- package/dist/src/lexer/tokens.js +81 -0
- package/dist/src/lexer/tokens.js.map +1 -0
- package/dist/src/reporters/index.d.ts +7 -0
- package/dist/src/reporters/index.d.ts.map +1 -0
- package/dist/src/reporters/index.js +7 -0
- package/dist/src/reporters/index.js.map +1 -0
- package/dist/src/reporters/json.d.ts +20 -0
- package/dist/src/reporters/json.d.ts.map +1 -0
- package/dist/src/reporters/json.js +35 -0
- package/dist/src/reporters/json.js.map +1 -0
- package/dist/src/reporters/markdown.d.ts +6 -0
- package/dist/src/reporters/markdown.d.ts.map +1 -0
- package/dist/src/reporters/markdown.js +70 -0
- package/dist/src/reporters/markdown.js.map +1 -0
- package/dist/src/reporters/terminal.d.ts +10 -0
- package/dist/src/reporters/terminal.d.ts.map +1 -0
- package/dist/src/reporters/terminal.js +109 -0
- package/dist/src/reporters/terminal.js.map +1 -0
- package/dist/src/rules/addColumnNotNull.d.ts +9 -0
- package/dist/src/rules/addColumnNotNull.d.ts.map +1 -0
- package/dist/src/rules/addColumnNotNull.js +131 -0
- package/dist/src/rules/addColumnNotNull.js.map +1 -0
- package/dist/src/rules/foreignKeyNotValid.d.ts +9 -0
- package/dist/src/rules/foreignKeyNotValid.d.ts.map +1 -0
- package/dist/src/rules/foreignKeyNotValid.js +119 -0
- package/dist/src/rules/foreignKeyNotValid.js.map +1 -0
- package/dist/src/rules/index.d.ts +14 -0
- package/dist/src/rules/index.d.ts.map +1 -0
- package/dist/src/rules/index.js +28 -0
- package/dist/src/rules/index.js.map +1 -0
- package/dist/src/rules/indexConcurrently.d.ts +9 -0
- package/dist/src/rules/indexConcurrently.d.ts.map +1 -0
- package/dist/src/rules/indexConcurrently.js +92 -0
- package/dist/src/rules/indexConcurrently.js.map +1 -0
- package/dist/src/rules/prismaRenameDropAdd.d.ts +11 -0
- package/dist/src/rules/prismaRenameDropAdd.d.ts.map +1 -0
- package/dist/src/rules/prismaRenameDropAdd.js +144 -0
- package/dist/src/rules/prismaRenameDropAdd.js.map +1 -0
- package/dist/src/rules/setNotNullFullScan.d.ts +9 -0
- package/dist/src/rules/setNotNullFullScan.d.ts.map +1 -0
- package/dist/src/rules/setNotNullFullScan.js +93 -0
- package/dist/src/rules/setNotNullFullScan.js.map +1 -0
- package/dist/src/rules/transactionTrap.d.ts +9 -0
- package/dist/src/rules/transactionTrap.d.ts.map +1 -0
- package/dist/src/rules/transactionTrap.js +104 -0
- package/dist/src/rules/transactionTrap.js.map +1 -0
- package/dist/src/rules/types.d.ts +36 -0
- package/dist/src/rules/types.d.ts.map +1 -0
- package/dist/src/rules/types.js +5 -0
- package/dist/src/rules/types.js.map +1 -0
- package/dist/src/rules/unbatchedBackfill.d.ts +9 -0
- package/dist/src/rules/unbatchedBackfill.d.ts.map +1 -0
- package/dist/src/rules/unbatchedBackfill.js +58 -0
- package/dist/src/rules/unbatchedBackfill.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - AST-lite IR builder & rule pipeline
|
|
3
|
+
*/
|
|
4
|
+
import { Finding, Rule } from '../rules/types.js';
|
|
5
|
+
export interface AnalyzerOptions {
|
|
6
|
+
filePath?: string;
|
|
7
|
+
pgVersion?: number;
|
|
8
|
+
rules?: Rule[];
|
|
9
|
+
isPrismaMigration?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface AnalysisResult {
|
|
12
|
+
file: string;
|
|
13
|
+
statementsAnalyzed: number;
|
|
14
|
+
findings: Finding[];
|
|
15
|
+
blockersCount: number;
|
|
16
|
+
warningsCount: number;
|
|
17
|
+
advisoriesCount: number;
|
|
18
|
+
durationMs: number;
|
|
19
|
+
hasBlockers: boolean;
|
|
20
|
+
exitCode: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class MigrationAnalyzer {
|
|
23
|
+
private readonly rules;
|
|
24
|
+
constructor(rules?: Rule[]);
|
|
25
|
+
analyze(sql: string, options?: AnalyzerOptions): AnalysisResult;
|
|
26
|
+
private detectIsPrisma;
|
|
27
|
+
private detectPrismaNoTransaction;
|
|
28
|
+
}
|
|
29
|
+
export declare function analyzeSql(sql: string, options?: AnalyzerOptions): AnalysisResult;
|
|
30
|
+
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/engine/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAG/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B,YAAY,KAAK,GAAE,IAAI,EAAc,EAEpC;IAEM,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CAmDzE;IAED,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,yBAAyB;CAIlC;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAGjF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - AST-lite IR builder & rule pipeline
|
|
3
|
+
*/
|
|
4
|
+
import { splitStatements } from '../lexer/sqlTokenizer.js';
|
|
5
|
+
import { ALL_RULES } from '../rules/index.js';
|
|
6
|
+
export class MigrationAnalyzer {
|
|
7
|
+
rules;
|
|
8
|
+
constructor(rules = ALL_RULES) {
|
|
9
|
+
this.rules = rules;
|
|
10
|
+
}
|
|
11
|
+
analyze(sql, options = {}) {
|
|
12
|
+
const startTime = performance.now();
|
|
13
|
+
const filePath = options.filePath ?? 'anonymous.sql';
|
|
14
|
+
const pgVersion = options.pgVersion ?? 16;
|
|
15
|
+
// Split statements single-pass
|
|
16
|
+
const statements = splitStatements(sql);
|
|
17
|
+
// Prisma migration detection
|
|
18
|
+
const isPrisma = options.isPrismaMigration ?? this.detectIsPrisma(filePath, sql);
|
|
19
|
+
const hasFilePrismaNoTransaction = this.detectPrismaNoTransaction(sql);
|
|
20
|
+
const context = {
|
|
21
|
+
filePath,
|
|
22
|
+
fileContent: sql,
|
|
23
|
+
pgVersion,
|
|
24
|
+
statements,
|
|
25
|
+
isPrismaMigration: isPrisma,
|
|
26
|
+
hasFilePrismaNoTransaction,
|
|
27
|
+
};
|
|
28
|
+
const findings = [];
|
|
29
|
+
for (const rule of this.rules) {
|
|
30
|
+
const ruleFindings = rule.check(context);
|
|
31
|
+
findings.push(...ruleFindings);
|
|
32
|
+
}
|
|
33
|
+
// Sort findings by line and column
|
|
34
|
+
findings.sort((a, b) => {
|
|
35
|
+
if (a.line !== b.line)
|
|
36
|
+
return a.line - b.line;
|
|
37
|
+
return a.column - b.column;
|
|
38
|
+
});
|
|
39
|
+
const blockersCount = findings.filter(f => f.severity === 'BLOCKER').length;
|
|
40
|
+
const warningsCount = findings.filter(f => f.severity === 'WARNING').length;
|
|
41
|
+
const advisoriesCount = findings.filter(f => f.severity === 'ADVISORY').length;
|
|
42
|
+
const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
|
|
43
|
+
const hasBlockers = blockersCount > 0;
|
|
44
|
+
return {
|
|
45
|
+
file: filePath,
|
|
46
|
+
statementsAnalyzed: statements.length,
|
|
47
|
+
findings,
|
|
48
|
+
blockersCount,
|
|
49
|
+
warningsCount,
|
|
50
|
+
advisoriesCount,
|
|
51
|
+
durationMs,
|
|
52
|
+
hasBlockers,
|
|
53
|
+
exitCode: hasBlockers ? 1 : 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
detectIsPrisma(filePath, content) {
|
|
57
|
+
const normalizedPath = filePath.replace(/\\/g, '/').toLowerCase();
|
|
58
|
+
if (normalizedPath.includes('prisma/migrations') || normalizedPath.includes('prisma/')) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// Prisma generator markers
|
|
62
|
+
if (/--\s*(CreateTable|AlterTable|DropTable|CreateIndex|DropIndex|RedefineTables)/.test(content)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
detectPrismaNoTransaction(content) {
|
|
68
|
+
// Detect -- prisma:no-transaction or -- prisma-no-transaction on its own directive line
|
|
69
|
+
return /^\s*--\s*prisma[:\-]no-transaction\s*$/im.test(content);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function analyzeSql(sql, options) {
|
|
73
|
+
const analyzer = new MigrationAnalyzer(options?.rules);
|
|
74
|
+
return analyzer.analyze(sql, options);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../../src/engine/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAG3D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAqB9C,MAAM,OAAO,iBAAiB;IACX,KAAK,CAAS;IAE/B,YAAY,KAAK,GAAW,SAAS;QACnC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEM,OAAO,CAAC,GAAW,EAAE,OAAO,GAAoB,EAAE;QACvD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,eAAe,CAAC;QACrD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QAE1C,+BAA+B;QAC/B,MAAM,UAAU,GAAgB,eAAe,CAAC,GAAG,CAAC,CAAC;QAErD,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,iBAAiB,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjF,MAAM,0BAA0B,GAAG,IAAI,CAAC,yBAAyB,CAAC,GAAG,CAAC,CAAC;QAEvE,MAAM,OAAO,GAAgB;YAC3B,QAAQ;YACR,WAAW,EAAE,GAAG;YAChB,SAAS;YACT,UAAU;YACV,iBAAiB,EAAE,QAAQ;YAC3B,0BAA0B;SAC3B,CAAC;QAEF,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;QACjC,CAAC;QAED,mCAAmC;QACnC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;gBAAE,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;YAC9C,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QAC5E,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;QAC5E,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;QAC/E,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;QAC3E,MAAM,WAAW,GAAG,aAAa,GAAG,CAAC,CAAC;QAEtC,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,kBAAkB,EAAE,UAAU,CAAC,MAAM;YACrC,QAAQ;YACR,aAAa;YACb,aAAa;YACb,eAAe;YACf,UAAU;YACV,WAAW;YACX,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC9B,CAAC;IACJ,CAAC;IAEO,cAAc,CAAC,QAAgB,EAAE,OAAe;QACtD,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;QAClE,IAAI,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACvF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,2BAA2B;QAC3B,IAAI,8EAA8E,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACjG,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,yBAAyB,CAAC,OAAe;QAC/C,wFAAwF;QACxF,OAAO,0CAA0C,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClE,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,OAAyB;IAC/D,MAAM,QAAQ,GAAG,IAAI,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - Postgres Lock levels and concurrency conflict model
|
|
3
|
+
*/
|
|
4
|
+
export declare enum PostgresLockLevel {
|
|
5
|
+
ACCESS_SHARE = "ACCESS SHARE",
|
|
6
|
+
ROW_SHARE = "ROW SHARE",
|
|
7
|
+
ROW_EXCLUSIVE = "ROW EXCLUSIVE",
|
|
8
|
+
SHARE_UPDATE_EXCLUSIVE = "SHARE UPDATE EXCLUSIVE",
|
|
9
|
+
SHARE = "SHARE",
|
|
10
|
+
SHARE_ROW_EXCLUSIVE = "SHARE ROW EXCLUSIVE",
|
|
11
|
+
EXCLUSIVE = "EXCLUSIVE",
|
|
12
|
+
ACCESS_EXCLUSIVE = "ACCESS EXCLUSIVE",
|
|
13
|
+
NONE = "NONE"
|
|
14
|
+
}
|
|
15
|
+
export interface LockMetadata {
|
|
16
|
+
level: PostgresLockLevel;
|
|
17
|
+
rank: number;
|
|
18
|
+
blocksReads: boolean;
|
|
19
|
+
blocksWrites: boolean;
|
|
20
|
+
description: string;
|
|
21
|
+
conflictsWith: PostgresLockLevel[];
|
|
22
|
+
}
|
|
23
|
+
export declare const LOCK_METADATA: Record<PostgresLockLevel, LockMetadata>;
|
|
24
|
+
export declare function getLockMetadata(level: PostgresLockLevel): LockMetadata;
|
|
25
|
+
export declare function locksConflict(lockA: PostgresLockLevel, lockB: PostgresLockLevel): boolean;
|
|
26
|
+
//# sourceMappingURL=locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../../src/engine/locks.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oBAAY,iBAAiB;IAC3B,YAAY,iBAAiB;IAC7B,SAAS,cAAc;IACvB,aAAa,kBAAkB;IAC/B,sBAAsB,2BAA2B;IACjD,KAAK,UAAU;IACf,mBAAmB,wBAAwB;IAC3C,SAAS,cAAc;IACvB,gBAAgB,qBAAqB;IACrC,IAAI,SAAS;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,iBAAiB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,iBAAiB,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,iBAAiB,EAAE,YAAY,CAiHjE,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAEtE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEzF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - Postgres Lock levels and concurrency conflict model
|
|
3
|
+
*/
|
|
4
|
+
export var PostgresLockLevel;
|
|
5
|
+
(function (PostgresLockLevel) {
|
|
6
|
+
PostgresLockLevel["ACCESS_SHARE"] = "ACCESS SHARE";
|
|
7
|
+
PostgresLockLevel["ROW_SHARE"] = "ROW SHARE";
|
|
8
|
+
PostgresLockLevel["ROW_EXCLUSIVE"] = "ROW EXCLUSIVE";
|
|
9
|
+
PostgresLockLevel["SHARE_UPDATE_EXCLUSIVE"] = "SHARE UPDATE EXCLUSIVE";
|
|
10
|
+
PostgresLockLevel["SHARE"] = "SHARE";
|
|
11
|
+
PostgresLockLevel["SHARE_ROW_EXCLUSIVE"] = "SHARE ROW EXCLUSIVE";
|
|
12
|
+
PostgresLockLevel["EXCLUSIVE"] = "EXCLUSIVE";
|
|
13
|
+
PostgresLockLevel["ACCESS_EXCLUSIVE"] = "ACCESS EXCLUSIVE";
|
|
14
|
+
PostgresLockLevel["NONE"] = "NONE";
|
|
15
|
+
})(PostgresLockLevel || (PostgresLockLevel = {}));
|
|
16
|
+
export const LOCK_METADATA = {
|
|
17
|
+
[PostgresLockLevel.ACCESS_SHARE]: {
|
|
18
|
+
level: PostgresLockLevel.ACCESS_SHARE,
|
|
19
|
+
rank: 1,
|
|
20
|
+
blocksReads: false,
|
|
21
|
+
blocksWrites: false,
|
|
22
|
+
description: 'Read-only queries (SELECT). Permissive.',
|
|
23
|
+
conflictsWith: [PostgresLockLevel.ACCESS_EXCLUSIVE],
|
|
24
|
+
},
|
|
25
|
+
[PostgresLockLevel.ROW_SHARE]: {
|
|
26
|
+
level: PostgresLockLevel.ROW_SHARE,
|
|
27
|
+
rank: 2,
|
|
28
|
+
blocksReads: false,
|
|
29
|
+
blocksWrites: false,
|
|
30
|
+
description: 'Row locks (SELECT FOR UPDATE / FOR SHARE).',
|
|
31
|
+
conflictsWith: [PostgresLockLevel.EXCLUSIVE, PostgresLockLevel.ACCESS_EXCLUSIVE],
|
|
32
|
+
},
|
|
33
|
+
[PostgresLockLevel.ROW_EXCLUSIVE]: {
|
|
34
|
+
level: PostgresLockLevel.ROW_EXCLUSIVE,
|
|
35
|
+
rank: 3,
|
|
36
|
+
blocksReads: false,
|
|
37
|
+
blocksWrites: false,
|
|
38
|
+
description: 'Standard DML (INSERT, UPDATE, DELETE). Conflicts with table-level DDL locks.',
|
|
39
|
+
conflictsWith: [
|
|
40
|
+
PostgresLockLevel.SHARE,
|
|
41
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
42
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
43
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
[PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE]: {
|
|
47
|
+
level: PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE,
|
|
48
|
+
rank: 4,
|
|
49
|
+
blocksReads: false,
|
|
50
|
+
blocksWrites: false,
|
|
51
|
+
description: 'Concurrent operations (CREATE INDEX CONCURRENTLY, VALIDATE CONSTRAINT, VACUUM). Safe for concurrent reads and writes.',
|
|
52
|
+
conflictsWith: [
|
|
53
|
+
PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE,
|
|
54
|
+
PostgresLockLevel.SHARE,
|
|
55
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
56
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
57
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
[PostgresLockLevel.SHARE]: {
|
|
61
|
+
level: PostgresLockLevel.SHARE,
|
|
62
|
+
rank: 5,
|
|
63
|
+
blocksReads: false,
|
|
64
|
+
blocksWrites: true,
|
|
65
|
+
description: 'Index creation without CONCURRENTLY. BLOCKS ALL WRITES (INSERT, UPDATE, DELETE).',
|
|
66
|
+
conflictsWith: [
|
|
67
|
+
PostgresLockLevel.ROW_EXCLUSIVE,
|
|
68
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
69
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
70
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
[PostgresLockLevel.SHARE_ROW_EXCLUSIVE]: {
|
|
74
|
+
level: PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
75
|
+
rank: 6,
|
|
76
|
+
blocksReads: false,
|
|
77
|
+
blocksWrites: true,
|
|
78
|
+
description: 'Foreign key addition without NOT VALID. BLOCKS ALL WRITES on target and referenced tables.',
|
|
79
|
+
conflictsWith: [
|
|
80
|
+
PostgresLockLevel.ROW_EXCLUSIVE,
|
|
81
|
+
PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE,
|
|
82
|
+
PostgresLockLevel.SHARE,
|
|
83
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
84
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
85
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
[PostgresLockLevel.EXCLUSIVE]: {
|
|
89
|
+
level: PostgresLockLevel.EXCLUSIVE,
|
|
90
|
+
rank: 7,
|
|
91
|
+
blocksReads: false,
|
|
92
|
+
blocksWrites: true,
|
|
93
|
+
description: 'Exclusive lock. Blocks row-share and all writes.',
|
|
94
|
+
conflictsWith: [
|
|
95
|
+
PostgresLockLevel.ROW_SHARE,
|
|
96
|
+
PostgresLockLevel.ROW_EXCLUSIVE,
|
|
97
|
+
PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE,
|
|
98
|
+
PostgresLockLevel.SHARE,
|
|
99
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
100
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
101
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
[PostgresLockLevel.ACCESS_EXCLUSIVE]: {
|
|
105
|
+
level: PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
106
|
+
rank: 8,
|
|
107
|
+
blocksReads: true,
|
|
108
|
+
blocksWrites: true,
|
|
109
|
+
description: 'FULL TABLE LOCK (ALTER TABLE, DROP TABLE, TRUNCATE). Blocks EVERYTHING, including SELECT.',
|
|
110
|
+
conflictsWith: [
|
|
111
|
+
PostgresLockLevel.ACCESS_SHARE,
|
|
112
|
+
PostgresLockLevel.ROW_SHARE,
|
|
113
|
+
PostgresLockLevel.ROW_EXCLUSIVE,
|
|
114
|
+
PostgresLockLevel.SHARE_UPDATE_EXCLUSIVE,
|
|
115
|
+
PostgresLockLevel.SHARE,
|
|
116
|
+
PostgresLockLevel.SHARE_ROW_EXCLUSIVE,
|
|
117
|
+
PostgresLockLevel.EXCLUSIVE,
|
|
118
|
+
PostgresLockLevel.ACCESS_EXCLUSIVE,
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
[PostgresLockLevel.NONE]: {
|
|
122
|
+
level: PostgresLockLevel.NONE,
|
|
123
|
+
rank: 0,
|
|
124
|
+
blocksReads: false,
|
|
125
|
+
blocksWrites: false,
|
|
126
|
+
description: 'No database lock acquired.',
|
|
127
|
+
conflictsWith: [],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
export function getLockMetadata(level) {
|
|
131
|
+
return LOCK_METADATA[level] ?? LOCK_METADATA[PostgresLockLevel.NONE];
|
|
132
|
+
}
|
|
133
|
+
export function locksConflict(lockA, lockB) {
|
|
134
|
+
return LOCK_METADATA[lockA]?.conflictsWith.includes(lockB) ?? false;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.js","sourceRoot":"","sources":["../../../src/engine/locks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - Ultra-fast, zero-runtime-dependency Postgres migration lock linter
|
|
3
|
+
* and data-loss prevention engine.
|
|
4
|
+
*/
|
|
5
|
+
export * from './lexer/tokens.js';
|
|
6
|
+
export * from './lexer/sqlTokenizer.js';
|
|
7
|
+
export * from './engine/locks.js';
|
|
8
|
+
export * from './engine/analyzer.js';
|
|
9
|
+
export * from './rules/index.js';
|
|
10
|
+
export * from './reporters/index.js';
|
|
11
|
+
export { parseArgs, discoverSqlFiles, runCli, CliOptions } from './cli.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AAGrC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,sBAAsB,CAAC;AAGrC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - Ultra-fast, zero-runtime-dependency Postgres migration lock linter
|
|
3
|
+
* and data-loss prevention engine.
|
|
4
|
+
*/
|
|
5
|
+
// Lexer & Tokens
|
|
6
|
+
export * from './lexer/tokens.js';
|
|
7
|
+
export * from './lexer/sqlTokenizer.js';
|
|
8
|
+
// Engine & Locks
|
|
9
|
+
export * from './engine/locks.js';
|
|
10
|
+
export * from './engine/analyzer.js';
|
|
11
|
+
// Rules
|
|
12
|
+
export * from './rules/index.js';
|
|
13
|
+
// Reporters
|
|
14
|
+
export * from './reporters/index.js';
|
|
15
|
+
// CLI Utilities
|
|
16
|
+
export { parseArgs, discoverSqlFiles, runCli } from './cli.js';
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iBAAiB;AACjB,cAAc,mBAAmB,CAAC;AAClC,cAAc,yBAAyB,CAAC;AAExC,iBAAiB;AACjB,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AAErC,QAAQ;AACR,cAAc,kBAAkB,CAAC;AAEjC,YAAY;AACZ,cAAc,sBAAsB,CAAC;AAErC,gBAAgB;AAChB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAc,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ddlforge - Zero-dependency SQL DDL lexer & statement splitter
|
|
3
|
+
*/
|
|
4
|
+
import { Token, Statement } from './tokens.js';
|
|
5
|
+
export interface LexerOptions {
|
|
6
|
+
includeWhitespace?: boolean;
|
|
7
|
+
includeComments?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class SqlTokenizer {
|
|
10
|
+
private readonly input;
|
|
11
|
+
private readonly length;
|
|
12
|
+
private pos;
|
|
13
|
+
private line;
|
|
14
|
+
private column;
|
|
15
|
+
constructor(input: string);
|
|
16
|
+
tokenize(options?: LexerOptions): Token[];
|
|
17
|
+
private peek;
|
|
18
|
+
private advance;
|
|
19
|
+
private isWhitespace;
|
|
20
|
+
private isDigit;
|
|
21
|
+
private isIdentifierStart;
|
|
22
|
+
private isIdentifierPart;
|
|
23
|
+
private createToken;
|
|
24
|
+
private readWhitespace;
|
|
25
|
+
private readSingleLineComment;
|
|
26
|
+
private readMultiLineComment;
|
|
27
|
+
private isDollarQuoteStart;
|
|
28
|
+
private readDollarString;
|
|
29
|
+
private readSingleQuotedString;
|
|
30
|
+
private readQuotedIdentifier;
|
|
31
|
+
private readNumber;
|
|
32
|
+
private readIdentifierOrKeyword;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Splits SQL into discrete executable statements while tracking comments,
|
|
36
|
+
* source coordinates, and inline lint directives.
|
|
37
|
+
*/
|
|
38
|
+
export declare function splitStatements(sql: string): Statement[];
|
|
39
|
+
export declare function tokenize(sql: string, options?: LexerOptions): Token[];
|
|
40
|
+
//# sourceMappingURL=sqlTokenizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlTokenizer.d.ts","sourceRoot":"","sources":["../../../src/lexer/sqlTokenizer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAa,SAAS,EAAgB,MAAM,aAAa,CAAC;AAExE,MAAM,WAAW,YAAY;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,GAAG,CAAa;IACxB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,MAAM,CAAa;IAE3B,YAAY,KAAK,EAAE,MAAM,EAGxB;IAEM,QAAQ,CAAC,OAAO,GAAE,YAAiB,GAAG,KAAK,EAAE,CAkHnD;IAED,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,OAAO;IAcf,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,qBAAqB;IAgC7B,OAAO,CAAC,oBAAoB;IA+B5B,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,gBAAgB;IA+CxB,OAAO,CAAC,sBAAsB;IA8C9B,OAAO,CAAC,oBAAoB;IAwC5B,OAAO,CAAC,UAAU;IAqBlB,OAAO,CAAC,uBAAuB;CAuBhC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,CA4CxD;AAyCD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,KAAK,EAAE,CAGrE"}
|