vouchington-tooling 0.0.2 → 0.0.4

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.
Files changed (56) hide show
  1. package/README.md +4 -0
  2. package/dist/cli/commands/gha-runtime-audit.d.mts +3 -0
  3. package/dist/cli/commands/gha-runtime-audit.mjs +23 -0
  4. package/dist/cli/index.d.mts +1 -1
  5. package/dist/cli/index.mjs +15 -2
  6. package/dist/cli/parse-gha-runtime-audit.d.mts +13 -0
  7. package/dist/cli/parse-gha-runtime-audit.mjs +44 -0
  8. package/dist/cli/parse.d.mts +2 -1
  9. package/dist/cli/parse.mjs +3 -0
  10. package/dist/cli/usage.d.mts +1 -1
  11. package/dist/cli/usage.mjs +7 -0
  12. package/dist/gha-runtime-audit/audit.d.mts +3 -0
  13. package/dist/gha-runtime-audit/audit.mjs +106 -0
  14. package/dist/gha-runtime-audit/index.d.mts +5 -0
  15. package/dist/gha-runtime-audit/index.mjs +3 -0
  16. package/dist/gha-runtime-audit/index.test-helpers.d.mts +14 -0
  17. package/dist/gha-runtime-audit/index.test-helpers.mjs +63 -0
  18. package/dist/gha-runtime-audit/model.d.mts +60 -0
  19. package/dist/gha-runtime-audit/model.mjs +83 -0
  20. package/dist/gha-runtime-audit/results.d.mts +15 -0
  21. package/dist/gha-runtime-audit/results.mjs +48 -0
  22. package/dist/gha-runtime-audit/scope.d.mts +29 -0
  23. package/dist/gha-runtime-audit/scope.mjs +39 -0
  24. package/dist/index.d.mts +5 -1
  25. package/dist/index.mjs +3 -1
  26. package/dist/sql-ast/add-column.d.mts +1 -0
  27. package/dist/sql-ast/add-column.mjs +22 -0
  28. package/dist/sql-ast/constraint-do-block.d.mts +11 -0
  29. package/dist/sql-ast/constraint-do-block.mjs +83 -0
  30. package/dist/sql-ast/constraint-shared.d.mts +19 -0
  31. package/dist/sql-ast/constraint-shared.mjs +20 -0
  32. package/dist/sql-ast/constraint.d.mts +2 -0
  33. package/dist/sql-ast/constraint.mjs +63 -0
  34. package/dist/sql-ast/create-table.d.mts +25 -0
  35. package/dist/sql-ast/create-table.mjs +79 -0
  36. package/dist/sql-ast/default-function.d.mts +18 -0
  37. package/dist/sql-ast/default-function.mjs +53 -0
  38. package/dist/sql-ast/drop-index.d.mts +5 -0
  39. package/dist/sql-ast/drop-index.mjs +31 -0
  40. package/dist/sql-ast/implicit-indexes.d.mts +3 -0
  41. package/dist/sql-ast/implicit-indexes.mjs +66 -0
  42. package/dist/sql-ast/index-metadata.d.mts +19 -0
  43. package/dist/sql-ast/index-metadata.mjs +100 -0
  44. package/dist/sql-ast/index.d.mts +12 -12
  45. package/dist/sql-ast/index.mjs +8 -79
  46. package/dist/sql-ast/line-of-offset.d.mts +1 -0
  47. package/dist/sql-ast/line-of-offset.mjs +12 -0
  48. package/dist/sql-ast/parser.d.mts +11 -0
  49. package/dist/sql-ast/parser.mjs +46 -0
  50. package/dist/sql-ast/unknown-record.d.mts +1 -0
  51. package/dist/sql-ast/unknown-record.mjs +3 -0
  52. package/dist/sql-scanner/index.d.mts +7 -0
  53. package/dist/sql-scanner/index.mjs +172 -0
  54. package/dist/sql-scanner/literals.d.mts +17 -0
  55. package/dist/sql-scanner/literals.mjs +158 -0
  56. package/package.json +12 -2
@@ -0,0 +1,39 @@
1
+ function matchesWorkflowName(name, match) {
2
+ return typeof match === 'string' ? name === match : match.test(name);
3
+ }
4
+ export function parseWorkflowNameMatch(value) {
5
+ if (value.length >= 2 && value.startsWith('/') && value.endsWith('/')) {
6
+ return new RegExp(value.slice(1, -1));
7
+ }
8
+ return value;
9
+ }
10
+ export function resolveRuntimeAuditOptions(options) {
11
+ if (!/^[^/]+\/[^/]+$/.test(options.repository)) {
12
+ throw new Error('Repository must be owner/name');
13
+ }
14
+ if (options.workflows.length === 0) {
15
+ throw new Error('At least one workflow filter is required');
16
+ }
17
+ return {
18
+ repository: options.repository,
19
+ workflows: options.workflows,
20
+ branch: options.branch ?? 'main',
21
+ sampleLimit: options.sampleLimit ?? 5,
22
+ recentCompletedRunHorizon: options.recentCompletedRunHorizon ?? 10,
23
+ medianThresholdSeconds: options.medianThresholdSeconds ?? 360,
24
+ hardCeilingSeconds: options.hardCeilingSeconds ?? 600,
25
+ };
26
+ }
27
+ export function matchingWorkflowFilter(name, filters) {
28
+ return filters.find((filter) => matchesWorkflowName(name, filter.name));
29
+ }
30
+ export function isSelectedWorkflow(workflow, filters) {
31
+ return workflow.state === 'active' && matchingWorkflowFilter(workflow.name, filters) !== undefined;
32
+ }
33
+ export function isRunInScope(run, filter, branch) {
34
+ if (run.event !== filter.event)
35
+ return false;
36
+ if (filter.event === 'pull_request')
37
+ return run.pullRequestBaseBranches.includes(branch);
38
+ return run.headBranch === branch;
39
+ }
package/dist/index.d.mts CHANGED
@@ -1,3 +1,7 @@
1
1
  export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mts';
2
2
  export type { EphemeralListenerOptions, RunnerPortPolicy } from './runner-port-policy/index.mts';
3
- export { extractAlterTableAddColumnLocations, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, } from './sql-ast/index.mts';
3
+ export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mts';
4
+ export type { ForeignKey, SqlCreateIndexMetadata, SqlCreateTableColumn, SqlCreateTableMetadata, SqlDropIndexMetadata, SqlIndexParam, SqlMigrationConstraintMetadata, } from './sql-ast/index.mts';
5
+ export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mts';
6
+ export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/index.mts';
7
+ export type { GhApiExecutor, RuntimeAuditOptions, RuntimeAuditResult, RuntimeAuditWorkflowFilter, RuntimeJobResult, RuntimeSample, } from './gha-runtime-audit/index.mts';
package/dist/index.mjs CHANGED
@@ -1,2 +1,4 @@
1
1
  export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mjs';
2
- export { extractAlterTableAddColumnLocations, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, } from './sql-ast/index.mjs';
2
+ export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mjs';
3
+ export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mjs';
4
+ export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/index.mjs';
@@ -0,0 +1 @@
1
+ export declare function extractAlterTableAddColumnLocations(content: string): number[];
@@ -0,0 +1,22 @@
1
+ import { parseSql } from './parser.mjs';
2
+ export function extractAlterTableAddColumnLocations(content) {
3
+ const locations = [];
4
+ const parseResult = parseSql(content);
5
+ for (const rawStmt of parseResult.stmts ?? []) {
6
+ const node = rawStmt.stmt;
7
+ if (!node || !('AlterTableStmt' in node))
8
+ continue;
9
+ for (const rawCommand of node.AlterTableStmt.cmds ?? []) {
10
+ if (!rawCommand || !('AlterTableCmd' in rawCommand))
11
+ continue;
12
+ const command = rawCommand.AlterTableCmd;
13
+ if (command.subtype !== 'AT_AddColumn')
14
+ continue;
15
+ const definition = command.def;
16
+ if (!definition || !('ColumnDef' in definition))
17
+ continue;
18
+ locations.push(rawStmt.stmt_location ?? 0);
19
+ }
20
+ }
21
+ return locations;
22
+ }
@@ -0,0 +1,11 @@
1
+ import type { AlterTableStmt, DoStmt } from '@libpg-query/parser';
2
+ import { type SqlMigrationConstraintMetadata } from './constraint-shared.mts';
3
+ /**
4
+ * Applies AT_AddConstraint / AT_ValidateConstraint commands from one ALTER TABLE to `result`.
5
+ *
6
+ * `trackConstraintValidation` gates NOT VALID/VALIDATE pairing bookkeeping. Callers that
7
+ * parse ALTER TABLE text out of a DO body should pass false so only `foreignKeys` is filled.
8
+ */
9
+ export declare function processAlterTableStmt(statement: AlterTableStmt, baseOffset: number, result: SqlMigrationConstraintMetadata, trackConstraintValidation?: boolean): void;
10
+ /** Idempotent migrations wrap `ALTER TABLE ... ADD CONSTRAINT` in `DO $$ ... $$` catalog checks. */
11
+ export declare function collectDoStmtConstraints(content: string, statement: DoStmt, stmtLocation: number, result: SqlMigrationConstraintMetadata): void;
@@ -0,0 +1,83 @@
1
+ import { fkAttrNames, foreignKeyFromConstraint, } from './constraint-shared.mjs';
2
+ import { parseSql } from './parser.mjs';
3
+ /**
4
+ * Applies AT_AddConstraint / AT_ValidateConstraint commands from one ALTER TABLE to `result`.
5
+ *
6
+ * `trackConstraintValidation` gates NOT VALID/VALIDATE pairing bookkeeping. Callers that
7
+ * parse ALTER TABLE text out of a DO body should pass false so only `foreignKeys` is filled.
8
+ */
9
+ export function processAlterTableStmt(statement, baseOffset, result, trackConstraintValidation = true) {
10
+ const tableName = statement.relation?.relname;
11
+ /* v8 ignore next */
12
+ if (!tableName)
13
+ return;
14
+ for (const rawCommand of statement.cmds ?? []) {
15
+ /* v8 ignore next */
16
+ if (!rawCommand || !('AlterTableCmd' in rawCommand))
17
+ continue;
18
+ const command = rawCommand.AlterTableCmd;
19
+ if (command.subtype === 'AT_ValidateConstraint' && command.name) {
20
+ if (trackConstraintValidation)
21
+ result.validatedConstraints.add(`${tableName}.${command.name}`);
22
+ continue;
23
+ }
24
+ /* v8 ignore next */
25
+ if (command.subtype !== 'AT_AddConstraint')
26
+ continue;
27
+ const definition = command.def;
28
+ /* v8 ignore next */
29
+ if (!definition || !('Constraint' in definition))
30
+ continue;
31
+ const constraint = definition.Constraint;
32
+ /* v8 ignore next */
33
+ if (!constraint)
34
+ continue;
35
+ if (constraint.contype === 'CONSTR_FOREIGN') {
36
+ const constraintRecord = constraint;
37
+ result.foreignKeys.push(foreignKeyFromConstraint(constraintRecord, tableName, fkAttrNames(constraintRecord), baseOffset));
38
+ }
39
+ if (!trackConstraintValidation)
40
+ continue;
41
+ /* v8 ignore next */
42
+ if (constraint.contype !== 'CONSTR_CHECK' && constraint.contype !== 'CONSTR_FOREIGN')
43
+ continue;
44
+ result.addedConstraints.push({
45
+ /* v8 ignore next 3 */
46
+ constraintType: constraint.contype ?? null,
47
+ location: baseOffset + (constraint.location ?? 0),
48
+ name: constraint.conname ?? null,
49
+ tableName,
50
+ });
51
+ }
52
+ }
53
+ const ALTER_TABLE_STATEMENT_RE = /ALTER\s+TABLE\b[^;]*;/gi;
54
+ /** Idempotent migrations wrap `ALTER TABLE ... ADD CONSTRAINT` in `DO $$ ... $$` catalog checks. */
55
+ /* v8 ignore start -- DO-body regex/parse fallbacks */
56
+ export function collectDoStmtConstraints(content, statement, stmtLocation, result) {
57
+ for (const arg of statement.args ?? []) {
58
+ const defElem = arg && 'DefElem' in arg ? arg.DefElem : undefined;
59
+ const stringArg = defElem?.arg && 'String' in defElem.arg ? defElem.arg.String : undefined;
60
+ const body = typeof stringArg?.sval === 'string' ? stringArg.sval : '';
61
+ for (const match of body.matchAll(ALTER_TABLE_STATEMENT_RE)) {
62
+ const baseOffset = content.indexOf(match[0], stmtLocation);
63
+ /* v8 ignore next */
64
+ if (baseOffset === -1)
65
+ continue;
66
+ let nested;
67
+ try {
68
+ nested = parseSql(match[0]);
69
+ }
70
+ catch {
71
+ /* v8 ignore next */
72
+ continue;
73
+ }
74
+ for (const nestedStmt of nested.stmts ?? []) {
75
+ const node = nestedStmt.stmt;
76
+ if (node && 'AlterTableStmt' in node) {
77
+ processAlterTableStmt(node.AlterTableStmt, baseOffset, result, false);
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+ /* v8 ignore stop */
@@ -0,0 +1,19 @@
1
+ export type SqlMigrationConstraintMetadata = {
2
+ addedConstraints: Array<{
3
+ constraintType: string | null;
4
+ location: number;
5
+ name: string | null;
6
+ tableName: string;
7
+ }>;
8
+ foreignKeys: Array<{
9
+ columnNames: string[];
10
+ deleteAction: string | null;
11
+ location: number;
12
+ referencedTableName: string | null;
13
+ tableName: string | null;
14
+ }>;
15
+ validatedConstraints: Set<string>;
16
+ };
17
+ export type ForeignKey = SqlMigrationConstraintMetadata['foreignKeys'][number];
18
+ export declare function fkAttrNames(constraint: Record<string, unknown>): string[];
19
+ export declare function foreignKeyFromConstraint(constraint: Record<string, unknown>, tableName: string | null, columnNames: string[], baseOffset?: number): ForeignKey;
@@ -0,0 +1,20 @@
1
+ import { isRecord } from './unknown-record.mjs';
2
+ export function fkAttrNames(constraint) {
3
+ const attrs = constraint.fk_attrs;
4
+ if (!Array.isArray(attrs))
5
+ return [];
6
+ return attrs.flatMap((attr) => {
7
+ const sval = isRecord(attr) && isRecord(attr.String) ? attr.String.sval : undefined;
8
+ return typeof sval === 'string' ? [sval] : [];
9
+ });
10
+ }
11
+ export function foreignKeyFromConstraint(constraint, tableName, columnNames, baseOffset = 0) {
12
+ const pktable = constraint.pktable;
13
+ return {
14
+ columnNames,
15
+ deleteAction: typeof constraint.fk_del_action === 'string' ? constraint.fk_del_action : null,
16
+ location: baseOffset + (typeof constraint.location === 'number' ? constraint.location : 0),
17
+ referencedTableName: isRecord(pktable) && typeof pktable.relname === 'string' ? pktable.relname : null,
18
+ tableName,
19
+ };
20
+ }
@@ -0,0 +1,2 @@
1
+ import { type SqlMigrationConstraintMetadata } from './constraint-shared.mts';
2
+ export declare function extractMigrationConstraintMetadata(content: string): SqlMigrationConstraintMetadata;
@@ -0,0 +1,63 @@
1
+ import { collectDoStmtConstraints, processAlterTableStmt } from './constraint-do-block.mjs';
2
+ import { fkAttrNames, foreignKeyFromConstraint, } from './constraint-shared.mjs';
3
+ import { parseSql } from './parser.mjs';
4
+ import { isRecord } from './unknown-record.mjs';
5
+ /** Inline `col UUID REFERENCES ...` and table-level `FOREIGN KEY (...) REFERENCES ...` forms. */
6
+ /* v8 ignore start -- defensive parse-tree walks */
7
+ function collectCreateStmtForeignKeys(statement, foreignKeys) {
8
+ const relation = statement.relation;
9
+ const tableName = isRecord(relation) && typeof relation.relname === 'string' ? relation.relname : null;
10
+ const tableElts = statement.tableElts;
11
+ if (!Array.isArray(tableElts))
12
+ return;
13
+ for (const element of tableElts) {
14
+ if (!isRecord(element))
15
+ continue;
16
+ if (isRecord(element.ColumnDef)) {
17
+ const column = element.ColumnDef;
18
+ const colname = typeof column.colname === 'string' ? column.colname : null;
19
+ for (const constraintNode of column.constraints ?? []) {
20
+ /* v8 ignore next 3 */
21
+ if (!isRecord(constraintNode) || !isRecord(constraintNode.Constraint))
22
+ continue;
23
+ const constraint = constraintNode.Constraint;
24
+ if (constraint.contype !== 'CONSTR_FOREIGN')
25
+ continue;
26
+ foreignKeys.push(foreignKeyFromConstraint(constraint, tableName, colname ? [colname] : []));
27
+ }
28
+ continue;
29
+ }
30
+ if (isRecord(element.Constraint) && element.Constraint.contype === 'CONSTR_FOREIGN') {
31
+ foreignKeys.push(foreignKeyFromConstraint(element.Constraint, tableName, fkAttrNames(element.Constraint)));
32
+ }
33
+ }
34
+ }
35
+ /* v8 ignore stop */
36
+ export function extractMigrationConstraintMetadata(content) {
37
+ const result = {
38
+ addedConstraints: [],
39
+ foreignKeys: [],
40
+ validatedConstraints: new Set(),
41
+ };
42
+ const parseResult = parseSql(content);
43
+ for (const rawStmt of parseResult.stmts ?? []) {
44
+ const node = rawStmt.stmt;
45
+ /* v8 ignore next */
46
+ if (!node)
47
+ continue;
48
+ const stmtLocation = rawStmt.stmt_location ?? 0;
49
+ if ('CreateStmt' in node) {
50
+ collectCreateStmtForeignKeys(node.CreateStmt, result.foreignKeys);
51
+ continue;
52
+ }
53
+ if ('DoStmt' in node) {
54
+ collectDoStmtConstraints(content, node.DoStmt, stmtLocation, result);
55
+ continue;
56
+ }
57
+ /* v8 ignore next */
58
+ if (!('AlterTableStmt' in node))
59
+ continue;
60
+ processAlterTableStmt(node.AlterTableStmt, 0, result);
61
+ }
62
+ return result;
63
+ }
@@ -0,0 +1,25 @@
1
+ export type SqlCreateTableColumnConstraint = {
2
+ contype: string | null;
3
+ };
4
+ export type SqlCreateTableColumn = {
5
+ name: string;
6
+ location: number | null;
7
+ constraints: SqlCreateTableColumnConstraint[];
8
+ isPrimaryKey: boolean;
9
+ defaultFunction: string | null;
10
+ /** Function name of a CONSTR_GENERATED expression (e.g. `uuid_extract_timestamp`), null if not generated or unrecognized. */
11
+ generatedFunction: string | null;
12
+ /** Lowercased argument column names of a CONSTR_GENERATED expression's function call (e.g. `['id']`). */
13
+ generatedFunctionArgColumns: string[];
14
+ };
15
+ export type SqlCreateTableMetadata = {
16
+ tableName: string;
17
+ columns: SqlCreateTableColumn[];
18
+ };
19
+ /**
20
+ * A column is a primary key via either the inline `col TYPE PRIMARY KEY` constraint
21
+ * (nested inside its ColumnDef) or the table-level `PRIMARY KEY (col)` constraint
22
+ * (a bare `Constraint` sibling in tableElts, referencing the column by name in `keys`).
23
+ * Both forms are checked so table-level-PK migrations aren't silently mis-detected.
24
+ */
25
+ export declare function extractCreateTableMetadata(content: string): SqlCreateTableMetadata[];
@@ -0,0 +1,79 @@
1
+ import { extractDefaultFunction, extractFuncCallArgColumnNames } from './default-function.mjs';
2
+ import { parseSql } from './parser.mjs';
3
+ /**
4
+ * A column is a primary key via either the inline `col TYPE PRIMARY KEY` constraint
5
+ * (nested inside its ColumnDef) or the table-level `PRIMARY KEY (col)` constraint
6
+ * (a bare `Constraint` sibling in tableElts, referencing the column by name in `keys`).
7
+ * Both forms are checked so table-level-PK migrations aren't silently mis-detected.
8
+ */
9
+ export function extractCreateTableMetadata(content) {
10
+ const tables = [];
11
+ const parseResult = parseSql(content);
12
+ for (const rawStmt of parseResult.stmts ?? []) {
13
+ const node = rawStmt.stmt;
14
+ if (!node || !('CreateStmt' in node))
15
+ continue;
16
+ const createStmt = node.CreateStmt;
17
+ const tableName = createStmt.relation?.relname;
18
+ if (!tableName)
19
+ continue;
20
+ const tableLevelPrimaryKeyColumns = new Set((createStmt.tableElts ?? []).flatMap((elt) => {
21
+ /* v8 ignore next 8 */
22
+ if (!elt || typeof elt !== 'object' || !('Constraint' in elt))
23
+ return [];
24
+ const constraint = elt.Constraint;
25
+ if (constraint.contype !== 'CONSTR_PRIMARY')
26
+ return [];
27
+ return (constraint.keys ?? []).flatMap((key) => key && typeof key === 'object' && 'String' in key && typeof key.String.sval === 'string'
28
+ ? [key.String.sval]
29
+ : []);
30
+ }));
31
+ tables.push({
32
+ tableName,
33
+ columns: (createStmt.tableElts ?? []).flatMap((elt) => {
34
+ if (!elt || typeof elt !== 'object' || !('ColumnDef' in elt))
35
+ return [];
36
+ const column = elt.ColumnDef;
37
+ if (!column || typeof column !== 'object' || !column.colname)
38
+ return [];
39
+ const rawConstraints = (column.constraints ?? []).flatMap((constraint) => {
40
+ /* v8 ignore next 4 */
41
+ if (!constraint || typeof constraint !== 'object' || !('Constraint' in constraint)) {
42
+ return [];
43
+ }
44
+ return constraint.Constraint ? [constraint.Constraint] : [];
45
+ });
46
+ let defaultConstraint;
47
+ let generatedConstraint;
48
+ for (const constraint of rawConstraints) {
49
+ if (constraint.contype === 'CONSTR_DEFAULT')
50
+ defaultConstraint ??= constraint;
51
+ if (constraint.contype === 'CONSTR_GENERATED')
52
+ generatedConstraint ??= constraint;
53
+ }
54
+ return [
55
+ {
56
+ name: column.colname,
57
+ location: column.location ?? null,
58
+ constraints: rawConstraints.map((constraint) => ({
59
+ /* v8 ignore next */
60
+ contype: constraint.contype ?? null,
61
+ })),
62
+ isPrimaryKey: rawConstraints.some((constraint) => constraint.contype === 'CONSTR_PRIMARY') ||
63
+ tableLevelPrimaryKeyColumns.has(column.colname),
64
+ defaultFunction: defaultConstraint
65
+ ? extractDefaultFunction(defaultConstraint.raw_expr)
66
+ : null,
67
+ generatedFunction: generatedConstraint
68
+ ? extractDefaultFunction(generatedConstraint.raw_expr)
69
+ : null,
70
+ generatedFunctionArgColumns: generatedConstraint
71
+ ? extractFuncCallArgColumnNames(generatedConstraint.raw_expr)
72
+ : [],
73
+ },
74
+ ];
75
+ }),
76
+ });
77
+ }
78
+ return tables;
79
+ }
@@ -0,0 +1,18 @@
1
+ import type { Node } from '@libpg-query/parser';
2
+ /**
3
+ * Resolves a CONSTR_DEFAULT constraint's raw_expr to a lowercase function/op name.
4
+ * Handles the shapes @libpg-query/parser produces for column defaults:
5
+ * - `FuncCall` (e.g. `uuidv7()`, `now()`): last segment of `funcname`, lowercased.
6
+ * - `SQLValueFunction` (e.g. bare `CURRENT_TIMESTAMP`): mapped from its `op` enum.
7
+ * - `TypeCast` (e.g. `now()::timestamptz`): recurses into the cast's inner arg.
8
+ * Returns null for any other/unrecognized raw_expr shape.
9
+ */
10
+ export declare function extractDefaultFunction(rawExpr: Node | undefined): string | null;
11
+ /**
12
+ * Resolves a FuncCall raw_expr's argument column names, lowercased (e.g. the `id` in
13
+ * `uuid_extract_timestamp(id)`). Used to verify a GENERATED column expression references
14
+ * a specific source column, not just that it calls the expected function.
15
+ * Returns an empty array for non-FuncCall shapes, a TypeCast-wrapped call recurses into
16
+ * its inner arg; non-column-ref args are skipped.
17
+ */
18
+ export declare function extractFuncCallArgColumnNames(rawExpr: Node | undefined): string[];
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Resolves a CONSTR_DEFAULT constraint's raw_expr to a lowercase function/op name.
3
+ * Handles the shapes @libpg-query/parser produces for column defaults:
4
+ * - `FuncCall` (e.g. `uuidv7()`, `now()`): last segment of `funcname`, lowercased.
5
+ * - `SQLValueFunction` (e.g. bare `CURRENT_TIMESTAMP`): mapped from its `op` enum.
6
+ * - `TypeCast` (e.g. `now()::timestamptz`): recurses into the cast's inner arg.
7
+ * Returns null for any other/unrecognized raw_expr shape.
8
+ */
9
+ export function extractDefaultFunction(rawExpr) {
10
+ if (!rawExpr)
11
+ return null;
12
+ if ('TypeCast' in rawExpr) {
13
+ return extractDefaultFunction(rawExpr.TypeCast.arg);
14
+ }
15
+ if ('FuncCall' in rawExpr) {
16
+ const funcname = rawExpr.FuncCall.funcname ?? [];
17
+ const last = funcname.at(-1);
18
+ if (last && 'String' in last && typeof last.String.sval === 'string') {
19
+ return last.String.sval.toLowerCase();
20
+ }
21
+ return null;
22
+ }
23
+ if ('SQLValueFunction' in rawExpr) {
24
+ return rawExpr.SQLValueFunction.op === 'SVFOP_CURRENT_TIMESTAMP' ? 'current_timestamp' : null;
25
+ }
26
+ return null;
27
+ }
28
+ /**
29
+ * Resolves a FuncCall raw_expr's argument column names, lowercased (e.g. the `id` in
30
+ * `uuid_extract_timestamp(id)`). Used to verify a GENERATED column expression references
31
+ * a specific source column, not just that it calls the expected function.
32
+ * Returns an empty array for non-FuncCall shapes, a TypeCast-wrapped call recurses into
33
+ * its inner arg; non-column-ref args are skipped.
34
+ */
35
+ export function extractFuncCallArgColumnNames(rawExpr) {
36
+ if (!rawExpr)
37
+ return [];
38
+ if ('TypeCast' in rawExpr) {
39
+ return extractFuncCallArgColumnNames(rawExpr.TypeCast.arg);
40
+ }
41
+ if (!('FuncCall' in rawExpr))
42
+ return [];
43
+ return (rawExpr.FuncCall.args ?? []).flatMap((arg) => {
44
+ if (!arg || typeof arg !== 'object' || !('ColumnRef' in arg))
45
+ return [];
46
+ const fields = arg.ColumnRef.fields ?? [];
47
+ const last = fields.at(-1);
48
+ /* v8 ignore next 4 */
49
+ return last && 'String' in last && typeof last.String.sval === 'string'
50
+ ? [last.String.sval.toLowerCase()]
51
+ : [];
52
+ });
53
+ }
@@ -0,0 +1,5 @@
1
+ export type SqlDropIndexMetadata = {
2
+ idxname: string;
3
+ location: number;
4
+ };
5
+ export declare function extractDropIndexMetadata(content: string): SqlDropIndexMetadata[];
@@ -0,0 +1,31 @@
1
+ import { parseSql } from './parser.mjs';
2
+ import { isRecord } from './unknown-record.mjs';
3
+ export function extractDropIndexMetadata(content) {
4
+ const indexes = [];
5
+ const parseResult = parseSql(content);
6
+ for (const rawStmt of parseResult.stmts ?? []) {
7
+ const node = rawStmt.stmt;
8
+ /* v8 ignore next 2 */
9
+ if (!node || !('DropStmt' in node) || node.DropStmt.removeType !== 'OBJECT_INDEX')
10
+ continue;
11
+ for (const object of node.DropStmt.objects ?? []) {
12
+ const objectValue = object;
13
+ const items = isRecord(objectValue) && isRecord(objectValue['List'])
14
+ ? objectValue['List']['items']
15
+ : undefined;
16
+ if (!Array.isArray(items))
17
+ continue;
18
+ const names = items.flatMap((item) => {
19
+ const itemValue = item;
20
+ const value = isRecord(itemValue) && isRecord(itemValue['String'])
21
+ ? itemValue['String']['sval']
22
+ : undefined;
23
+ return typeof value === 'string' ? [value] : [];
24
+ });
25
+ const idxname = names.at(-1);
26
+ if (idxname)
27
+ indexes.push({ idxname, location: rawStmt.stmt_location ?? 0 });
28
+ }
29
+ }
30
+ return indexes;
31
+ }
@@ -0,0 +1,3 @@
1
+ import type { SqlCreateIndexMetadata } from './index-metadata.mts';
2
+ /** UNIQUE/PRIMARY KEY create implicit unique indexes that never appear as CREATE INDEX. */
3
+ export declare function collectCreateStmtImplicitIndexes(statement: Record<string, unknown>, location: number, indexes: SqlCreateIndexMetadata[]): void;
@@ -0,0 +1,66 @@
1
+ import { isRecord } from './unknown-record.mjs';
2
+ function implicitIndexParam(name) {
3
+ return { name, opclass: null, ordering: null, nullsOrdering: null };
4
+ }
5
+ function pushImplicitIndex(indexes, relname, location, columnNames) {
6
+ indexes.push({
7
+ relname,
8
+ idxname: null,
9
+ unique: true,
10
+ indexParams: columnNames,
11
+ indexParamDetails: columnNames.map(implicitIndexParam),
12
+ includeParams: [],
13
+ whereClause: null,
14
+ whereClauseKey: null,
15
+ accessMethod: 'btree',
16
+ location,
17
+ });
18
+ }
19
+ function constraintKeyNames(constraint) {
20
+ const keys = constraint.keys;
21
+ if (!Array.isArray(keys))
22
+ return [];
23
+ return keys.flatMap((key) => {
24
+ const sval = isRecord(key) && isRecord(key.String) ? key.String.sval : undefined;
25
+ /* v8 ignore next */
26
+ return typeof sval === 'string' ? [sval] : [];
27
+ });
28
+ }
29
+ /** UNIQUE/PRIMARY KEY create implicit unique indexes that never appear as CREATE INDEX. */
30
+ export function collectCreateStmtImplicitIndexes(statement, location, indexes) {
31
+ const relation = statement.relation;
32
+ const relname = isRecord(relation) && typeof relation.relname === 'string' ? relation.relname : null;
33
+ const tableElts = statement.tableElts;
34
+ if (!relname || !Array.isArray(tableElts))
35
+ return;
36
+ for (const element of tableElts) {
37
+ if (!isRecord(element))
38
+ continue;
39
+ if (isRecord(element.ColumnDef)) {
40
+ const column = element.ColumnDef;
41
+ const colname = typeof column.colname === 'string' ? column.colname : null;
42
+ if (!colname)
43
+ continue;
44
+ for (const constraintNode of column.constraints ?? []) {
45
+ /* v8 ignore next */
46
+ if (!isRecord(constraintNode) || !isRecord(constraintNode.Constraint))
47
+ continue;
48
+ const contype = constraintNode.Constraint.contype;
49
+ if (contype !== 'CONSTR_UNIQUE' && contype !== 'CONSTR_PRIMARY')
50
+ continue;
51
+ pushImplicitIndex(indexes, relname, location, [colname]);
52
+ }
53
+ continue;
54
+ }
55
+ /* v8 ignore next */
56
+ if (!isRecord(element.Constraint))
57
+ continue;
58
+ const contype = element.Constraint.contype;
59
+ if (contype !== 'CONSTR_UNIQUE' && contype !== 'CONSTR_PRIMARY')
60
+ continue;
61
+ const columnNames = constraintKeyNames(element.Constraint);
62
+ if (columnNames.length === 0)
63
+ continue;
64
+ pushImplicitIndex(indexes, relname, location, columnNames);
65
+ }
66
+ }
@@ -0,0 +1,19 @@
1
+ export type SqlIndexParam = {
2
+ name: string | null;
3
+ opclass: string | null;
4
+ ordering: string | null;
5
+ nullsOrdering: string | null;
6
+ };
7
+ export type SqlCreateIndexMetadata = {
8
+ relname: string;
9
+ idxname: string | null;
10
+ unique: boolean;
11
+ indexParams: Array<string | null>;
12
+ indexParamDetails: SqlIndexParam[];
13
+ includeParams: string[];
14
+ whereClause: unknown;
15
+ whereClauseKey: string | null;
16
+ accessMethod: string;
17
+ location: number;
18
+ };
19
+ export declare function extractCreateIndexMetadata(content: string): SqlCreateIndexMetadata[];