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.
Files changed (104) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +304 -0
  3. package/package.json +52 -0
  4. package/src/behavioral/behavioral-applier.js +374 -0
  5. package/src/behavioral/behavioral-extractor.js +266 -0
  6. package/src/behavioral/behavioral-puller.js +35 -0
  7. package/src/behavioral/index.js +4 -0
  8. package/src/behavioral/phase-sorter.js +24 -0
  9. package/src/constants.js +97 -0
  10. package/src/ddl-generator/alter-generator.js +1512 -0
  11. package/src/ddl-generator/comment-generator.js +95 -0
  12. package/src/ddl-generator/create-generator.js +1426 -0
  13. package/src/ddl-generator/drop-generator.js +194 -0
  14. package/src/ddl-generator/generator.js +78 -0
  15. package/src/ddl-generator/grant-generator.js +31 -0
  16. package/src/ddl-generator/index.js +3 -0
  17. package/src/ddl-generator/pg-version.js +15 -0
  18. package/src/ddl-generator/rename-generator.js +88 -0
  19. package/src/ddl-generator/safe-patterns.js +241 -0
  20. package/src/differ/change-classifier.js +192 -0
  21. package/src/differ/dependency-resolver.js +523 -0
  22. package/src/differ/index.js +12 -0
  23. package/src/differ/object-matcher.js +510 -0
  24. package/src/differ/property-differ.js +1134 -0
  25. package/src/differ/risk-tagger.js +561 -0
  26. package/src/differ/schema-differ.js +309 -0
  27. package/src/differ/utils/levenshtein.js +210 -0
  28. package/src/differ/utils/path-builder.js +198 -0
  29. package/src/differ/utils/type-compatibility.js +472 -0
  30. package/src/errors.js +147 -0
  31. package/src/executor/drift-detector.js +221 -0
  32. package/src/executor/index.js +7 -0
  33. package/src/executor/lock-manager.js +308 -0
  34. package/src/executor/migration-executor.js +1249 -0
  35. package/src/executor/progress-tracker.js +81 -0
  36. package/src/executor/recovery-manager.js +47 -0
  37. package/src/executor/snapshot-manager.js +22 -0
  38. package/src/executor/sql-splitter.js +120 -0
  39. package/src/executor/transaction-manager.js +288 -0
  40. package/src/index.js +483 -0
  41. package/src/introspection/index.js +4 -0
  42. package/src/introspection/introspector.js +250 -0
  43. package/src/introspection/queries/access-methods.js +29 -0
  44. package/src/introspection/queries/casts.js +38 -0
  45. package/src/introspection/queries/collations.js +51 -0
  46. package/src/introspection/queries/comments.js +66 -0
  47. package/src/introspection/queries/constraints.js +65 -0
  48. package/src/introspection/queries/conversions.js +39 -0
  49. package/src/introspection/queries/databases.js +43 -0
  50. package/src/introspection/queries/default-privileges.js +37 -0
  51. package/src/introspection/queries/event-triggers.js +43 -0
  52. package/src/introspection/queries/extensions.js +21 -0
  53. package/src/introspection/queries/foreign-data.js +136 -0
  54. package/src/introspection/queries/functions.js +75 -0
  55. package/src/introspection/queries/grants.js +38 -0
  56. package/src/introspection/queries/index.js +33 -0
  57. package/src/introspection/queries/indexes.js +99 -0
  58. package/src/introspection/queries/inheritance.js +24 -0
  59. package/src/introspection/queries/multiranges.js +37 -0
  60. package/src/introspection/queries/operators.js +180 -0
  61. package/src/introspection/queries/partitions.js +42 -0
  62. package/src/introspection/queries/pg18-19.js +43 -0
  63. package/src/introspection/queries/policies.js +35 -0
  64. package/src/introspection/queries/procedural-languages.js +36 -0
  65. package/src/introspection/queries/publications.js +114 -0
  66. package/src/introspection/queries/roles.js +66 -0
  67. package/src/introspection/queries/rules.js +49 -0
  68. package/src/introspection/queries/sequences.js +37 -0
  69. package/src/introspection/queries/statistics.js +74 -0
  70. package/src/introspection/queries/subscriptions.js +99 -0
  71. package/src/introspection/queries/tables.js +151 -0
  72. package/src/introspection/queries/tablespaces.js +36 -0
  73. package/src/introspection/queries/text-search.js +146 -0
  74. package/src/introspection/queries/triggers.js +61 -0
  75. package/src/introspection/queries/types.js +106 -0
  76. package/src/introspection/queries/views.js +146 -0
  77. package/src/introspection/translator.js +1283 -0
  78. package/src/introspection/version-detector.js +25 -0
  79. package/src/planner/backfill-planner.js +36 -0
  80. package/src/planner/dry-run.js +21 -0
  81. package/src/planner/index.js +6 -0
  82. package/src/planner/migration-planner.js +356 -0
  83. package/src/planner/smart-migrator.js +82 -0
  84. package/src/planner/step-sequencer.js +61 -0
  85. package/src/planner/type-registry.js +47 -0
  86. package/src/risk/compatibility-checker.js +29 -0
  87. package/src/risk/data-loss-checker.js +32 -0
  88. package/src/risk/destructive-checker.js +38 -0
  89. package/src/risk/index.js +6 -0
  90. package/src/risk/lock-analyzer.js +39 -0
  91. package/src/risk/recommendations.js +44 -0
  92. package/src/risk/risk-engine.js +25 -0
  93. package/src/storage/index.js +5 -0
  94. package/src/storage/memory-storage.js +87 -0
  95. package/src/storage/migration-table.js +423 -0
  96. package/src/storage/rollback-generator.js +344 -0
  97. package/src/types/changes.js +136 -0
  98. package/src/types/connection.js +17 -0
  99. package/src/types/execution.js +107 -0
  100. package/src/types/index.js +1 -0
  101. package/src/types/migration.js +67 -0
  102. package/src/types/risk.js +32 -0
  103. package/src/types/schema.d.ts +1004 -0
  104. 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
+ }