pg-migration-engine 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 +35 -0
- package/README.md +304 -0
- package/package.json +52 -0
- package/src/behavioral/behavioral-applier.js +374 -0
- package/src/behavioral/behavioral-extractor.js +266 -0
- package/src/behavioral/behavioral-puller.js +35 -0
- package/src/behavioral/index.js +4 -0
- package/src/behavioral/phase-sorter.js +24 -0
- package/src/constants.js +97 -0
- package/src/ddl-generator/alter-generator.js +1512 -0
- package/src/ddl-generator/comment-generator.js +95 -0
- package/src/ddl-generator/create-generator.js +1426 -0
- package/src/ddl-generator/drop-generator.js +194 -0
- package/src/ddl-generator/generator.js +78 -0
- package/src/ddl-generator/grant-generator.js +31 -0
- package/src/ddl-generator/index.js +3 -0
- package/src/ddl-generator/pg-version.js +15 -0
- package/src/ddl-generator/rename-generator.js +88 -0
- package/src/ddl-generator/safe-patterns.js +241 -0
- package/src/differ/change-classifier.js +192 -0
- package/src/differ/dependency-resolver.js +523 -0
- package/src/differ/index.js +12 -0
- package/src/differ/object-matcher.js +510 -0
- package/src/differ/property-differ.js +1134 -0
- package/src/differ/risk-tagger.js +561 -0
- package/src/differ/schema-differ.js +309 -0
- package/src/differ/utils/levenshtein.js +210 -0
- package/src/differ/utils/path-builder.js +198 -0
- package/src/differ/utils/type-compatibility.js +472 -0
- package/src/errors.js +147 -0
- package/src/executor/drift-detector.js +221 -0
- package/src/executor/index.js +7 -0
- package/src/executor/lock-manager.js +308 -0
- package/src/executor/migration-executor.js +1249 -0
- package/src/executor/progress-tracker.js +81 -0
- package/src/executor/recovery-manager.js +47 -0
- package/src/executor/snapshot-manager.js +22 -0
- package/src/executor/sql-splitter.js +120 -0
- package/src/executor/transaction-manager.js +288 -0
- package/src/index.js +483 -0
- package/src/introspection/index.js +4 -0
- package/src/introspection/introspector.js +250 -0
- package/src/introspection/queries/access-methods.js +29 -0
- package/src/introspection/queries/casts.js +38 -0
- package/src/introspection/queries/collations.js +51 -0
- package/src/introspection/queries/comments.js +66 -0
- package/src/introspection/queries/constraints.js +65 -0
- package/src/introspection/queries/conversions.js +39 -0
- package/src/introspection/queries/databases.js +43 -0
- package/src/introspection/queries/default-privileges.js +37 -0
- package/src/introspection/queries/event-triggers.js +43 -0
- package/src/introspection/queries/extensions.js +21 -0
- package/src/introspection/queries/foreign-data.js +136 -0
- package/src/introspection/queries/functions.js +75 -0
- package/src/introspection/queries/grants.js +38 -0
- package/src/introspection/queries/index.js +33 -0
- package/src/introspection/queries/indexes.js +99 -0
- package/src/introspection/queries/inheritance.js +24 -0
- package/src/introspection/queries/multiranges.js +37 -0
- package/src/introspection/queries/operators.js +180 -0
- package/src/introspection/queries/partitions.js +42 -0
- package/src/introspection/queries/pg18-19.js +43 -0
- package/src/introspection/queries/policies.js +35 -0
- package/src/introspection/queries/procedural-languages.js +36 -0
- package/src/introspection/queries/publications.js +114 -0
- package/src/introspection/queries/roles.js +66 -0
- package/src/introspection/queries/rules.js +49 -0
- package/src/introspection/queries/sequences.js +37 -0
- package/src/introspection/queries/statistics.js +74 -0
- package/src/introspection/queries/subscriptions.js +99 -0
- package/src/introspection/queries/tables.js +151 -0
- package/src/introspection/queries/tablespaces.js +36 -0
- package/src/introspection/queries/text-search.js +146 -0
- package/src/introspection/queries/triggers.js +61 -0
- package/src/introspection/queries/types.js +106 -0
- package/src/introspection/queries/views.js +146 -0
- package/src/introspection/translator.js +1283 -0
- package/src/introspection/version-detector.js +25 -0
- package/src/planner/backfill-planner.js +36 -0
- package/src/planner/dry-run.js +21 -0
- package/src/planner/index.js +6 -0
- package/src/planner/migration-planner.js +356 -0
- package/src/planner/smart-migrator.js +82 -0
- package/src/planner/step-sequencer.js +61 -0
- package/src/planner/type-registry.js +47 -0
- package/src/risk/compatibility-checker.js +29 -0
- package/src/risk/data-loss-checker.js +32 -0
- package/src/risk/destructive-checker.js +38 -0
- package/src/risk/index.js +6 -0
- package/src/risk/lock-analyzer.js +39 -0
- package/src/risk/recommendations.js +44 -0
- package/src/risk/risk-engine.js +25 -0
- package/src/storage/index.js +5 -0
- package/src/storage/memory-storage.js +87 -0
- package/src/storage/migration-table.js +423 -0
- package/src/storage/rollback-generator.js +344 -0
- package/src/types/changes.js +136 -0
- package/src/types/connection.js +17 -0
- package/src/types/execution.js +107 -0
- package/src/types/index.js +1 -0
- package/src/types/migration.js +67 -0
- package/src/types/risk.js +32 -0
- package/src/types/schema.d.ts +1004 -0
- package/src/types/schema.js +561 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const FDW_QUERY = `
|
|
2
|
+
SELECT
|
|
3
|
+
f.oid,
|
|
4
|
+
f.fdwname AS name,
|
|
5
|
+
pg_catalog.pg_get_userbyid(f.fdwowner) AS owner,
|
|
6
|
+
f.fdwhandler::regproc::text AS handler,
|
|
7
|
+
f.fdwvalidator::regproc::text AS validator,
|
|
8
|
+
f.fdwoptions AS options,
|
|
9
|
+
f.fdwacl AS privileges,
|
|
10
|
+
pg_catalog.obj_description(f.oid, 'pg_foreign_data_wrapper') AS comment
|
|
11
|
+
FROM pg_catalog.pg_foreign_data_wrapper f
|
|
12
|
+
ORDER BY f.fdwname
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const FOREIGN_SERVERS_QUERY = `
|
|
16
|
+
SELECT
|
|
17
|
+
s.oid,
|
|
18
|
+
s.srvname AS name,
|
|
19
|
+
pg_catalog.pg_get_userbyid(s.srvowner) AS owner,
|
|
20
|
+
s.srvtype AS type,
|
|
21
|
+
s.srvversion AS version,
|
|
22
|
+
f.fdwname AS fdw,
|
|
23
|
+
s.srvoptions AS options,
|
|
24
|
+
s.srvacl AS privileges,
|
|
25
|
+
pg_catalog.obj_description(s.oid, 'pg_foreign_server') AS comment
|
|
26
|
+
FROM pg_catalog.pg_foreign_server s
|
|
27
|
+
JOIN pg_catalog.pg_foreign_data_wrapper f ON f.oid = s.srvfdw
|
|
28
|
+
ORDER BY s.srvname
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const USER_MAPPINGS_QUERY = `
|
|
32
|
+
SELECT
|
|
33
|
+
u.oid,
|
|
34
|
+
pg_catalog.pg_get_userbyid(u.umuser) AS user,
|
|
35
|
+
s.srvname AS server,
|
|
36
|
+
u.umoptions AS options
|
|
37
|
+
FROM pg_catalog.pg_user_mapping u
|
|
38
|
+
JOIN pg_catalog.pg_foreign_server s ON s.oid = u.umserver
|
|
39
|
+
ORDER BY s.srvname, u.umuser
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const FOREIGN_TABLES_QUERY = `
|
|
43
|
+
SELECT
|
|
44
|
+
c.oid,
|
|
45
|
+
n.nspname AS schema,
|
|
46
|
+
c.relname AS name,
|
|
47
|
+
s.srvname AS server_name,
|
|
48
|
+
f.ftoptions AS options,
|
|
49
|
+
pg_catalog.obj_description(c.oid, 'pg_class') AS comment,
|
|
50
|
+
c.relacl AS privileges
|
|
51
|
+
FROM pg_catalog.pg_foreign_table f
|
|
52
|
+
JOIN pg_catalog.pg_class c ON c.oid = f.ftrelid
|
|
53
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
54
|
+
JOIN pg_catalog.pg_foreign_server s ON s.oid = f.ftserver
|
|
55
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
56
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
57
|
+
ORDER BY n.nspname, c.relname
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const FOREIGN_TABLE_COLUMNS_QUERY = `
|
|
61
|
+
SELECT
|
|
62
|
+
n.nspname AS schema,
|
|
63
|
+
c.relname AS table_name,
|
|
64
|
+
a.attname AS column_name,
|
|
65
|
+
a.attfdwoptions AS options
|
|
66
|
+
FROM pg_catalog.pg_attribute a
|
|
67
|
+
JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
|
|
68
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
69
|
+
JOIN pg_catalog.pg_foreign_table f ON f.ftrelid = c.oid
|
|
70
|
+
WHERE a.attnum > 0 AND NOT a.attisdropped
|
|
71
|
+
AND a.attfdwoptions IS NOT NULL
|
|
72
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
73
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
74
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {import('pg').Pool} pool
|
|
79
|
+
* @returns {Promise<Array>}
|
|
80
|
+
*/
|
|
81
|
+
export async function queryForeignDataWrappers(pool) {
|
|
82
|
+
try {
|
|
83
|
+
const result = await pool.query(FDW_QUERY);
|
|
84
|
+
return result.rows;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {import('pg').Pool} pool
|
|
92
|
+
* @returns {Promise<Array>}
|
|
93
|
+
*/
|
|
94
|
+
export async function queryForeignServers(pool) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await pool.query(FOREIGN_SERVERS_QUERY);
|
|
97
|
+
return result.rows;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {import('pg').Pool} pool
|
|
105
|
+
* @returns {Promise<Array>}
|
|
106
|
+
*/
|
|
107
|
+
export async function queryUserMappings(pool) {
|
|
108
|
+
try {
|
|
109
|
+
const result = await pool.query(USER_MAPPINGS_QUERY);
|
|
110
|
+
return result.rows;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {import('pg').Pool} pool
|
|
118
|
+
* @returns {Promise<{tables: Array, columnOptions: Array}>}
|
|
119
|
+
*/
|
|
120
|
+
export async function queryForeignTables(pool) {
|
|
121
|
+
try {
|
|
122
|
+
const tablesResult = await pool.query(FOREIGN_TABLES_QUERY);
|
|
123
|
+
let columnsResult = { rows: [] };
|
|
124
|
+
try {
|
|
125
|
+
columnsResult = await pool.query(FOREIGN_TABLE_COLUMNS_QUERY);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// Column options query may fail on older PG versions
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
tables: tablesResult.rows,
|
|
131
|
+
columnOptions: columnsResult.rows,
|
|
132
|
+
};
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return { tables: [], columnOptions: [] };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const FUNCTIONS_QUERY = `
|
|
2
|
+
SELECT p.oid,
|
|
3
|
+
n.nspname AS schema,
|
|
4
|
+
p.proname AS name,
|
|
5
|
+
pg_catalog.pg_get_function_identity_arguments(p.oid) AS argument_types,
|
|
6
|
+
p.proargnames AS argument_names,
|
|
7
|
+
p.proargdefaults AS argument_defaults,
|
|
8
|
+
p.proargmodes AS argument_modes,
|
|
9
|
+
pg_catalog.pg_get_function_result(p.oid) AS return_type,
|
|
10
|
+
p.proretset AS return_set,
|
|
11
|
+
l.lanname AS language,
|
|
12
|
+
p.prosrc AS source,
|
|
13
|
+
p.probin AS precompiled_body,
|
|
14
|
+
CASE p.provolatile
|
|
15
|
+
WHEN 'i' THEN 'IMMUTABLE'
|
|
16
|
+
WHEN 's' THEN 'STABLE'
|
|
17
|
+
WHEN 'v' THEN 'VOLATILE'
|
|
18
|
+
END AS volatility,
|
|
19
|
+
p.proisstrict AS is_strict,
|
|
20
|
+
CASE p.prosecdef WHEN true THEN 'DEFINER' ELSE 'INVOKER' END AS security,
|
|
21
|
+
CASE p.proparallel
|
|
22
|
+
WHEN 's' THEN 'SAFE'
|
|
23
|
+
WHEN 'u' THEN 'UNSAFE'
|
|
24
|
+
WHEN 'r' THEN 'RESTRICTED'
|
|
25
|
+
END AS parallel,
|
|
26
|
+
p.proleakproof AS is_leakproof,
|
|
27
|
+
p.procost AS cost,
|
|
28
|
+
p.prorows AS rows,
|
|
29
|
+
CASE p.prokind
|
|
30
|
+
WHEN 'f' THEN 'FUNCTION'
|
|
31
|
+
WHEN 'p' THEN 'PROCEDURE'
|
|
32
|
+
WHEN 'a' THEN 'AGGREGATE'
|
|
33
|
+
WHEN 'w' THEN 'WINDOW'
|
|
34
|
+
END AS kind,
|
|
35
|
+
pg_catalog.pg_get_userbyid(p.proowner) AS owner,
|
|
36
|
+
p.proconfig AS configuration,
|
|
37
|
+
p.proacl AS privileges,
|
|
38
|
+
p.prosupport::regproc::text AS support_function,
|
|
39
|
+
pg_catalog.obj_description(p.oid, 'pg_proc') AS comment,
|
|
40
|
+
a.aggtransfn::text AS agg_sfunc,
|
|
41
|
+
a.aggfinalfn::text AS agg_finalfunc,
|
|
42
|
+
a.aggcombinefn::text AS agg_combinefunc,
|
|
43
|
+
a.aggtranstype::regtype::text AS agg_stype,
|
|
44
|
+
a.agginitval AS agg_initcond,
|
|
45
|
+
a.aggtransspace AS agg_sspace,
|
|
46
|
+
a.aggfinalextra AS agg_finalfunc_extra,
|
|
47
|
+
CASE a.aggfinalmodify
|
|
48
|
+
WHEN 'r' THEN 'READ_ONLY'
|
|
49
|
+
WHEN 's' THEN 'SHAREABLE'
|
|
50
|
+
WHEN 'w' THEN 'READ_WRITE'
|
|
51
|
+
END AS agg_finalfunc_modify,
|
|
52
|
+
a.aggserialfn::text AS agg_serialfunc,
|
|
53
|
+
a.aggdeserialfn::text AS agg_deserialfunc,
|
|
54
|
+
a.aggsortop::regoper::text AS agg_sortop,
|
|
55
|
+
CASE a.aggkind
|
|
56
|
+
WHEN 'n' THEN false
|
|
57
|
+
WHEN 'h' THEN true
|
|
58
|
+
END AS agg_hypothetical
|
|
59
|
+
FROM pg_catalog.pg_proc p
|
|
60
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
61
|
+
JOIN pg_catalog.pg_language l ON l.oid = p.prolang
|
|
62
|
+
LEFT JOIN pg_catalog.pg_aggregate a ON a.aggfnoid = p.oid
|
|
63
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
64
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
65
|
+
ORDER BY n.nspname, p.proname
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {import('pg').Pool} pool
|
|
70
|
+
* @returns {Promise<Array>}
|
|
71
|
+
*/
|
|
72
|
+
export async function queryFunctions(pool) {
|
|
73
|
+
const result = await pool.query(FUNCTIONS_QUERY);
|
|
74
|
+
return result.rows;
|
|
75
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const GRANTS_QUERY = `
|
|
2
|
+
SELECT n.nspname AS schema,
|
|
3
|
+
c.relname AS object,
|
|
4
|
+
CASE c.relkind
|
|
5
|
+
WHEN 'r' THEN 'TABLE'
|
|
6
|
+
WHEN 'p' THEN 'TABLE'
|
|
7
|
+
WHEN 'v' THEN 'VIEW'
|
|
8
|
+
WHEN 'm' THEN 'MATERIALIZED_VIEW'
|
|
9
|
+
WHEN 'S' THEN 'SEQUENCE'
|
|
10
|
+
WHEN 'f' THEN 'FOREIGN_TABLE'
|
|
11
|
+
END AS object_type,
|
|
12
|
+
a.grantee::regrole::text AS grantee,
|
|
13
|
+
a.grantor::regrole::text AS grantor,
|
|
14
|
+
a.privilege_type AS privilege,
|
|
15
|
+
a.is_grantable
|
|
16
|
+
FROM pg_catalog.pg_class c
|
|
17
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
18
|
+
CROSS JOIN LATERAL aclexplode(
|
|
19
|
+
CASE c.relkind
|
|
20
|
+
WHEN 'S' THEN c.relacl
|
|
21
|
+
ELSE c.relacl
|
|
22
|
+
END
|
|
23
|
+
) a
|
|
24
|
+
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'S', 'f')
|
|
25
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
26
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
27
|
+
AND a.grantee::regrole::text != a.grantor::regrole::text
|
|
28
|
+
ORDER BY n.nspname, c.relname, a.grantee::regrole::text
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {import('pg').Pool} pool
|
|
33
|
+
* @returns {Promise<Array>}
|
|
34
|
+
*/
|
|
35
|
+
export async function queryGrants(pool) {
|
|
36
|
+
const result = await pool.query(GRANTS_QUERY);
|
|
37
|
+
return result.rows;
|
|
38
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { querySchemas, queryTables, queryColumns, queryToastOptions } from './tables.js';
|
|
2
|
+
export { queryConstraints } from './constraints.js';
|
|
3
|
+
export { queryIndexes, queryIndexColumns } from './indexes.js';
|
|
4
|
+
export { queryFunctions } from './functions.js';
|
|
5
|
+
export { queryTriggers } from './triggers.js';
|
|
6
|
+
export { queryTypes } from './types.js';
|
|
7
|
+
export { queryViews } from './views.js';
|
|
8
|
+
export { querySequences } from './sequences.js';
|
|
9
|
+
export { queryPartitions } from './partitions.js';
|
|
10
|
+
export { queryPolicies } from './policies.js';
|
|
11
|
+
export { queryExtensions } from './extensions.js';
|
|
12
|
+
export { queryInheritance } from './inheritance.js';
|
|
13
|
+
export { queryComments } from './comments.js';
|
|
14
|
+
export { queryGrants } from './grants.js';
|
|
15
|
+
export { queryPg18Features } from './pg18-19.js';
|
|
16
|
+
export { queryPublications } from './publications.js';
|
|
17
|
+
export { querySubscriptions } from './subscriptions.js';
|
|
18
|
+
export { queryStatistics } from './statistics.js';
|
|
19
|
+
export { queryCollations } from './collations.js';
|
|
20
|
+
export { queryConversions } from './conversions.js';
|
|
21
|
+
export { queryOperators, queryOperatorClasses, queryOperatorFamilies } from './operators.js';
|
|
22
|
+
export { queryTextSearchConfigs, queryTextSearchDictionaries, queryTextSearchParsers, queryTextSearchTemplates } from './text-search.js';
|
|
23
|
+
export { queryForeignDataWrappers, queryForeignServers, queryUserMappings, queryForeignTables } from './foreign-data.js';
|
|
24
|
+
export { queryCasts } from './casts.js';
|
|
25
|
+
export { queryEventTriggers } from './event-triggers.js';
|
|
26
|
+
export { queryRules } from './rules.js';
|
|
27
|
+
export { queryRoles } from './roles.js';
|
|
28
|
+
export { queryInterfaceTablespaces } from './tablespaces.js';
|
|
29
|
+
export { queryInterfaceAccessMethods } from './access-methods.js';
|
|
30
|
+
export { queryInterfaceDatabases } from './databases.js';
|
|
31
|
+
export { queryDefaultPrivileges } from './default-privileges.js';
|
|
32
|
+
export { queryProceduralLanguages } from './procedural-languages.js';
|
|
33
|
+
export { queryMultiranges } from './multiranges.js';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const INDEXES_QUERY = `
|
|
2
|
+
SELECT c.relname AS index_name,
|
|
3
|
+
n.nspname AS schema,
|
|
4
|
+
c.relname AS index_relname,
|
|
5
|
+
t.relname AS table_name,
|
|
6
|
+
tn.nspname AS table_schema,
|
|
7
|
+
c.relowner::regrole::text AS owner,
|
|
8
|
+
ix.indisunique AS is_unique,
|
|
9
|
+
ix.indisprimary AS is_primary,
|
|
10
|
+
am.amname AS method,
|
|
11
|
+
ix.indkey AS column_indices,
|
|
12
|
+
pg_catalog.pg_get_indexdef(ix.indexrelid) AS definition,
|
|
13
|
+
CASE WHEN ix.indpred IS NOT NULL THEN pg_catalog.pg_get_expr(ix.indpred, ix.indrelid) ELSE NULL END AS where_clause,
|
|
14
|
+
CASE WHEN c.reltablespace != 0 THEN (SELECT spcname FROM pg_catalog.pg_tablespace WHERE oid = c.reltablespace) ELSE NULL END AS tablespace,
|
|
15
|
+
c.reloptions AS storage_options,
|
|
16
|
+
pg_catalog.obj_description(ix.indexrelid, 'pg_class') AS comment,
|
|
17
|
+
ix.indisvalid AS is_valid,
|
|
18
|
+
ix.indisready AS is_ready,
|
|
19
|
+
ix.indislive AS is_live,
|
|
20
|
+
ix.indisreplident AS is_replica_identity,
|
|
21
|
+
ix.indisclustered AS is_clustered,
|
|
22
|
+
ix.indnkeyatts AS number_of_key_columns,
|
|
23
|
+
ix.indoption AS column_options,
|
|
24
|
+
(SELECT (regexp_matches(unnest_opt, 'pages_per_range=(\d+)'))[1]::int
|
|
25
|
+
FROM unnest(COALESCE(c.reloptions, ARRAY[]::text[])) AS unnest_opt
|
|
26
|
+
WHERE unnest_opt LIKE 'pages_per_range=%'
|
|
27
|
+
LIMIT 1
|
|
28
|
+
) AS brin_pages_per_range
|
|
29
|
+
FROM pg_catalog.pg_index ix
|
|
30
|
+
JOIN pg_catalog.pg_class c ON c.oid = ix.indexrelid
|
|
31
|
+
JOIN pg_catalog.pg_class t ON t.oid = ix.indrelid
|
|
32
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
33
|
+
JOIN pg_catalog.pg_namespace tn ON tn.oid = t.relnamespace
|
|
34
|
+
JOIN pg_catalog.pg_am am ON am.oid = c.relam
|
|
35
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
36
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
37
|
+
ORDER BY tn.nspname, t.relname, c.relname
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const INDEX_COLUMNS_QUERY = `
|
|
42
|
+
WITH index_keys AS (
|
|
43
|
+
SELECT
|
|
44
|
+
i.indexrelid,
|
|
45
|
+
i.indrelid,
|
|
46
|
+
i.indkey,
|
|
47
|
+
i.indoption,
|
|
48
|
+
i.indcollation,
|
|
49
|
+
i.indclass,
|
|
50
|
+
i.indexprs,
|
|
51
|
+
t.idx,
|
|
52
|
+
t.ordinality,
|
|
53
|
+
t.ordinality - 1 AS idx_offset
|
|
54
|
+
FROM pg_catalog.pg_index i
|
|
55
|
+
CROSS JOIN LATERAL unnest(i.indkey) WITH ORDINALITY AS t(idx, ordinality)
|
|
56
|
+
)
|
|
57
|
+
SELECT
|
|
58
|
+
ik.indexrelid::regclass::text AS index_name,
|
|
59
|
+
n.nspname AS schema,
|
|
60
|
+
c.relname AS index_relname,
|
|
61
|
+
ik.idx AS attnum,
|
|
62
|
+
CASE WHEN ik.idx = 0 THEN NULL ELSE a.attname END AS column_name,
|
|
63
|
+
ik.ordinality AS position,
|
|
64
|
+
COALESCE(coll.collname, NULL) AS collation,
|
|
65
|
+
COALESCE(opc.opcname, NULL) AS opclass,
|
|
66
|
+
CASE WHEN (ik.indoption[ik.idx_offset] & 1) = 1 THEN 'DESC' ELSE 'ASC' END AS direction,
|
|
67
|
+
CASE WHEN (ik.indoption[ik.idx_offset] & 2) = 2 THEN 'NULLS FIRST' ELSE 'NULLS LAST' END AS nulls_order,
|
|
68
|
+
pg_catalog.col_description(a.attrelid, a.attnum) AS column_comment,
|
|
69
|
+
CASE WHEN ik.idx = 0 AND ik.indexprs IS NOT NULL THEN
|
|
70
|
+
pg_catalog.pg_get_indexdef(ik.indexrelid, (ik.idx_offset + 1)::integer, true)
|
|
71
|
+
ELSE NULL END AS expression
|
|
72
|
+
FROM index_keys ik
|
|
73
|
+
JOIN pg_catalog.pg_class c ON c.oid = ik.indexrelid
|
|
74
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
75
|
+
LEFT JOIN pg_catalog.pg_attribute a ON a.attrelid = ik.indrelid AND a.attnum = ik.idx
|
|
76
|
+
LEFT JOIN pg_catalog.pg_collation coll ON coll.oid = ik.indcollation[ik.idx_offset]
|
|
77
|
+
LEFT JOIN pg_catalog.pg_opclass opc ON opc.oid = ik.indclass[ik.idx_offset]
|
|
78
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
79
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
80
|
+
ORDER BY n.nspname, c.relname, ik.ordinality
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {import('pg').Pool} pool
|
|
85
|
+
* @returns {Promise<Array>}
|
|
86
|
+
*/
|
|
87
|
+
export async function queryIndexes(pool) {
|
|
88
|
+
const result = await pool.query(INDEXES_QUERY);
|
|
89
|
+
return result.rows;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {import('pg').Pool} pool
|
|
94
|
+
* @returns {Promise<Array>}
|
|
95
|
+
*/
|
|
96
|
+
export async function queryIndexColumns(pool) {
|
|
97
|
+
const result = await pool.query(INDEX_COLUMNS_QUERY);
|
|
98
|
+
return result.rows;
|
|
99
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const INHERITANCE_QUERY = `
|
|
2
|
+
SELECT child.relname AS child_table,
|
|
3
|
+
cn.nspname AS child_schema,
|
|
4
|
+
parent.relname AS parent_table,
|
|
5
|
+
pn.nspname AS parent_schema
|
|
6
|
+
FROM pg_catalog.pg_inherits i
|
|
7
|
+
JOIN pg_catalog.pg_class child ON child.oid = i.inhrelid
|
|
8
|
+
JOIN pg_catalog.pg_namespace cn ON cn.oid = child.relnamespace
|
|
9
|
+
JOIN pg_catalog.pg_class parent ON parent.oid = i.inhparent
|
|
10
|
+
JOIN pg_catalog.pg_namespace pn ON pn.oid = parent.relnamespace
|
|
11
|
+
WHERE parent.relkind != 'p'
|
|
12
|
+
AND cn.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
13
|
+
AND cn.nspname NOT LIKE 'pg_temp_%'
|
|
14
|
+
ORDER BY cn.nspname, child.relname, pn.nspname, parent.relname
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('pg').Pool} pool
|
|
19
|
+
* @returns {Promise<Array>}
|
|
20
|
+
*/
|
|
21
|
+
export async function queryInheritance(pool) {
|
|
22
|
+
const result = await pool.query(INHERITANCE_QUERY);
|
|
23
|
+
return result.rows;
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const MULTIRANGES_QUERY = `
|
|
2
|
+
SELECT
|
|
3
|
+
t.oid,
|
|
4
|
+
n.nspname AS schema,
|
|
5
|
+
t.typname AS name,
|
|
6
|
+
pg_catalog.pg_get_userbyid(t.typowner) AS owner,
|
|
7
|
+
format_type(t.typbasetype, NULL) AS range_type,
|
|
8
|
+
pg_catalog.obj_description(t.oid, 'pg_type') AS comment
|
|
9
|
+
FROM pg_catalog.pg_type t
|
|
10
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
11
|
+
WHERE t.typtype = 'm'
|
|
12
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
13
|
+
ORDER BY n.nspname, t.typname
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('pg').Pool} pool
|
|
18
|
+
* @param {number} version
|
|
19
|
+
* @returns {Promise<Array>}
|
|
20
|
+
*/
|
|
21
|
+
export async function queryMultiranges(pool, version) {
|
|
22
|
+
if (version < 140000) return [];
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await pool.query(MULTIRANGES_QUERY);
|
|
26
|
+
|
|
27
|
+
return result.rows.map(row => ({
|
|
28
|
+
schema: row.schema,
|
|
29
|
+
name: row.name,
|
|
30
|
+
owner: row.owner,
|
|
31
|
+
range_type: row.range_type,
|
|
32
|
+
comment: row.comment || undefined,
|
|
33
|
+
}));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const OPERATORS_QUERY = `
|
|
2
|
+
SELECT
|
|
3
|
+
o.oid,
|
|
4
|
+
n.nspname AS schema,
|
|
5
|
+
o.oprname AS name,
|
|
6
|
+
pg_catalog.pg_get_userbyid(o.oprowner) AS owner,
|
|
7
|
+
o.oprkind AS kind,
|
|
8
|
+
CASE WHEN o.oprleft = 0 THEN NULL ELSE format_type(o.oprleft, NULL) END AS left_type,
|
|
9
|
+
CASE WHEN o.oprright = 0 THEN NULL ELSE format_type(o.oprright, NULL) END AS right_type,
|
|
10
|
+
format_type(o.oprresult, NULL) AS result_type,
|
|
11
|
+
o.oprcode::regproc::text AS proc,
|
|
12
|
+
CASE WHEN o.oprcanhash THEN 'HASH' ELSE NULL END AS can_hash,
|
|
13
|
+
CASE WHEN o.oprcanmerge THEN 'MERGE' ELSE NULL END AS can_merge,
|
|
14
|
+
o.oprcom::regoper::text AS commutator,
|
|
15
|
+
o.oprnegate::regoper::text AS negator,
|
|
16
|
+
o.oprrest::regproc::text AS restrict_func,
|
|
17
|
+
o.oprjoin::regproc::text AS join_func,
|
|
18
|
+
pg_catalog.obj_description(o.oid, 'pg_operator') AS comment
|
|
19
|
+
FROM pg_catalog.pg_operator o
|
|
20
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace
|
|
21
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
22
|
+
ORDER BY n.nspname, o.oprname
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {import('pg').Pool} pool
|
|
27
|
+
* @returns {Promise<Array>}
|
|
28
|
+
*/
|
|
29
|
+
export async function queryOperators(pool) {
|
|
30
|
+
try {
|
|
31
|
+
const result = await pool.query(OPERATORS_QUERY);
|
|
32
|
+
|
|
33
|
+
return result.rows.map(row => ({
|
|
34
|
+
schema: row.schema,
|
|
35
|
+
name: row.name,
|
|
36
|
+
owner: row.owner,
|
|
37
|
+
kind: row.kind,
|
|
38
|
+
left_type: row.left_type,
|
|
39
|
+
right_type: row.right_type,
|
|
40
|
+
result_type: row.result_type,
|
|
41
|
+
proc: row.proc,
|
|
42
|
+
can_hash: !!row.can_hash,
|
|
43
|
+
can_merge: !!row.can_merge,
|
|
44
|
+
commutator: row.commutator || undefined,
|
|
45
|
+
negator: row.negator || undefined,
|
|
46
|
+
restrict_function: row.restrict_func || undefined,
|
|
47
|
+
join_function: row.join_func || undefined,
|
|
48
|
+
comment: row.comment || undefined,
|
|
49
|
+
}));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('pg').Pool} pool
|
|
57
|
+
* @returns {Promise<Array>}
|
|
58
|
+
*/
|
|
59
|
+
export async function queryOperatorFamilies(pool) {
|
|
60
|
+
const OPERATOR_FAMILIES_QUERY = `
|
|
61
|
+
SELECT
|
|
62
|
+
of.oid,
|
|
63
|
+
n.nspname AS schema,
|
|
64
|
+
of.opfname AS name,
|
|
65
|
+
pg_catalog.pg_get_userbyid(of.opfowner) AS owner,
|
|
66
|
+
am.amname AS access_method
|
|
67
|
+
FROM pg_catalog.pg_opfamily of
|
|
68
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = of.opfnamespace
|
|
69
|
+
JOIN pg_catalog.pg_am am ON am.oid = of.opfmethod
|
|
70
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
71
|
+
ORDER BY n.nspname, of.opfname
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const result = await pool.query(OPERATOR_FAMILIES_QUERY);
|
|
76
|
+
return result.rows;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {import('pg').Pool} pool
|
|
84
|
+
* @returns {Promise<Array>}
|
|
85
|
+
*/
|
|
86
|
+
export async function queryOperatorClasses(pool) {
|
|
87
|
+
const OPERATOR_CLASSES_QUERY = `
|
|
88
|
+
SELECT
|
|
89
|
+
oc.oid,
|
|
90
|
+
n.nspname AS schema,
|
|
91
|
+
oc.opcname AS name,
|
|
92
|
+
pg_catalog.pg_get_userbyid(oc.opcowner) AS owner,
|
|
93
|
+
of.opfname AS family,
|
|
94
|
+
format_type(oc.opcintype, NULL) AS input_type,
|
|
95
|
+
oc.opcdefault AS is_default,
|
|
96
|
+
am.amname AS access_method,
|
|
97
|
+
CASE WHEN oc.opckeytype = 0 THEN NULL ELSE format_type(oc.opckeytype, NULL) END AS storage_type,
|
|
98
|
+
pg_catalog.obj_description(oc.oid, 'pg_opclass') AS comment
|
|
99
|
+
FROM pg_catalog.pg_opclass oc
|
|
100
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = oc.opcnamespace
|
|
101
|
+
JOIN pg_catalog.pg_opfamily of ON of.oid = oc.opcfamily
|
|
102
|
+
JOIN pg_catalog.pg_am am ON am.oid = oc.opcmethod
|
|
103
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
104
|
+
ORDER BY n.nspname, oc.opcname
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const OPERATOR_CLASS_OPERATORS_QUERY = `
|
|
108
|
+
SELECT
|
|
109
|
+
n.nspname AS schema,
|
|
110
|
+
oc.opcname AS opclass_name,
|
|
111
|
+
ao.amopstrategy AS strategy,
|
|
112
|
+
CASE WHEN o.oprleft = 0 THEN NULL ELSE format_type(o.oprleft, NULL) END AS left_type,
|
|
113
|
+
CASE WHEN o.oprright = 0 THEN NULL ELSE format_type(o.oprright, NULL) END AS right_type,
|
|
114
|
+
o.oprname AS operator_name
|
|
115
|
+
FROM pg_catalog.pg_amop ao
|
|
116
|
+
JOIN pg_catalog.pg_opclass oc ON oc.oid = ao.amopfamily
|
|
117
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = oc.opcnamespace
|
|
118
|
+
JOIN pg_catalog.pg_operator o ON o.oid = ao.amopopr
|
|
119
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
120
|
+
ORDER BY n.nspname, oc.opcname, ao.amopstrategy
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const OPERATOR_CLASS_FUNCTIONS_QUERY = `
|
|
124
|
+
SELECT
|
|
125
|
+
n.nspname AS schema,
|
|
126
|
+
oc.opcname AS opclass_name,
|
|
127
|
+
ap.amprocnum AS support_num,
|
|
128
|
+
p.proname AS function_name
|
|
129
|
+
FROM pg_catalog.pg_amproc ap
|
|
130
|
+
JOIN pg_catalog.pg_opclass oc ON oc.oid = ap.amprocfamily
|
|
131
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = oc.opcnamespace
|
|
132
|
+
JOIN pg_catalog.pg_proc p ON p.oid = ap.amproc
|
|
133
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
134
|
+
ORDER BY n.nspname, oc.opcname, ap.amprocnum
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const classesResult = await pool.query(OPERATOR_CLASSES_QUERY);
|
|
139
|
+
let opsResult = { rows: [] };
|
|
140
|
+
let funcsResult = { rows: [] };
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
opsResult = await pool.query(OPERATOR_CLASS_OPERATORS_QUERY);
|
|
144
|
+
} catch (e) {}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
funcsResult = await pool.query(OPERATOR_CLASS_FUNCTIONS_QUERY);
|
|
148
|
+
} catch (e) {}
|
|
149
|
+
|
|
150
|
+
const operatorsMap = {};
|
|
151
|
+
for (const row of opsResult.rows) {
|
|
152
|
+
const key = `${row.schema}.${row.opclass_name}`;
|
|
153
|
+
if (!operatorsMap[key]) operatorsMap[key] = [];
|
|
154
|
+
operatorsMap[key].push({
|
|
155
|
+
strategy: row.strategy,
|
|
156
|
+
name: row.operator_name,
|
|
157
|
+
leftType: row.left_type,
|
|
158
|
+
rightType: row.right_type,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const functionsMap = {};
|
|
163
|
+
for (const row of funcsResult.rows) {
|
|
164
|
+
const key = `${row.schema}.${row.opclass_name}`;
|
|
165
|
+
if (!functionsMap[key]) functionsMap[key] = [];
|
|
166
|
+
functionsMap[key].push({
|
|
167
|
+
num: row.support_num,
|
|
168
|
+
name: row.function_name,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return classesResult.rows.map(row => ({
|
|
173
|
+
...row,
|
|
174
|
+
operators: operatorsMap[`${row.schema}.${row.name}`] || [],
|
|
175
|
+
functions: functionsMap[`${row.schema}.${row.name}`] || [],
|
|
176
|
+
}));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const PARTITIONS_QUERY = `
|
|
2
|
+
SELECT parent.relname AS parent_table,
|
|
3
|
+
pn.nspname AS parent_schema,
|
|
4
|
+
child.relname AS child_table,
|
|
5
|
+
cn.nspname AS child_schema,
|
|
6
|
+
pt.partstrat AS strategy,
|
|
7
|
+
pt.partnatts AS num_columns,
|
|
8
|
+
array_agg(pa.attname ORDER BY array_position(pt.partattrs, pa.attnum)) AS partition_columns,
|
|
9
|
+
pg_catalog.pg_get_expr(child.relpartbound, child.oid) AS partition_bound,
|
|
10
|
+
child.relispartition AS is_partition,
|
|
11
|
+
COALESCE(
|
|
12
|
+
(SELECT true FROM pg_catalog.pg_partitioned_table cp WHERE cp.partrelid = child.oid),
|
|
13
|
+
false
|
|
14
|
+
) AS is_default,
|
|
15
|
+
pg_catalog.pg_get_expr(pt.partexprs, pt.partrelid) AS partition_expression
|
|
16
|
+
FROM pg_catalog.pg_inherits i
|
|
17
|
+
JOIN pg_catalog.pg_class parent ON parent.oid = i.inhparent
|
|
18
|
+
JOIN pg_catalog.pg_namespace pn ON pn.oid = parent.relnamespace
|
|
19
|
+
JOIN pg_catalog.pg_class child ON child.oid = i.inhrelid
|
|
20
|
+
JOIN pg_catalog.pg_namespace cn ON cn.oid = child.relnamespace
|
|
21
|
+
JOIN pg_catalog.pg_partitioned_table pt ON pt.partrelid = parent.oid
|
|
22
|
+
LEFT JOIN pg_catalog.pg_attribute pa ON pa.attrelid = parent.oid
|
|
23
|
+
AND pa.attnum = ANY(pt.partattrs)
|
|
24
|
+
AND pa.attnum > 0
|
|
25
|
+
AND NOT pa.attisdropped
|
|
26
|
+
WHERE parent.relkind = 'p'
|
|
27
|
+
AND pn.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
28
|
+
AND pn.nspname NOT LIKE 'pg_temp_%'
|
|
29
|
+
GROUP BY parent.oid, parent.relname, pn.nspname,
|
|
30
|
+
child.oid, child.relname, cn.nspname,
|
|
31
|
+
pt.partstrat, pt.partnatts, child.relpartbound, child.relispartition, pt.partexprs
|
|
32
|
+
ORDER BY pn.nspname, parent.relname, child.relname
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {import('pg').Pool} pool
|
|
37
|
+
* @returns {Promise<Array>}
|
|
38
|
+
*/
|
|
39
|
+
export async function queryPartitions(pool) {
|
|
40
|
+
const result = await pool.query(PARTITIONS_QUERY);
|
|
41
|
+
return result.rows;
|
|
42
|
+
}
|