@memberjunction/cli 5.37.0 → 5.38.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/README.md +74 -0
- package/dist/baseline/cli-helpers.d.ts +12 -0
- package/dist/baseline/cli-helpers.d.ts.map +1 -0
- package/dist/baseline/cli-helpers.js +40 -0
- package/dist/baseline/cli-helpers.js.map +1 -0
- package/dist/baseline/comparator.d.ts +26 -0
- package/dist/baseline/comparator.d.ts.map +1 -0
- package/dist/baseline/comparator.js +527 -0
- package/dist/baseline/comparator.js.map +1 -0
- package/dist/baseline/connection.d.ts +39 -0
- package/dist/baseline/connection.d.ts.map +1 -0
- package/dist/baseline/connection.js +104 -0
- package/dist/baseline/connection.js.map +1 -0
- package/dist/baseline/data-dumper.d.ts +23 -0
- package/dist/baseline/data-dumper.d.ts.map +1 -0
- package/dist/baseline/data-dumper.js +94 -0
- package/dist/baseline/data-dumper.js.map +1 -0
- package/dist/baseline/emitter.d.ts +36 -0
- package/dist/baseline/emitter.d.ts.map +1 -0
- package/dist/baseline/emitter.js +512 -0
- package/dist/baseline/emitter.js.map +1 -0
- package/dist/baseline/index.d.ts +18 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/baseline/index.js +14 -0
- package/dist/baseline/index.js.map +1 -0
- package/dist/baseline/introspector-mssql.d.ts +15 -0
- package/dist/baseline/introspector-mssql.d.ts.map +1 -0
- package/dist/baseline/introspector-mssql.js +673 -0
- package/dist/baseline/introspector-mssql.js.map +1 -0
- package/dist/baseline/introspector-postgres.d.ts +14 -0
- package/dist/baseline/introspector-postgres.d.ts.map +1 -0
- package/dist/baseline/introspector-postgres.js +277 -0
- package/dist/baseline/introspector-postgres.js.map +1 -0
- package/dist/baseline/report.d.ts +7 -0
- package/dist/baseline/report.d.ts.map +1 -0
- package/dist/baseline/report.js +105 -0
- package/dist/baseline/report.js.map +1 -0
- package/dist/baseline/types.d.ts +286 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +10 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/baseline/util.d.ts +112 -0
- package/dist/baseline/util.d.ts.map +1 -0
- package/dist/baseline/util.js +325 -0
- package/dist/baseline/util.js.map +1 -0
- package/dist/commands/baseline/build.d.ts +29 -0
- package/dist/commands/baseline/build.d.ts.map +1 -0
- package/dist/commands/baseline/build.js +188 -0
- package/dist/commands/baseline/build.js.map +1 -0
- package/dist/commands/baseline/compare.d.ts +19 -0
- package/dist/commands/baseline/compare.d.ts.map +1 -0
- package/dist/commands/baseline/compare.js +133 -0
- package/dist/commands/baseline/compare.js.map +1 -0
- package/dist/commands/baseline/roundtrip.d.ts +23 -0
- package/dist/commands/baseline/roundtrip.d.ts.map +1 -0
- package/dist/commands/baseline/roundtrip.js +245 -0
- package/dist/commands/baseline/roundtrip.js.map +1 -0
- package/dist/commands/test/compare.d.ts +3 -0
- package/dist/commands/test/compare.d.ts.map +1 -1
- package/dist/commands/test/compare.js +23 -3
- package/dist/commands/test/compare.js.map +1 -1
- package/dist/commands/test/regression/build.d.ts +14 -0
- package/dist/commands/test/regression/build.d.ts.map +1 -0
- package/dist/commands/test/regression/build.js +48 -0
- package/dist/commands/test/regression/build.js.map +1 -0
- package/dist/commands/test/regression/compare.d.ts +15 -0
- package/dist/commands/test/regression/compare.d.ts.map +1 -0
- package/dist/commands/test/regression/compare.js +66 -0
- package/dist/commands/test/regression/compare.js.map +1 -0
- package/dist/commands/test/regression/down.d.ts +10 -0
- package/dist/commands/test/regression/down.d.ts.map +1 -0
- package/dist/commands/test/regression/down.js +29 -0
- package/dist/commands/test/regression/down.js.map +1 -0
- package/dist/commands/test/regression/export.d.ts +11 -0
- package/dist/commands/test/regression/export.d.ts.map +1 -0
- package/dist/commands/test/regression/export.js +56 -0
- package/dist/commands/test/regression/export.js.map +1 -0
- package/dist/commands/test/regression/gen-forms.d.ts +7 -0
- package/dist/commands/test/regression/gen-forms.d.ts.map +1 -0
- package/dist/commands/test/regression/gen-forms.js +18 -0
- package/dist/commands/test/regression/gen-forms.js.map +1 -0
- package/dist/commands/test/regression/index.d.ts +7 -0
- package/dist/commands/test/regression/index.d.ts.map +1 -0
- package/dist/commands/test/regression/index.js +33 -0
- package/dist/commands/test/regression/index.js.map +1 -0
- package/dist/commands/test/regression/init.d.ts +21 -0
- package/dist/commands/test/regression/init.d.ts.map +1 -0
- package/dist/commands/test/regression/init.js +111 -0
- package/dist/commands/test/regression/init.js.map +1 -0
- package/dist/commands/test/regression/remote.d.ts +23 -0
- package/dist/commands/test/regression/remote.d.ts.map +1 -0
- package/dist/commands/test/regression/remote.js +140 -0
- package/dist/commands/test/regression/remote.js.map +1 -0
- package/dist/commands/test/regression/up.d.ts +21 -0
- package/dist/commands/test/regression/up.d.ts.map +1 -0
- package/dist/commands/test/regression/up.js +111 -0
- package/dist/commands/test/regression/up.js.map +1 -0
- package/dist/commands/test/run.d.ts +1 -0
- package/dist/commands/test/run.d.ts.map +1 -1
- package/dist/commands/test/run.js +6 -0
- package/dist/commands/test/run.js.map +1 -1
- package/dist/commands/test/suite.d.ts +5 -0
- package/dist/commands/test/suite.d.ts.map +1 -1
- package/dist/commands/test/suite.js +27 -0
- package/dist/commands/test/suite.js.map +1 -1
- package/dist/commands/translate-sql/index.d.ts.map +1 -1
- package/dist/commands/translate-sql/index.js +3 -2
- package/dist/commands/translate-sql/index.js.map +1 -1
- package/dist/init-templates/bring-your-own-app/README.md +74 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/.mj-sync.json +10 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.byo-suite.json +51 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.mj-sync.json +12 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T01-counter-increment.json +63 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T02-contact-form-submit.json +63 -0
- package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.mj-sync.json +12 -0
- package/dist/init-templates/bring-your-own-app/docker-compose.app.yml +76 -0
- package/dist/init-templates/bring-your-own-app/target.json +23 -0
- package/dist/init-templates/generic-web/README.md +115 -0
- package/dist/init-templates/generic-web/metadata/.mj-sync.json +10 -0
- package/dist/init-templates/generic-web/metadata/test-suites/.generic-web-suite.json +48 -0
- package/dist/init-templates/generic-web/metadata/test-suites/.mj-sync.json +12 -0
- package/dist/init-templates/generic-web/metadata/tests/.GW01-home-loads.json +74 -0
- package/dist/init-templates/generic-web/metadata/tests/.GW02-search-array-map.json +82 -0
- package/dist/init-templates/generic-web/metadata/tests/.mj-sync.json +12 -0
- package/dist/init-templates/generic-web/oracles/mdn-oracles.cjs +121 -0
- package/dist/init-templates/generic-web/target.json +25 -0
- package/dist/init-templates/github-actions/README.md +26 -0
- package/dist/init-templates/github-actions/regression.yml +52 -0
- package/dist/init-templates/remote-mj/README.md +95 -0
- package/dist/init-templates/remote-mj/target.json +22 -0
- package/dist/init-templates/static-file-server/Dockerfile +23 -0
- package/dist/init-templates/static-file-server/README.md +88 -0
- package/dist/init-templates/static-file-server/docker-compose.app.yml +44 -0
- package/dist/init-templates/static-file-server/metadata/.mj-sync.json +10 -0
- package/dist/init-templates/static-file-server/metadata/test-suites/.mj-sync.json +12 -0
- package/dist/init-templates/static-file-server/metadata/test-suites/.static-server-suite.json +27 -0
- package/dist/init-templates/static-file-server/metadata/tests/.SS01-home-loads.json +45 -0
- package/dist/init-templates/static-file-server/metadata/tests/.SS02-navigate-about.json +45 -0
- package/dist/init-templates/static-file-server/metadata/tests/.mj-sync.json +12 -0
- package/dist/init-templates/static-file-server/public/about.html +12 -0
- package/dist/init-templates/static-file-server/public/index.html +27 -0
- package/dist/init-templates/static-file-server/target.json +16 -0
- package/dist/lib/regression/docker-helpers.d.ts +98 -0
- package/dist/lib/regression/docker-helpers.d.ts.map +1 -0
- package/dist/lib/regression/docker-helpers.js +184 -0
- package/dist/lib/regression/docker-helpers.js.map +1 -0
- package/oclif.manifest.json +1356 -509
- package/package.json +22 -17
- package/regression-compose/docker-compose.bacpac-standalone.yml +109 -0
- package/regression-compose/docker-compose.standalone.yml +50 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MSSQL introspector.
|
|
3
|
+
*
|
|
4
|
+
* Returns a normalized SchemaSnapshot. All object bodies (views, procs,
|
|
5
|
+
* functions, triggers) are pulled via OBJECT_DEFINITION() so they are
|
|
6
|
+
* byte-faithful to what the database actually has — no AST round-trip.
|
|
7
|
+
*/
|
|
8
|
+
import { isExcludedTable, stableSortBy } from './util.js';
|
|
9
|
+
export async function introspectMssql(db, progress = {}) {
|
|
10
|
+
progress.onPhase?.('schemas');
|
|
11
|
+
const schemas = await db.query(`
|
|
12
|
+
SELECT s.name
|
|
13
|
+
FROM sys.schemas s
|
|
14
|
+
WHERE s.name NOT IN ('sys', 'INFORMATION_SCHEMA', 'guest', 'db_owner', 'db_accessadmin',
|
|
15
|
+
'db_securityadmin', 'db_ddladmin', 'db_backupoperator', 'db_datareader',
|
|
16
|
+
'db_datawriter', 'db_denydatareader', 'db_denydatawriter')
|
|
17
|
+
`);
|
|
18
|
+
progress.onPhase?.('tables');
|
|
19
|
+
const tables = await readTables(db);
|
|
20
|
+
progress.onPhase?.('views');
|
|
21
|
+
const views = await readViews(db);
|
|
22
|
+
progress.onPhase?.('routines');
|
|
23
|
+
const procedures = await readRoutines(db, 'P');
|
|
24
|
+
const functions = await readRoutines(db, 'FN_TF_IF');
|
|
25
|
+
progress.onPhase?.('triggers');
|
|
26
|
+
const triggers = await readTriggers(db);
|
|
27
|
+
progress.onPhase?.('sequences');
|
|
28
|
+
const sequences = await readSequences(db);
|
|
29
|
+
progress.onPhase?.('userDefinedTypes');
|
|
30
|
+
const userDefinedTypes = await readUserDefinedTypes(db);
|
|
31
|
+
progress.onPhase?.('extendedProperties');
|
|
32
|
+
const extendedProperties = await readExtendedProperties(db);
|
|
33
|
+
progress.onPhase?.('principals');
|
|
34
|
+
const principals = await readPrincipals(db);
|
|
35
|
+
progress.onPhase?.('roleMemberships');
|
|
36
|
+
const roleMemberships = await readRoleMemberships(db);
|
|
37
|
+
progress.onPhase?.('permissions');
|
|
38
|
+
const permissions = await readPermissions(db);
|
|
39
|
+
return {
|
|
40
|
+
dialect: 'mssql',
|
|
41
|
+
schemas: stableSortBy(schemas, (s) => s.name.toLowerCase()),
|
|
42
|
+
tables: stableSortBy(tables, (t) => `${t.schema}.${t.name}`.toLowerCase()),
|
|
43
|
+
views: stableSortBy(views, (v) => `${v.schema}.${v.name}`.toLowerCase()),
|
|
44
|
+
procedures: stableSortBy(procedures, (r) => `${r.schema}.${r.name}`.toLowerCase()),
|
|
45
|
+
functions: stableSortBy(functions, (r) => `${r.schema}.${r.name}`.toLowerCase()),
|
|
46
|
+
triggers: stableSortBy(triggers, (t) => `${t.schema}.${t.name}`.toLowerCase()),
|
|
47
|
+
sequences: stableSortBy(sequences, (s) => `${s.schema}.${s.name}`.toLowerCase()),
|
|
48
|
+
userDefinedTypes: stableSortBy(userDefinedTypes, (u) => `${u.schema}.${u.name}`.toLowerCase()),
|
|
49
|
+
extendedProperties: stableSortBy(extendedProperties, (p) => extPropSortKey(p)),
|
|
50
|
+
principals: stableSortBy(principals, (p) => `${p.kind}|${p.name}`.toLowerCase()),
|
|
51
|
+
roleMemberships: stableSortBy(roleMemberships, (m) => `${m.role}|${m.member}`.toLowerCase()),
|
|
52
|
+
permissions: stableSortBy(permissions, (p) => permissionSortKey(p)),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** Canonical sort key for permissions so the emitted GRANT block is byte-deterministic. */
|
|
56
|
+
function permissionSortKey(p) {
|
|
57
|
+
return [
|
|
58
|
+
p.grantee.toLowerCase(),
|
|
59
|
+
p.targetClass,
|
|
60
|
+
(p.targetSchema || '').toLowerCase(),
|
|
61
|
+
(p.targetObject || p.targetType || '').toLowerCase(),
|
|
62
|
+
(p.targetColumn || '').toLowerCase(),
|
|
63
|
+
p.permission.toLowerCase(),
|
|
64
|
+
p.state,
|
|
65
|
+
].join('|');
|
|
66
|
+
}
|
|
67
|
+
/** Canonical sort key for extended properties so output stays byte-deterministic. */
|
|
68
|
+
function extPropSortKey(p) {
|
|
69
|
+
return [
|
|
70
|
+
p.schemaName.toLowerCase(),
|
|
71
|
+
(p.level1Type || '').toLowerCase(),
|
|
72
|
+
(p.level1Name || '').toLowerCase(),
|
|
73
|
+
(p.level2Type || '').toLowerCase(),
|
|
74
|
+
(p.level2Name || '').toLowerCase(),
|
|
75
|
+
p.name.toLowerCase(),
|
|
76
|
+
].join('|');
|
|
77
|
+
}
|
|
78
|
+
async function readTables(db) {
|
|
79
|
+
// Pull tables in one round-trip then enrich with columns/PKs/indexes/FKs/checks.
|
|
80
|
+
const tableRows = await db.query(`
|
|
81
|
+
SELECT s.name AS schema_name,
|
|
82
|
+
t.name AS table_name,
|
|
83
|
+
CAST(CASE WHEN EXISTS (
|
|
84
|
+
SELECT 1 FROM sys.columns c WHERE c.object_id = t.object_id AND c.is_identity = 1
|
|
85
|
+
) THEN 1 ELSE 0 END AS INT) AS has_identity
|
|
86
|
+
FROM sys.tables t
|
|
87
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
88
|
+
WHERE t.is_ms_shipped = 0
|
|
89
|
+
`);
|
|
90
|
+
const columnRows = await db.query(`
|
|
91
|
+
SELECT s.name AS schema_name, t.name AS table_name, c.name AS column_name,
|
|
92
|
+
c.column_id AS ordinal,
|
|
93
|
+
ty.name AS data_type, c.max_length, c.precision, c.scale,
|
|
94
|
+
c.is_nullable, c.is_identity, c.is_computed,
|
|
95
|
+
cc.definition AS computed_definition,
|
|
96
|
+
CAST(cc.is_persisted AS INT) AS computed_is_persisted,
|
|
97
|
+
dc.definition AS default_definition,
|
|
98
|
+
dc.name AS default_name,
|
|
99
|
+
c.collation_name AS collation
|
|
100
|
+
FROM sys.tables t
|
|
101
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
102
|
+
JOIN sys.columns c ON c.object_id = t.object_id
|
|
103
|
+
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
|
104
|
+
LEFT JOIN sys.computed_columns cc ON cc.object_id = c.object_id AND cc.column_id = c.column_id
|
|
105
|
+
LEFT JOIN sys.default_constraints dc ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
|
|
106
|
+
WHERE t.is_ms_shipped = 0
|
|
107
|
+
`);
|
|
108
|
+
const pkRows = await db.query(`
|
|
109
|
+
SELECT s.name AS schema_name, t.name AS table_name, kc.name AS constraint_name,
|
|
110
|
+
c.name AS column_name, ic.key_ordinal,
|
|
111
|
+
CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered,
|
|
112
|
+
CAST(kc.type_desc AS VARCHAR(64)) AS type_desc_raw,
|
|
113
|
+
CAST(CASE WHEN kc.type = 'PK' THEN 1 ELSE 0 END AS INT) AS is_primary,
|
|
114
|
+
CAST(CASE WHEN kc.type = 'UQ' THEN 1 ELSE 0 END AS INT) AS is_unique
|
|
115
|
+
FROM sys.key_constraints kc
|
|
116
|
+
JOIN sys.tables t ON t.object_id = kc.parent_object_id
|
|
117
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
118
|
+
JOIN sys.indexes i ON i.object_id = kc.parent_object_id AND i.index_id = kc.unique_index_id
|
|
119
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
120
|
+
JOIN sys.columns c ON c.object_id = i.object_id AND c.column_id = ic.column_id
|
|
121
|
+
WHERE t.is_ms_shipped = 0
|
|
122
|
+
`);
|
|
123
|
+
const indexRows = await db.query(`
|
|
124
|
+
SELECT s.name AS schema_name, t.name AS table_name, i.name AS index_name,
|
|
125
|
+
CAST(i.is_unique AS INT) AS is_unique,
|
|
126
|
+
CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered,
|
|
127
|
+
CAST(CASE WHEN i.is_primary_key = 1 OR i.is_unique_constraint = 1 THEN 1 ELSE 0 END AS INT) AS is_constraint,
|
|
128
|
+
c.name AS column_name, ic.key_ordinal,
|
|
129
|
+
CAST(ic.is_included_column AS INT) AS is_included,
|
|
130
|
+
i.filter_definition
|
|
131
|
+
FROM sys.indexes i
|
|
132
|
+
JOIN sys.tables t ON t.object_id = i.object_id
|
|
133
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
134
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
135
|
+
JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
|
|
136
|
+
WHERE t.is_ms_shipped = 0 AND i.name IS NOT NULL
|
|
137
|
+
`);
|
|
138
|
+
const fkRows = await db.query(`
|
|
139
|
+
SELECT ps.name AS schema_name, pt.name AS table_name, fk.name AS constraint_name,
|
|
140
|
+
pc.name AS column_name,
|
|
141
|
+
rs.name AS ref_schema, rt.name AS ref_table, rc.name AS ref_column,
|
|
142
|
+
fk.delete_referential_action_desc AS on_delete,
|
|
143
|
+
fk.update_referential_action_desc AS on_update,
|
|
144
|
+
fkc.constraint_column_id AS key_index
|
|
145
|
+
FROM sys.foreign_keys fk
|
|
146
|
+
JOIN sys.tables pt ON pt.object_id = fk.parent_object_id
|
|
147
|
+
JOIN sys.schemas ps ON ps.schema_id = pt.schema_id
|
|
148
|
+
JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id
|
|
149
|
+
JOIN sys.schemas rs ON rs.schema_id = rt.schema_id
|
|
150
|
+
JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
|
|
151
|
+
JOIN sys.columns pc ON pc.object_id = fkc.parent_object_id AND pc.column_id = fkc.parent_column_id
|
|
152
|
+
JOIN sys.columns rc ON rc.object_id = fkc.referenced_object_id AND rc.column_id = fkc.referenced_column_id
|
|
153
|
+
`);
|
|
154
|
+
const checkRows = await db.query(`
|
|
155
|
+
SELECT s.name AS schema_name, t.name AS table_name, cc.name AS constraint_name, cc.definition
|
|
156
|
+
FROM sys.check_constraints cc
|
|
157
|
+
JOIN sys.tables t ON t.object_id = cc.parent_object_id
|
|
158
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
159
|
+
WHERE t.is_ms_shipped = 0
|
|
160
|
+
`);
|
|
161
|
+
const tables = [];
|
|
162
|
+
for (const t of tableRows) {
|
|
163
|
+
// Skip Skyway/Flyway's own bookkeeping table — the migrator creates it
|
|
164
|
+
// automatically before running any migration, so emitting CREATE TABLE for
|
|
165
|
+
// it in the baseline collides ("There is already an object named ...").
|
|
166
|
+
// Filtering at the introspector level removes the table AND all of its
|
|
167
|
+
// nested constraints/indexes/data from the snapshot in one pass.
|
|
168
|
+
if (isExcludedTable(t.table_name))
|
|
169
|
+
continue;
|
|
170
|
+
const tableKey = `${t.schema_name}.${t.table_name}`;
|
|
171
|
+
const columns = stableSortBy(columnRows.filter((c) => c.schema_name === t.schema_name && c.table_name === t.table_name), (c) => String(c.ordinal).padStart(6, '0')).map((c) => ({
|
|
172
|
+
name: c.column_name,
|
|
173
|
+
ordinal: c.ordinal,
|
|
174
|
+
dataType: formatMssqlType(c),
|
|
175
|
+
isNullable: !!c.is_nullable,
|
|
176
|
+
isIdentity: !!c.is_identity,
|
|
177
|
+
isComputed: !!c.is_computed,
|
|
178
|
+
computedExpression: c.computed_definition ?? undefined,
|
|
179
|
+
isComputedPersisted: c.computed_is_persisted == null ? undefined : !!c.computed_is_persisted,
|
|
180
|
+
defaultExpression: c.default_definition ?? undefined,
|
|
181
|
+
defaultConstraintName: c.default_name ?? undefined,
|
|
182
|
+
collation: c.collation ?? undefined,
|
|
183
|
+
}));
|
|
184
|
+
const pkCols = pkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_primary === 1);
|
|
185
|
+
const primaryKey = pkCols.length
|
|
186
|
+
? {
|
|
187
|
+
name: pkCols[0].constraint_name,
|
|
188
|
+
clustered: !!pkCols[0].is_clustered,
|
|
189
|
+
columns: stableSortBy(pkCols, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
|
|
190
|
+
}
|
|
191
|
+
: undefined;
|
|
192
|
+
const uniqueGroups = groupBy(pkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_unique === 1), (r) => r.constraint_name);
|
|
193
|
+
const uniqueConstraints = stableSortBy([...uniqueGroups.entries()].map(([name, rows]) => ({
|
|
194
|
+
name,
|
|
195
|
+
clustered: !!rows[0].is_clustered,
|
|
196
|
+
columns: stableSortBy(rows, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
|
|
197
|
+
})), (u) => u.name.toLowerCase());
|
|
198
|
+
const indexGroups = groupBy(indexRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_constraint === 0), (r) => r.index_name);
|
|
199
|
+
const indexes = stableSortBy([...indexGroups.entries()].map(([name, rows]) => {
|
|
200
|
+
const keyCols = stableSortBy(rows.filter((r) => r.is_included === 0), (r) => String(r.key_ordinal).padStart(6, '0'));
|
|
201
|
+
const inclCols = rows.filter((r) => r.is_included === 1).map((r) => r.column_name);
|
|
202
|
+
return {
|
|
203
|
+
name,
|
|
204
|
+
isUnique: !!rows[0].is_unique,
|
|
205
|
+
isClustered: !!rows[0].is_clustered,
|
|
206
|
+
columns: keyCols.map((r) => r.column_name),
|
|
207
|
+
includes: stableSortBy(inclCols, (n) => n.toLowerCase()),
|
|
208
|
+
filter: rows[0].filter_definition ?? undefined,
|
|
209
|
+
};
|
|
210
|
+
}), (i) => i.name.toLowerCase());
|
|
211
|
+
const fkGroups = groupBy(fkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name), (r) => r.constraint_name);
|
|
212
|
+
const foreignKeys = stableSortBy([...fkGroups.entries()].map(([name, rows]) => {
|
|
213
|
+
const ordered = stableSortBy(rows, (r) => String(r.key_index).padStart(6, '0'));
|
|
214
|
+
return {
|
|
215
|
+
name,
|
|
216
|
+
columns: ordered.map((r) => r.column_name),
|
|
217
|
+
referencedSchema: ordered[0].ref_schema,
|
|
218
|
+
referencedTable: ordered[0].ref_table,
|
|
219
|
+
referencedColumns: ordered.map((r) => r.ref_column),
|
|
220
|
+
onDelete: normalizeAction(ordered[0].on_delete),
|
|
221
|
+
onUpdate: normalizeAction(ordered[0].on_update),
|
|
222
|
+
};
|
|
223
|
+
}), (f) => f.name.toLowerCase());
|
|
224
|
+
const checks = stableSortBy(checkRows
|
|
225
|
+
.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name)
|
|
226
|
+
.map((r) => ({ name: r.constraint_name, expression: r.definition })), (c) => c.name.toLowerCase());
|
|
227
|
+
tables.push({
|
|
228
|
+
schema: t.schema_name,
|
|
229
|
+
name: t.table_name,
|
|
230
|
+
hasIdentity: !!t.has_identity,
|
|
231
|
+
columns,
|
|
232
|
+
primaryKey,
|
|
233
|
+
uniqueConstraints,
|
|
234
|
+
indexes,
|
|
235
|
+
foreignKeys,
|
|
236
|
+
checks,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return tables;
|
|
240
|
+
}
|
|
241
|
+
async function readViews(db) {
|
|
242
|
+
const rows = await db.query(`
|
|
243
|
+
SELECT s.name AS schema_name, v.name AS view_name,
|
|
244
|
+
OBJECT_DEFINITION(v.object_id) AS definition
|
|
245
|
+
FROM sys.views v
|
|
246
|
+
JOIN sys.schemas s ON s.schema_id = v.schema_id
|
|
247
|
+
WHERE v.is_ms_shipped = 0
|
|
248
|
+
`);
|
|
249
|
+
return rows.map((r) => ({ schema: r.schema_name, name: r.view_name, definition: r.definition ?? '' }));
|
|
250
|
+
}
|
|
251
|
+
async function readRoutines(db, kind) {
|
|
252
|
+
const typePredicate = kind === 'P' ? `o.type = 'P'` : `o.type IN ('FN','TF','IF')`;
|
|
253
|
+
const rows = await db.query(`
|
|
254
|
+
SELECT s.name AS schema_name, o.name AS object_name,
|
|
255
|
+
OBJECT_DEFINITION(o.object_id) AS definition,
|
|
256
|
+
CAST(o.type AS VARCHAR(8)) AS routine_kind
|
|
257
|
+
FROM sys.objects o
|
|
258
|
+
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
|
259
|
+
WHERE ${typePredicate}
|
|
260
|
+
AND o.is_ms_shipped = 0
|
|
261
|
+
`);
|
|
262
|
+
return rows.map((r) => ({
|
|
263
|
+
schema: r.schema_name,
|
|
264
|
+
name: r.object_name,
|
|
265
|
+
definition: r.definition ?? '',
|
|
266
|
+
kind: kind === 'P' ? 'procedure' : 'function',
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
async function readTriggers(db) {
|
|
270
|
+
const rows = await db.query(`
|
|
271
|
+
SELECT s.name AS schema_name, tr.name AS trigger_name, t.name AS table_name,
|
|
272
|
+
OBJECT_DEFINITION(tr.object_id) AS definition
|
|
273
|
+
FROM sys.triggers tr
|
|
274
|
+
JOIN sys.tables t ON t.object_id = tr.parent_id
|
|
275
|
+
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
|
276
|
+
WHERE tr.is_ms_shipped = 0
|
|
277
|
+
AND t.name <> 'flyway_schema_history' -- defensive: never emit triggers on the migrator's own table
|
|
278
|
+
`);
|
|
279
|
+
return rows.map((r) => ({
|
|
280
|
+
schema: r.schema_name,
|
|
281
|
+
name: r.trigger_name,
|
|
282
|
+
table: r.table_name,
|
|
283
|
+
definition: r.definition ?? '',
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Map `sys.database_principals.type` to our normalized kind.
|
|
288
|
+
* Skipped types: A (application role) is kept; X/E/G handle AAD/Windows; everything
|
|
289
|
+
* not in this table is filtered out at the WHERE level below so we never see it.
|
|
290
|
+
*/
|
|
291
|
+
const PRINCIPAL_TYPE_MAP = {
|
|
292
|
+
S: 'sql_user',
|
|
293
|
+
U: 'windows_user',
|
|
294
|
+
R: 'database_role',
|
|
295
|
+
A: 'application_role',
|
|
296
|
+
X: 'aad_group',
|
|
297
|
+
E: 'aad_user',
|
|
298
|
+
};
|
|
299
|
+
async function readPrincipals(db) {
|
|
300
|
+
// Filter out:
|
|
301
|
+
// - fixed roles (`is_fixed_role = 1`)
|
|
302
|
+
// - principal_id <= 4 (dbo / guest / INFORMATION_SCHEMA / sys)
|
|
303
|
+
// - `public` role
|
|
304
|
+
// - Anything starting with `db_` (defensive — covers fixed roles in older SQL versions)
|
|
305
|
+
const rows = await db.query(`
|
|
306
|
+
SELECT p.name,
|
|
307
|
+
CAST(p.type AS NVARCHAR(2)) AS type,
|
|
308
|
+
o.name AS owner_name,
|
|
309
|
+
p.default_schema_name
|
|
310
|
+
FROM sys.database_principals p
|
|
311
|
+
LEFT JOIN sys.database_principals o ON o.principal_id = p.owning_principal_id
|
|
312
|
+
WHERE p.is_fixed_role = 0
|
|
313
|
+
AND p.principal_id > 4
|
|
314
|
+
AND p.name NOT IN ('dbo','guest','public','sys','INFORMATION_SCHEMA')
|
|
315
|
+
AND p.name NOT LIKE 'db[_]%'
|
|
316
|
+
AND p.type IN ('S','U','R','A','X','E')
|
|
317
|
+
`);
|
|
318
|
+
return rows
|
|
319
|
+
.map((r) => {
|
|
320
|
+
const kind = PRINCIPAL_TYPE_MAP[r.type];
|
|
321
|
+
if (!kind)
|
|
322
|
+
return null;
|
|
323
|
+
const def = { name: r.name, kind };
|
|
324
|
+
if (r.owner_name)
|
|
325
|
+
def.owner = r.owner_name;
|
|
326
|
+
if (r.default_schema_name)
|
|
327
|
+
def.defaultSchema = r.default_schema_name;
|
|
328
|
+
return def;
|
|
329
|
+
})
|
|
330
|
+
.filter((p) => p !== null);
|
|
331
|
+
}
|
|
332
|
+
async function readRoleMemberships(db) {
|
|
333
|
+
// Skip the built-in `db_owner <- dbo` membership (SQL Server creates it
|
|
334
|
+
// automatically). Other memberships involving fixed roles (e.g.
|
|
335
|
+
// `db_datareader <- MJ_Connect`) ARE meaningful and need to be emitted.
|
|
336
|
+
const rows = await db.query(`
|
|
337
|
+
SELECT rp.name AS role_name, mp.name AS member_name
|
|
338
|
+
FROM sys.database_role_members rm
|
|
339
|
+
JOIN sys.database_principals rp ON rp.principal_id = rm.role_principal_id
|
|
340
|
+
JOIN sys.database_principals mp ON mp.principal_id = rm.member_principal_id
|
|
341
|
+
WHERE NOT (rp.name = 'db_owner' AND mp.name = 'dbo')
|
|
342
|
+
AND mp.name NOT IN ('public')
|
|
343
|
+
`);
|
|
344
|
+
return rows.map((r) => ({ role: r.role_name, member: r.member_name }));
|
|
345
|
+
}
|
|
346
|
+
async function readPermissions(db) {
|
|
347
|
+
// We support four classes: DATABASE (0), OBJECT_OR_COLUMN (1), SCHEMA (3), TYPE (6).
|
|
348
|
+
// Anything else (DATABASE_PRINCIPAL, ENDPOINT, ASSEMBLY, etc.) is intentionally skipped.
|
|
349
|
+
// Filtering: skip grants TO public (default), TO dbo (always has full access),
|
|
350
|
+
// and anything on system schemas / objects.
|
|
351
|
+
const rows = await db.query(`
|
|
352
|
+
SELECT 1 AS class_id, 'OBJECT_OR_COLUMN' AS class_desc,
|
|
353
|
+
CAST(p.state_desc AS NVARCHAR(40)) AS state,
|
|
354
|
+
CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
|
|
355
|
+
USER_NAME(p.grantee_principal_id) AS grantee_name,
|
|
356
|
+
OBJECT_SCHEMA_NAME(p.major_id) AS schema_name,
|
|
357
|
+
OBJECT_NAME(p.major_id) AS object_name,
|
|
358
|
+
CASE WHEN p.minor_id > 0
|
|
359
|
+
THEN (SELECT c.name FROM sys.columns c WHERE c.object_id = p.major_id AND c.column_id = p.minor_id)
|
|
360
|
+
ELSE NULL END AS column_name,
|
|
361
|
+
NULL AS type_name
|
|
362
|
+
FROM sys.database_permissions p
|
|
363
|
+
WHERE p.class = 1
|
|
364
|
+
AND OBJECT_SCHEMA_NAME(p.major_id) IS NOT NULL
|
|
365
|
+
AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
|
|
366
|
+
AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
|
|
367
|
+
|
|
368
|
+
UNION ALL
|
|
369
|
+
|
|
370
|
+
SELECT 0 AS class_id, 'DATABASE' AS class_desc,
|
|
371
|
+
CAST(p.state_desc AS NVARCHAR(40)) AS state,
|
|
372
|
+
CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
|
|
373
|
+
USER_NAME(p.grantee_principal_id) AS grantee_name,
|
|
374
|
+
NULL AS schema_name, NULL AS object_name, NULL AS column_name, NULL AS type_name
|
|
375
|
+
FROM sys.database_permissions p
|
|
376
|
+
WHERE p.class = 0
|
|
377
|
+
AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
|
|
378
|
+
AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
|
|
379
|
+
|
|
380
|
+
UNION ALL
|
|
381
|
+
|
|
382
|
+
SELECT 3 AS class_id, 'SCHEMA' AS class_desc,
|
|
383
|
+
CAST(p.state_desc AS NVARCHAR(40)) AS state,
|
|
384
|
+
CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
|
|
385
|
+
USER_NAME(p.grantee_principal_id) AS grantee_name,
|
|
386
|
+
s.name AS schema_name, NULL AS object_name, NULL AS column_name, NULL AS type_name
|
|
387
|
+
FROM sys.database_permissions p
|
|
388
|
+
JOIN sys.schemas s ON s.schema_id = p.major_id
|
|
389
|
+
WHERE p.class = 3
|
|
390
|
+
AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
|
|
391
|
+
AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
|
|
392
|
+
|
|
393
|
+
UNION ALL
|
|
394
|
+
|
|
395
|
+
SELECT 6 AS class_id, 'TYPE' AS class_desc,
|
|
396
|
+
CAST(p.state_desc AS NVARCHAR(40)) AS state,
|
|
397
|
+
CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
|
|
398
|
+
USER_NAME(p.grantee_principal_id) AS grantee_name,
|
|
399
|
+
SCHEMA_NAME(t.schema_id) AS schema_name, NULL AS object_name, NULL AS column_name, t.name AS type_name
|
|
400
|
+
FROM sys.database_permissions p
|
|
401
|
+
JOIN sys.types t ON t.user_type_id = p.major_id
|
|
402
|
+
WHERE p.class = 6
|
|
403
|
+
AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
|
|
404
|
+
AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
|
|
405
|
+
`);
|
|
406
|
+
return rows.map((r) => ({
|
|
407
|
+
grantee: r.grantee_name,
|
|
408
|
+
state: r.state.toUpperCase(),
|
|
409
|
+
permission: r.permission_name.toUpperCase(),
|
|
410
|
+
targetClass: r.class_id === 0 ? 'database' :
|
|
411
|
+
r.class_id === 1 ? 'object' :
|
|
412
|
+
r.class_id === 3 ? 'schema' : 'type',
|
|
413
|
+
targetSchema: r.schema_name ?? undefined,
|
|
414
|
+
targetObject: r.object_name ?? undefined,
|
|
415
|
+
targetType: r.type_name ?? undefined,
|
|
416
|
+
targetColumn: r.column_name ?? undefined,
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
async function readSequences(db) {
|
|
420
|
+
const rows = await db.query(`
|
|
421
|
+
SELECT s.name AS schema_name, sq.name AS seq_name,
|
|
422
|
+
CAST(sq.start_value AS NVARCHAR(64)) AS start_value,
|
|
423
|
+
CAST(sq.increment AS NVARCHAR(64)) AS increment,
|
|
424
|
+
CAST(sq.minimum_value AS NVARCHAR(64)) AS minimum_value,
|
|
425
|
+
CAST(sq.maximum_value AS NVARCHAR(64)) AS maximum_value,
|
|
426
|
+
CAST(sq.is_cycling AS INT) AS is_cycling,
|
|
427
|
+
CAST(sq.current_value AS NVARCHAR(64)) AS current_value
|
|
428
|
+
FROM sys.sequences sq
|
|
429
|
+
JOIN sys.schemas s ON s.schema_id = sq.schema_id
|
|
430
|
+
`);
|
|
431
|
+
return rows.map((r) => ({
|
|
432
|
+
schema: r.schema_name,
|
|
433
|
+
name: r.seq_name,
|
|
434
|
+
startValue: r.start_value,
|
|
435
|
+
increment: r.increment,
|
|
436
|
+
minValue: r.minimum_value ?? undefined,
|
|
437
|
+
maxValue: r.maximum_value ?? undefined,
|
|
438
|
+
cycle: !!r.is_cycling,
|
|
439
|
+
currentValue: r.current_value ?? undefined,
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
async function readUserDefinedTypes(db) {
|
|
443
|
+
// Table types live in sys.table_types. We only support user-defined ones.
|
|
444
|
+
// Note: tt.type_table_object_id is the synthetic object whose columns live in sys.columns.
|
|
445
|
+
const typeRows = await db.query(`
|
|
446
|
+
SELECT s.name AS schema_name,
|
|
447
|
+
tt.name AS type_name,
|
|
448
|
+
tt.type_table_object_id,
|
|
449
|
+
CAST(tt.is_memory_optimized AS INT) AS is_memory_optimized
|
|
450
|
+
FROM sys.table_types tt
|
|
451
|
+
JOIN sys.schemas s ON s.schema_id = tt.schema_id
|
|
452
|
+
WHERE tt.is_user_defined = 1
|
|
453
|
+
`);
|
|
454
|
+
if (typeRows.length === 0)
|
|
455
|
+
return [];
|
|
456
|
+
const columnRows = await db.query(`
|
|
457
|
+
SELECT tt.type_table_object_id,
|
|
458
|
+
c.name AS column_name,
|
|
459
|
+
c.column_id AS ordinal,
|
|
460
|
+
ty.name AS data_type, c.max_length, c.precision, c.scale,
|
|
461
|
+
CAST(c.is_nullable AS INT) AS is_nullable,
|
|
462
|
+
c.collation_name AS collation
|
|
463
|
+
FROM sys.table_types tt
|
|
464
|
+
JOIN sys.columns c ON c.object_id = tt.type_table_object_id
|
|
465
|
+
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
|
466
|
+
WHERE tt.is_user_defined = 1
|
|
467
|
+
`);
|
|
468
|
+
// Read primary keys defined inline on table types. Distinct query because the
|
|
469
|
+
// sys.indexes object_id matches type_table_object_id (not sys.tables).
|
|
470
|
+
const pkRows = await db.query(`
|
|
471
|
+
SELECT tt.type_table_object_id, kc.name AS constraint_name,
|
|
472
|
+
c.name AS column_name, ic.key_ordinal,
|
|
473
|
+
CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered
|
|
474
|
+
FROM sys.table_types tt
|
|
475
|
+
JOIN sys.key_constraints kc ON kc.parent_object_id = tt.type_table_object_id AND kc.type = 'PK'
|
|
476
|
+
JOIN sys.indexes i ON i.object_id = kc.parent_object_id AND i.index_id = kc.unique_index_id
|
|
477
|
+
JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
|
|
478
|
+
JOIN sys.columns c ON c.object_id = i.object_id AND c.column_id = ic.column_id
|
|
479
|
+
WHERE tt.is_user_defined = 1
|
|
480
|
+
`);
|
|
481
|
+
const out = [];
|
|
482
|
+
for (const t of typeRows) {
|
|
483
|
+
const cols = stableSortBy(columnRows.filter((c) => c.type_table_object_id === t.type_table_object_id), (c) => String(c.ordinal).padStart(6, '0')).map((c) => ({
|
|
484
|
+
name: c.column_name,
|
|
485
|
+
ordinal: c.ordinal,
|
|
486
|
+
dataType: formatMssqlType(c),
|
|
487
|
+
isNullable: !!c.is_nullable,
|
|
488
|
+
collation: c.collation ?? undefined,
|
|
489
|
+
}));
|
|
490
|
+
const pkCols = pkRows.filter((r) => r.type_table_object_id === t.type_table_object_id);
|
|
491
|
+
const primaryKey = pkCols.length
|
|
492
|
+
? {
|
|
493
|
+
name: pkCols[0].constraint_name,
|
|
494
|
+
clustered: !!pkCols[0].is_clustered,
|
|
495
|
+
columns: stableSortBy(pkCols, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
|
|
496
|
+
}
|
|
497
|
+
: undefined;
|
|
498
|
+
out.push({
|
|
499
|
+
schema: t.schema_name,
|
|
500
|
+
name: t.type_name,
|
|
501
|
+
kind: 'table',
|
|
502
|
+
isMemoryOptimized: !!t.is_memory_optimized,
|
|
503
|
+
columns: cols,
|
|
504
|
+
primaryKey,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
async function readExtendedProperties(db) {
|
|
510
|
+
// Three classes of extended properties matter for MJ:
|
|
511
|
+
// class=1 OBJECT_OR_COLUMN — table, view, proc, function, trigger, type, and any column thereof
|
|
512
|
+
// class=3 SCHEMA
|
|
513
|
+
// class=7 INDEX — rare in MJ but emit for completeness
|
|
514
|
+
// Everything else (DB-level, assemblies, etc.) is skipped intentionally.
|
|
515
|
+
//
|
|
516
|
+
// For class=1 we have to look up the object's type (sys.objects.type) and
|
|
517
|
+
// figure out the level1Type (TABLE/VIEW/PROCEDURE/FUNCTION/TYPE) plus the
|
|
518
|
+
// optional level2 column. Table types route through sys.table_types since
|
|
519
|
+
// their object_id matches type_table_object_id, not sys.objects.
|
|
520
|
+
const rows = await db.query(`
|
|
521
|
+
-- class 1: object (or column)
|
|
522
|
+
SELECT 1 AS class_id,
|
|
523
|
+
ep.name AS prop_name,
|
|
524
|
+
CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
|
|
525
|
+
s.name AS schema_name,
|
|
526
|
+
CASE
|
|
527
|
+
WHEN o.type IN ('U') THEN 'TABLE'
|
|
528
|
+
WHEN o.type IN ('V') THEN 'VIEW'
|
|
529
|
+
WHEN o.type IN ('P') THEN 'PROCEDURE'
|
|
530
|
+
WHEN o.type IN ('FN','TF','IF') THEN 'FUNCTION'
|
|
531
|
+
WHEN o.type IN ('SO') THEN 'SEQUENCE'
|
|
532
|
+
WHEN o.type IN ('TR') THEN NULL -- triggers get re-routed below to TABLE/TRIGGER
|
|
533
|
+
ELSE NULL
|
|
534
|
+
END AS level1_type,
|
|
535
|
+
o.name AS level1_name,
|
|
536
|
+
CASE WHEN ep.minor_id > 0 THEN 'COLUMN' ELSE NULL END AS level2_type,
|
|
537
|
+
c.name AS level2_name
|
|
538
|
+
FROM sys.extended_properties ep
|
|
539
|
+
JOIN sys.objects o ON o.object_id = ep.major_id
|
|
540
|
+
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
|
541
|
+
LEFT JOIN sys.columns c ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.minor_id > 0
|
|
542
|
+
WHERE ep.class = 1
|
|
543
|
+
AND o.is_ms_shipped = 0
|
|
544
|
+
AND o.type IN ('U','V','P','FN','TF','IF','SO') -- triggers handled below
|
|
545
|
+
|
|
546
|
+
UNION ALL
|
|
547
|
+
|
|
548
|
+
-- class 1 (triggers): level1=TABLE (parent), level2=TRIGGER (the trigger itself)
|
|
549
|
+
SELECT 1 AS class_id,
|
|
550
|
+
ep.name AS prop_name,
|
|
551
|
+
CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
|
|
552
|
+
ps.name AS schema_name,
|
|
553
|
+
'TABLE' AS level1_type,
|
|
554
|
+
pt.name AS level1_name,
|
|
555
|
+
'TRIGGER' AS level2_type,
|
|
556
|
+
tr.name AS level2_name
|
|
557
|
+
FROM sys.extended_properties ep
|
|
558
|
+
JOIN sys.triggers tr ON tr.object_id = ep.major_id
|
|
559
|
+
JOIN sys.tables pt ON pt.object_id = tr.parent_id
|
|
560
|
+
JOIN sys.schemas ps ON ps.schema_id = pt.schema_id
|
|
561
|
+
WHERE ep.class = 1
|
|
562
|
+
AND tr.is_ms_shipped = 0
|
|
563
|
+
|
|
564
|
+
UNION ALL
|
|
565
|
+
|
|
566
|
+
-- class 1 (table types): level1=TYPE, level2 = COLUMN if applicable
|
|
567
|
+
SELECT 1 AS class_id,
|
|
568
|
+
ep.name AS prop_name,
|
|
569
|
+
CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
|
|
570
|
+
s.name AS schema_name,
|
|
571
|
+
'TYPE' AS level1_type,
|
|
572
|
+
tt.name AS level1_name,
|
|
573
|
+
CASE WHEN ep.minor_id > 0 THEN 'COLUMN' ELSE NULL END AS level2_type,
|
|
574
|
+
c.name AS level2_name
|
|
575
|
+
FROM sys.extended_properties ep
|
|
576
|
+
JOIN sys.table_types tt ON tt.type_table_object_id = ep.major_id
|
|
577
|
+
JOIN sys.schemas s ON s.schema_id = tt.schema_id
|
|
578
|
+
LEFT JOIN sys.columns c ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.minor_id > 0
|
|
579
|
+
WHERE ep.class = 1
|
|
580
|
+
AND tt.is_user_defined = 1
|
|
581
|
+
|
|
582
|
+
UNION ALL
|
|
583
|
+
|
|
584
|
+
-- class 3: schema-level
|
|
585
|
+
SELECT 3 AS class_id,
|
|
586
|
+
ep.name AS prop_name,
|
|
587
|
+
CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
|
|
588
|
+
s.name AS schema_name,
|
|
589
|
+
NULL AS level1_type,
|
|
590
|
+
NULL AS level1_name,
|
|
591
|
+
NULL AS level2_type,
|
|
592
|
+
NULL AS level2_name
|
|
593
|
+
FROM sys.extended_properties ep
|
|
594
|
+
JOIN sys.schemas s ON s.schema_id = ep.major_id
|
|
595
|
+
WHERE ep.class = 3
|
|
596
|
+
AND s.name NOT IN ('sys', 'INFORMATION_SCHEMA', 'guest', 'db_owner', 'db_accessadmin',
|
|
597
|
+
'db_securityadmin', 'db_ddladmin', 'db_backupoperator', 'db_datareader',
|
|
598
|
+
'db_datawriter', 'db_denydatareader', 'db_denydatawriter')
|
|
599
|
+
`);
|
|
600
|
+
const out = [];
|
|
601
|
+
for (const r of rows) {
|
|
602
|
+
// Skip rows we couldn't classify (level1_type=NULL on class=1 means an
|
|
603
|
+
// object kind we don't emit baselines for, e.g. CLR assemblies).
|
|
604
|
+
if (r.class_id === 1 && !r.level1_type)
|
|
605
|
+
continue;
|
|
606
|
+
if (!r.schema_name)
|
|
607
|
+
continue;
|
|
608
|
+
// Mirror the table-level filter for any property attached to flyway_schema_history.
|
|
609
|
+
if (r.level1_name && isExcludedTable(r.level1_name))
|
|
610
|
+
continue;
|
|
611
|
+
out.push({
|
|
612
|
+
name: r.prop_name,
|
|
613
|
+
value: r.prop_value ?? '',
|
|
614
|
+
schemaName: r.schema_name,
|
|
615
|
+
level1Type: r.level1_type ?? undefined,
|
|
616
|
+
level1Name: r.level1_name ?? undefined,
|
|
617
|
+
level2Type: r.level2_type ?? undefined,
|
|
618
|
+
level2Name: r.level2_name ?? undefined,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
function formatMssqlType(c) {
|
|
624
|
+
const type = c.data_type.toLowerCase();
|
|
625
|
+
switch (type) {
|
|
626
|
+
case 'nvarchar':
|
|
627
|
+
case 'nchar': {
|
|
628
|
+
const length = c.max_length === -1 ? 'max' : String(c.max_length / 2);
|
|
629
|
+
return `${type}(${length})`;
|
|
630
|
+
}
|
|
631
|
+
case 'varchar':
|
|
632
|
+
case 'char':
|
|
633
|
+
case 'binary':
|
|
634
|
+
case 'varbinary': {
|
|
635
|
+
const length = c.max_length === -1 ? 'max' : String(c.max_length);
|
|
636
|
+
return `${type}(${length})`;
|
|
637
|
+
}
|
|
638
|
+
case 'decimal':
|
|
639
|
+
case 'numeric':
|
|
640
|
+
return `${type}(${c.precision},${c.scale})`;
|
|
641
|
+
case 'datetime2':
|
|
642
|
+
case 'datetimeoffset':
|
|
643
|
+
case 'time':
|
|
644
|
+
return `${type}(${c.scale})`;
|
|
645
|
+
case 'float':
|
|
646
|
+
return c.precision === 53 ? 'float' : `float(${c.precision})`;
|
|
647
|
+
default:
|
|
648
|
+
return type;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function normalizeAction(raw) {
|
|
652
|
+
const r = (raw || '').toUpperCase().replace(/_/g, '');
|
|
653
|
+
if (r.includes('CASCADE'))
|
|
654
|
+
return 'CASCADE';
|
|
655
|
+
if (r.includes('SETNULL'))
|
|
656
|
+
return 'SET_NULL';
|
|
657
|
+
if (r.includes('SETDEFAULT'))
|
|
658
|
+
return 'SET_DEFAULT';
|
|
659
|
+
return 'NO_ACTION';
|
|
660
|
+
}
|
|
661
|
+
function groupBy(items, key) {
|
|
662
|
+
const out = new Map();
|
|
663
|
+
for (const item of items) {
|
|
664
|
+
const k = key(item);
|
|
665
|
+
const list = out.get(k);
|
|
666
|
+
if (list)
|
|
667
|
+
list.push(item);
|
|
668
|
+
else
|
|
669
|
+
out.set(k, [item]);
|
|
670
|
+
}
|
|
671
|
+
return out;
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=introspector-mssql.js.map
|