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,151 @@
|
|
|
1
|
+
const SCHEMAS_QUERY = `
|
|
2
|
+
SELECT n.nspname AS name,
|
|
3
|
+
n.nspowner::regrole::text AS owner,
|
|
4
|
+
n.nspacl AS privileges,
|
|
5
|
+
pg_catalog.obj_description(n.oid, 'pg_namespace') AS comment
|
|
6
|
+
FROM pg_catalog.pg_namespace n
|
|
7
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
8
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
9
|
+
AND n.nspname NOT LIKE 'pg_toast_temp_%'
|
|
10
|
+
ORDER BY n.nspname
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
const TABLES_QUERY = `
|
|
14
|
+
SELECT c.oid,
|
|
15
|
+
n.nspname AS schema,
|
|
16
|
+
c.relname AS name,
|
|
17
|
+
c.relkind AS kind,
|
|
18
|
+
c.relowner::regrole::text AS owner,
|
|
19
|
+
c.relpersistence AS persistence,
|
|
20
|
+
c.relrowsecurity AS rls_enabled,
|
|
21
|
+
c.relforcerowsecurity AS rls_forced,
|
|
22
|
+
c.reloptions AS storage_options,
|
|
23
|
+
CASE WHEN c.reltablespace != 0 THEN c.reltablespace::regrole::text ELSE NULL END AS tablespace,
|
|
24
|
+
CASE WHEN c.relkind = 'p' THEN pt.partstrat ELSE NULL END AS partition_strategy,
|
|
25
|
+
pg_catalog.pg_get_expr(c.relpartbound, c.oid) AS partition_bound,
|
|
26
|
+
c.relreplident AS replica_identity,
|
|
27
|
+
(SELECT idx.relname FROM pg_catalog.pg_index i JOIN pg_catalog.pg_class idx ON idx.oid = i.indexrelid WHERE i.indrelid = c.oid AND i.indisreplident = true LIMIT 1) AS replica_identity_index,
|
|
28
|
+
pg_catalog.pg_get_partkeydef(c.oid) AS partition_key_def,
|
|
29
|
+
/* HAS_OIDS_PLACEHOLDER */,
|
|
30
|
+
EXISTS (SELECT 1 FROM pg_catalog.pg_class tc JOIN pg_catalog.pg_namespace tn ON tn.oid = tc.relnamespace JOIN pg_catalog.pg_depend d ON d.objid = tc.oid WHERE tc.relkind = 't' AND tn.nspname = 'pg_toast' AND d.refobjid = c.oid AND d.classid = 'pg_class'::regclass) AS has_toast_table,
|
|
31
|
+
EXISTS (SELECT 1 FROM pg_catalog.pg_depend d WHERE d.classid = 'pg_class'::regclass AND d.objid = c.oid AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'e') AS user_catalog_table,
|
|
32
|
+
am.amname AS access_method
|
|
33
|
+
FROM pg_catalog.pg_class c
|
|
34
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
35
|
+
LEFT JOIN pg_catalog.pg_partitioned_table pt ON pt.partrelid = c.oid
|
|
36
|
+
LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam
|
|
37
|
+
WHERE c.relkind IN ('r', 'p', 'f')
|
|
38
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
39
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
40
|
+
ORDER BY n.nspname, c.relname
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const TOAST_OPTIONS_QUERY = `
|
|
44
|
+
SELECT
|
|
45
|
+
n.nspname AS schema,
|
|
46
|
+
c.relname AS table_name,
|
|
47
|
+
tc.relname AS toast_table_name,
|
|
48
|
+
tc.reloptions AS toast_storage_options
|
|
49
|
+
FROM pg_catalog.pg_class c
|
|
50
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
51
|
+
JOIN pg_catalog.pg_depend d ON d.refobjid = c.oid AND d.classid = 'pg_class'::regclass
|
|
52
|
+
JOIN pg_catalog.pg_class tc ON tc.oid = d.objid AND tc.relkind = 't'
|
|
53
|
+
JOIN pg_catalog.pg_namespace tn ON tn.oid = tc.relnamespace AND tn.nspname = 'pg_toast'
|
|
54
|
+
WHERE c.relkind IN ('r', 'p')
|
|
55
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
56
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
57
|
+
ORDER BY n.nspname, c.relname
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const COLUMNS_QUERY = `
|
|
61
|
+
SELECT a.attrelid::regclass::text AS table_ref,
|
|
62
|
+
n.nspname AS schema,
|
|
63
|
+
c.relname AS table_name,
|
|
64
|
+
a.attname AS name,
|
|
65
|
+
a.attnum AS ordinal_position,
|
|
66
|
+
format_type(a.atttypid, a.atttypmod) AS data_type,
|
|
67
|
+
NOT a.attnotnull AS is_nullable,
|
|
68
|
+
a.attgenerated AS generated,
|
|
69
|
+
CASE WHEN a.attgenerated IN ('s', 'v') AND d.adbin IS NOT NULL
|
|
70
|
+
THEN pg_catalog.pg_get_expr(d.adbin, d.adrelid)
|
|
71
|
+
ELSE NULL END AS generated_expression,
|
|
72
|
+
CASE WHEN (a.attgenerated IS NULL OR a.attgenerated = '') AND d.adbin IS NOT NULL
|
|
73
|
+
THEN pg_catalog.pg_get_expr(d.adbin, d.adrelid)
|
|
74
|
+
ELSE NULL END AS default_value,
|
|
75
|
+
a.attidentity AS identity,
|
|
76
|
+
CASE WHEN a.attcollation != 0 AND a.attcollation != 100
|
|
77
|
+
THEN a.attcollation::regcollation::text ELSE NULL END AS collation,
|
|
78
|
+
a.attstorage::text AS storage,
|
|
79
|
+
pg_catalog.col_description(a.attrelid, a.attnum) AS comment,
|
|
80
|
+
a.attstattarget AS statistics_target,
|
|
81
|
+
/* COMPRESSION_PLACEHOLDER */
|
|
82
|
+
s.seqstart AS identity_start,
|
|
83
|
+
s.seqincrement AS identity_increment,
|
|
84
|
+
s.seqmin AS identity_min,
|
|
85
|
+
s.seqmax AS identity_max,
|
|
86
|
+
s.seqcycle AS identity_cycle,
|
|
87
|
+
s.seqcache AS identity_cache,
|
|
88
|
+
a.attinhcount AS inherited_count,
|
|
89
|
+
a.attislocal AS is_local,
|
|
90
|
+
a.attacl AS privileges,
|
|
91
|
+
EXISTS (SELECT 1 FROM pg_catalog.pg_constraint con WHERE con.conrelid = a.attrelid AND con.contype = 'p' AND a.attnum = ANY(con.conkey)) AS is_primary_key,
|
|
92
|
+
EXISTS (SELECT 1 FROM pg_catalog.pg_index idx WHERE idx.indrelid = a.attrelid AND idx.indisunique AND a.attnum = ANY(idx.indkey) AND NOT idx.indisprimary) AS is_unique,
|
|
93
|
+
a.attlen AS length,
|
|
94
|
+
a.attndims AS array_dimensions
|
|
95
|
+
FROM pg_catalog.pg_attribute a
|
|
96
|
+
JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
|
|
97
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
98
|
+
LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
99
|
+
LEFT JOIN pg_catalog.pg_sequence s ON s.seqrelid = (
|
|
100
|
+
SELECT seq.oid FROM pg_catalog.pg_class seq
|
|
101
|
+
JOIN pg_catalog.pg_namespace seqn ON seqn.oid = seq.relnamespace
|
|
102
|
+
JOIN pg_catalog.pg_depend dep ON dep.objid = seq.oid AND dep.classid = 'pg_class'::regclass
|
|
103
|
+
WHERE seq.relkind = 'S' AND dep.refobjid = a.attrelid AND dep.refobjsubid = a.attnum
|
|
104
|
+
AND seqn.nspname NOT LIKE 'pg_temp_%'
|
|
105
|
+
LIMIT 1
|
|
106
|
+
)
|
|
107
|
+
WHERE a.attnum > 0
|
|
108
|
+
AND NOT a.attisdropped
|
|
109
|
+
AND c.relkind IN ('r', 'p', 'f')
|
|
110
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
111
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
112
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {import('pg').Pool} pool
|
|
117
|
+
* @returns {Promise<Array<{name:string,owner:string}>>}
|
|
118
|
+
*/
|
|
119
|
+
export async function querySchemas(pool) {
|
|
120
|
+
const result = await pool.query(SCHEMAS_QUERY);
|
|
121
|
+
return result.rows;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {import('pg').Pool} pool
|
|
126
|
+
* @param {number} version
|
|
127
|
+
* @returns {Promise<Array>}
|
|
128
|
+
*/
|
|
129
|
+
export async function queryTables(pool, version = 150000) {
|
|
130
|
+
const hasOidsCol = (version < 120000) ? 'c.relhasoids' : 'false';
|
|
131
|
+
const query = TABLES_QUERY.replace('/* HAS_OIDS_PLACEHOLDER */', `${hasOidsCol} AS has_oids`);
|
|
132
|
+
const result = await pool.query(query);
|
|
133
|
+
return result.rows;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {import('pg').Pool} pool
|
|
138
|
+
* @param {number} version
|
|
139
|
+
* @returns {Promise<Array>}
|
|
140
|
+
*/
|
|
141
|
+
export async function queryColumns(pool, version = 150000) {
|
|
142
|
+
const hasCompression = (version >= 140000) ? "a.attcompression::text AS compression," : "NULL AS compression,";
|
|
143
|
+
const query = COLUMNS_QUERY.replace('/* COMPRESSION_PLACEHOLDER */', hasCompression);
|
|
144
|
+
const result = await pool.query(query);
|
|
145
|
+
return result.rows;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function queryToastOptions(pool) {
|
|
149
|
+
const result = await pool.query(TOAST_OPTIONS_QUERY);
|
|
150
|
+
return result.rows;
|
|
151
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const TABLESPACES_QUERY = `
|
|
2
|
+
SELECT
|
|
3
|
+
t.oid,
|
|
4
|
+
t.spcname AS name,
|
|
5
|
+
pg_catalog.pg_get_userbyid(t.spcowner) AS owner,
|
|
6
|
+
t.spclocation AS location,
|
|
7
|
+
t.spcdefault AS is_default,
|
|
8
|
+
t.spcacl AS acl,
|
|
9
|
+
t.spcoptions AS options
|
|
10
|
+
FROM pg_catalog.pg_tablespace t
|
|
11
|
+
WHERE t.spcname NOT LIKE 'pg_%'
|
|
12
|
+
ORDER BY t.spcname
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const DEFAULT_TABLESPACES = ['pg_default', 'pg_global'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('pg').Pool} pool
|
|
19
|
+
* @returns {Promise<Array>}
|
|
20
|
+
*/
|
|
21
|
+
export async function queryInterfaceTablespaces(pool) {
|
|
22
|
+
try {
|
|
23
|
+
const result = await pool.query(TABLESPACES_QUERY);
|
|
24
|
+
|
|
25
|
+
return result.rows.map(row => ({
|
|
26
|
+
name: row.name,
|
|
27
|
+
owner: row.owner,
|
|
28
|
+
location: row.location || undefined,
|
|
29
|
+
is_default: row.is_default || DEFAULT_TABLESPACES.includes(row.name),
|
|
30
|
+
acl: row.acl || undefined,
|
|
31
|
+
options: row.options || undefined,
|
|
32
|
+
}));
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const TEXT_SEARCH_CONFIGS_QUERY = `
|
|
2
|
+
SELECT
|
|
3
|
+
t.oid,
|
|
4
|
+
n.nspname AS schema,
|
|
5
|
+
t.cfgname AS name,
|
|
6
|
+
pg_catalog.pg_get_userbyid(t.cfgowner) AS owner,
|
|
7
|
+
format_type(t.cfgparser, NULL) AS parser,
|
|
8
|
+
n2.nspname AS parser_schema,
|
|
9
|
+
pg_catalog.obj_description(t.oid, 'pg_ts_config') AS comment
|
|
10
|
+
FROM pg_catalog.pg_ts_config t
|
|
11
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.cfgnamespace
|
|
12
|
+
LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = t.cfgparser
|
|
13
|
+
LEFT JOIN pg_catalog.pg_namespace n2 ON n2.oid = p.prsnamespace
|
|
14
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
15
|
+
ORDER BY n.nspname, t.cfgname
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const TEXT_SEARCH_CONFIG_MAP_QUERY = `
|
|
19
|
+
SELECT
|
|
20
|
+
n.nspname AS schema,
|
|
21
|
+
t.cfgname AS config_name,
|
|
22
|
+
m.maptokentype AS token_type,
|
|
23
|
+
m.mapseqno AS seq,
|
|
24
|
+
d.dictname AS dictionary,
|
|
25
|
+
nd.nspname AS dict_schema,
|
|
26
|
+
m.mapdict AS dict_oid
|
|
27
|
+
FROM pg_catalog.pg_ts_config_map m
|
|
28
|
+
JOIN pg_catalog.pg_ts_config t ON t.oid = m.mapcfg
|
|
29
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.cfgnamespace
|
|
30
|
+
JOIN pg_catalog.pg_ts_dict d ON d.oid = m.mapdict
|
|
31
|
+
JOIN pg_catalog.pg_namespace nd ON nd.oid = d.dictnamespace
|
|
32
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
33
|
+
ORDER BY n.nspname, t.cfgname, m.mapseqno
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const TEXT_SEARCH_DICTIONARIES_QUERY = `
|
|
37
|
+
SELECT
|
|
38
|
+
d.oid,
|
|
39
|
+
n.nspname AS schema,
|
|
40
|
+
d.dictname AS name,
|
|
41
|
+
pg_catalog.pg_get_userbyid(d.dictowner) AS owner,
|
|
42
|
+
format_type(d.dicttemplate, NULL) AS template,
|
|
43
|
+
n2.nspname AS template_schema,
|
|
44
|
+
d.dictinitoption AS options,
|
|
45
|
+
pg_catalog.obj_description(d.oid, 'pg_ts_dict') AS comment
|
|
46
|
+
FROM pg_catalog.pg_ts_dict d
|
|
47
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace
|
|
48
|
+
LEFT JOIN pg_catalog.pg_ts_template t ON t.oid = d.dicttemplate
|
|
49
|
+
LEFT JOIN pg_catalog.pg_namespace n2 ON n2.oid = t.tmplnamespace
|
|
50
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
51
|
+
ORDER BY n.nspname, d.dictname
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
const TEXT_SEARCH_PARSERS_QUERY = `
|
|
55
|
+
SELECT
|
|
56
|
+
p.oid,
|
|
57
|
+
n.nspname AS schema,
|
|
58
|
+
p.prsname AS name,
|
|
59
|
+
pg_catalog.pg_get_userbyid(p.prsowner) AS owner,
|
|
60
|
+
p.prsstart::regproc::text AS start,
|
|
61
|
+
p.prstoken::regproc::text AS get_token,
|
|
62
|
+
p.prsend::regproc::text AS end,
|
|
63
|
+
p.prsheadline::regproc::text AS headline,
|
|
64
|
+
p.prslextype::regproc::text AS lextypes,
|
|
65
|
+
pg_catalog.obj_description(p.oid, 'pg_ts_parser') AS comment
|
|
66
|
+
FROM pg_catalog.pg_ts_parser p
|
|
67
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.prsnamespace
|
|
68
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
69
|
+
ORDER BY n.nspname, p.prsname
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const TEXT_SEARCH_TEMPLATES_QUERY = `
|
|
73
|
+
SELECT
|
|
74
|
+
t.oid,
|
|
75
|
+
n.nspname AS schema,
|
|
76
|
+
t.tmplname AS name,
|
|
77
|
+
pg_catalog.pg_get_userbyid(t.tmplowner) AS owner,
|
|
78
|
+
t.tmplinit::regproc::text AS init,
|
|
79
|
+
t.tmpllexize::regproc::text AS lexize,
|
|
80
|
+
pg_catalog.obj_description(t.oid, 'pg_ts_template') AS comment
|
|
81
|
+
FROM pg_catalog.pg_ts_template t
|
|
82
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
|
|
83
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
84
|
+
ORDER BY n.nspname, t.tmplname
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {import('pg').Pool} pool
|
|
89
|
+
* @returns {Promise<{configs: Array, tokenMappings: Array}>}
|
|
90
|
+
*/
|
|
91
|
+
export async function queryTextSearchConfigs(pool) {
|
|
92
|
+
try {
|
|
93
|
+
const configsResult = await pool.query(TEXT_SEARCH_CONFIGS_QUERY);
|
|
94
|
+
let mappingsResult = { rows: [] };
|
|
95
|
+
try {
|
|
96
|
+
mappingsResult = await pool.query(TEXT_SEARCH_CONFIG_MAP_QUERY);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// May fail on older versions
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
configs: configsResult.rows,
|
|
102
|
+
tokenMappings: mappingsResult.rows,
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return { configs: [], tokenMappings: [] };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {import('pg').Pool} pool
|
|
111
|
+
* @returns {Promise<Array>}
|
|
112
|
+
*/
|
|
113
|
+
export async function queryTextSearchDictionaries(pool) {
|
|
114
|
+
try {
|
|
115
|
+
const result = await pool.query(TEXT_SEARCH_DICTIONARIES_QUERY);
|
|
116
|
+
return result.rows;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @param {import('pg').Pool} pool
|
|
124
|
+
* @returns {Promise<Array>}
|
|
125
|
+
*/
|
|
126
|
+
export async function queryTextSearchParsers(pool) {
|
|
127
|
+
try {
|
|
128
|
+
const result = await pool.query(TEXT_SEARCH_PARSERS_QUERY);
|
|
129
|
+
return result.rows;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {import('pg').Pool} pool
|
|
137
|
+
* @returns {Promise<Array>}
|
|
138
|
+
*/
|
|
139
|
+
export async function queryTextSearchTemplates(pool) {
|
|
140
|
+
try {
|
|
141
|
+
const result = await pool.query(TEXT_SEARCH_TEMPLATES_QUERY);
|
|
142
|
+
return result.rows;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const TRIGGERS_QUERY = `
|
|
2
|
+
SELECT t.tgname AS name,
|
|
3
|
+
n.nspname AS schema,
|
|
4
|
+
c.relname AS table_name,
|
|
5
|
+
tn.nspname AS table_schema,
|
|
6
|
+
CASE
|
|
7
|
+
WHEN (t.tgtype::int & 2) != 0 THEN 'BEFORE'
|
|
8
|
+
WHEN (t.tgtype::int & 64) != 0 THEN 'INSTEAD OF'
|
|
9
|
+
ELSE 'AFTER'
|
|
10
|
+
END AS timing,
|
|
11
|
+
array_remove(ARRAY[
|
|
12
|
+
CASE WHEN (t.tgtype::int & 4) != 0 THEN 'INSERT' ELSE NULL END,
|
|
13
|
+
CASE WHEN (t.tgtype::int & 8) != 0 THEN 'DELETE' ELSE NULL END,
|
|
14
|
+
CASE WHEN (t.tgtype::int & 16) != 0 THEN 'UPDATE' ELSE NULL END,
|
|
15
|
+
CASE WHEN (t.tgtype::int & 32) != 0 THEN 'TRUNCATE' ELSE NULL END
|
|
16
|
+
], NULL) AS events,
|
|
17
|
+
CASE WHEN (t.tgtype::int & 1) != 0 THEN 'ROW' ELSE 'STATEMENT' END AS level,
|
|
18
|
+
(t.tgtype::int & 1) != 0 AS is_for_each_row,
|
|
19
|
+
pg_catalog.pg_get_triggerdef(t.oid) AS definition,
|
|
20
|
+
CASE t.tgenabled
|
|
21
|
+
WHEN 'O' THEN 'ENABLED'
|
|
22
|
+
WHEN 'D' THEN 'DISABLED'
|
|
23
|
+
WHEN 'R' THEN 'REPLICA'
|
|
24
|
+
WHEN 'A' THEN 'ALWAYS'
|
|
25
|
+
END AS enabled,
|
|
26
|
+
p.proname AS function_name,
|
|
27
|
+
pn.nspname AS function_schema,
|
|
28
|
+
NULL AS when_condition,
|
|
29
|
+
t.tgconstraint IS NOT NULL AS is_constraint,
|
|
30
|
+
t.tgdeferrable AS is_deferrable,
|
|
31
|
+
t.tginitdeferred AS is_deferred,
|
|
32
|
+
(SELECT array_agg(a.attname)
|
|
33
|
+
FROM unnest(t.tgattr) AS uattnum
|
|
34
|
+
JOIN pg_attribute a ON a.attrelid = t.tgrelid AND a.attnum = uattnum
|
|
35
|
+
) AS update_of_columns,
|
|
36
|
+
t.tgoldtable AS old_table_name,
|
|
37
|
+
t.tgnewtable AS new_table_name,
|
|
38
|
+
pg_catalog.obj_description(t.oid, 'pg_trigger') AS comment
|
|
39
|
+
FROM pg_catalog.pg_trigger t
|
|
40
|
+
JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
|
|
41
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
42
|
+
JOIN pg_catalog.pg_namespace tn ON tn.oid = c.relnamespace
|
|
43
|
+
JOIN pg_catalog.pg_proc p ON p.oid = t.tgfoid
|
|
44
|
+
JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
45
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
46
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
47
|
+
ORDER BY tn.nspname, c.relname, t.tgname
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {import('pg').Pool} pool
|
|
52
|
+
* @returns {Promise<Array>}
|
|
53
|
+
*/
|
|
54
|
+
export async function queryTriggers(pool) {
|
|
55
|
+
const result = await pool.query(TRIGGERS_QUERY);
|
|
56
|
+
return result.rows.map(row => ({
|
|
57
|
+
...row,
|
|
58
|
+
events: (row.events || []).filter(Boolean),
|
|
59
|
+
function_call: `${row.function_schema}.${row.function_name}`,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const ENUMS_QUERY = `
|
|
2
|
+
SELECT t.typname AS name,
|
|
3
|
+
n.nspname AS schema,
|
|
4
|
+
t.typowner::regrole::text AS owner,
|
|
5
|
+
array_agg(e.enumlabel ORDER BY e.enumsortorder)::text[] AS enum_values,
|
|
6
|
+
pg_catalog.obj_description(t.oid, 'pg_type') AS comment,
|
|
7
|
+
t.typacl AS privileges,
|
|
8
|
+
t.typarray::regtype AS array_type
|
|
9
|
+
FROM pg_catalog.pg_type t
|
|
10
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
11
|
+
JOIN pg_catalog.pg_enum e ON e.enumtypid = t.oid
|
|
12
|
+
WHERE t.typtype = 'e'
|
|
13
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
14
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
15
|
+
GROUP BY t.oid, t.typname, n.nspname, t.typowner, t.typacl, t.typarray
|
|
16
|
+
ORDER BY n.nspname, t.typname
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const COMPOSITES_QUERY = `
|
|
20
|
+
SELECT t.typname AS name,
|
|
21
|
+
n.nspname AS schema,
|
|
22
|
+
t.typowner::regrole::text AS owner,
|
|
23
|
+
array_agg(
|
|
24
|
+
json_build_object(
|
|
25
|
+
'name', a.attname,
|
|
26
|
+
'type', format_type(a.atttypid, a.atttypmod),
|
|
27
|
+
'collation', COALESCE(coll.collname, NULL)
|
|
28
|
+
)
|
|
29
|
+
ORDER BY a.attnum
|
|
30
|
+
) AS attributes,
|
|
31
|
+
pg_catalog.obj_description(t.oid, 'pg_type') AS comment
|
|
32
|
+
FROM pg_catalog.pg_type t
|
|
33
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
34
|
+
JOIN pg_catalog.pg_class c ON c.oid = t.typrelid
|
|
35
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
|
|
36
|
+
LEFT JOIN pg_catalog.pg_collation coll ON coll.oid = a.attcollation
|
|
37
|
+
WHERE t.typtype = 'c'
|
|
38
|
+
AND c.relkind = 'c'
|
|
39
|
+
AND a.attnum > 0
|
|
40
|
+
AND NOT a.attisdropped
|
|
41
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
42
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
43
|
+
GROUP BY t.oid, t.typname, n.nspname, t.typowner
|
|
44
|
+
ORDER BY n.nspname, t.typname
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
const DOMAINS_QUERY = `
|
|
48
|
+
SELECT t.typname AS name,
|
|
49
|
+
n.nspname AS schema,
|
|
50
|
+
t.typowner::regrole::text AS owner,
|
|
51
|
+
format_type(t.typbasetype, t.typtypmod) AS base_type,
|
|
52
|
+
(SELECT n2.nspname FROM pg_type bt JOIN pg_namespace n2 ON n2.oid = bt.typnamespace WHERE bt.oid = t.typbasetype) AS base_type_schema,
|
|
53
|
+
t.typnotnull AS not_null,
|
|
54
|
+
pg_catalog.pg_get_expr(t.typdefaultbin, t.typbasetype) AS default_value,
|
|
55
|
+
pg_catalog.pg_get_constraintdef(con.oid) AS check_constraint,
|
|
56
|
+
pg_catalog.obj_description(t.oid, 'pg_type') AS comment,
|
|
57
|
+
CASE WHEN t.typcollation != 0 THEN t.typcollation::regcollation::text ELSE NULL END AS collation,
|
|
58
|
+
t.typacl AS privileges,
|
|
59
|
+
COALESCE(con.convalidated, true) AS is_validated,
|
|
60
|
+
t.typtypmod AS typmod,
|
|
61
|
+
bt.typlen AS length
|
|
62
|
+
FROM pg_catalog.pg_type t
|
|
63
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
64
|
+
LEFT JOIN pg_catalog.pg_constraint con ON con.contypid = t.oid AND con.contype = 'c'
|
|
65
|
+
LEFT JOIN pg_catalog.pg_type bt ON bt.oid = t.typbasetype
|
|
66
|
+
WHERE t.typtype = 'd'
|
|
67
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
68
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
69
|
+
ORDER BY n.nspname, t.typname
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
const RANGES_QUERY = `
|
|
73
|
+
SELECT t.typname AS name,
|
|
74
|
+
n.nspname AS schema,
|
|
75
|
+
t.typowner::regrole::text AS owner,
|
|
76
|
+
format_type(r.rngsubtype, NULL) AS subtype,
|
|
77
|
+
(SELECT n2.nspname FROM pg_type st JOIN pg_namespace n2 ON n2.oid = st.typnamespace WHERE st.oid = r.rngsubtype) AS subtype_schema,
|
|
78
|
+
r.rngmultitypid::regtype AS multirange_type,
|
|
79
|
+
CASE WHEN r.rngcollation != 0 THEN r.rngcollation::regcollation::text ELSE NULL END AS collation,
|
|
80
|
+
opc.opcname AS subtype_opclass,
|
|
81
|
+
CASE WHEN r.rngsubdiff != 0 THEN r.rngsubdiff::regproc::text ELSE NULL END AS subtype_diff,
|
|
82
|
+
CASE WHEN r.rngcanonical != 0 THEN r.rngcanonical::regproc::text ELSE NULL END AS canonical_function,
|
|
83
|
+
pg_catalog.obj_description(t.oid, 'pg_type') AS comment,
|
|
84
|
+
t.typacl AS privileges
|
|
85
|
+
FROM pg_catalog.pg_range r
|
|
86
|
+
JOIN pg_catalog.pg_type t ON t.oid = r.rngtypid
|
|
87
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
88
|
+
LEFT JOIN pg_catalog.pg_opclass opc ON opc.oid = r.rngsubopc
|
|
89
|
+
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
90
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
91
|
+
ORDER BY n.nspname, t.typname
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {import('pg').Pool} pool
|
|
96
|
+
* @returns {Promise<{enums:Array,composites:Array,domains:Array,ranges:Array}>}
|
|
97
|
+
*/
|
|
98
|
+
export async function queryTypes(pool) {
|
|
99
|
+
const [enums, composites, domains, ranges] = await Promise.all([
|
|
100
|
+
pool.query(ENUMS_QUERY).then(r => r.rows),
|
|
101
|
+
pool.query(COMPOSITES_QUERY).then(r => r.rows),
|
|
102
|
+
pool.query(DOMAINS_QUERY).then(r => r.rows),
|
|
103
|
+
pool.query(RANGES_QUERY).then(r => r.rows),
|
|
104
|
+
]);
|
|
105
|
+
return { enums, composites, domains, ranges };
|
|
106
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const VIEWS_QUERY = `
|
|
2
|
+
SELECT c.relname AS name,
|
|
3
|
+
n.nspname AS schema,
|
|
4
|
+
c.relowner::regrole::text AS owner,
|
|
5
|
+
pg_catalog.pg_get_viewdef(c.oid, true) AS definition,
|
|
6
|
+
CASE c.relkind WHEN 'v' THEN 'VIEW' ELSE 'MATERIALIZED_VIEW' END AS kind,
|
|
7
|
+
CASE
|
|
8
|
+
WHEN c.relkind = 'v' AND c.relchecks > 0 THEN
|
|
9
|
+
CASE WHEN EXISTS (
|
|
10
|
+
SELECT 1 FROM pg_catalog.pg_rewrite r
|
|
11
|
+
WHERE r.ev_class = c.oid AND r.ev_type = '1'
|
|
12
|
+
AND pg_catalog.pg_get_expr(r.ev_qual, r.ev_class) LIKE '%check_option=cascaded%'
|
|
13
|
+
) THEN 'CASCADED'
|
|
14
|
+
WHEN EXISTS (
|
|
15
|
+
SELECT 1 FROM pg_catalog.pg_rewrite r
|
|
16
|
+
WHERE r.ev_class = c.oid AND r.ev_type = '1'
|
|
17
|
+
AND pg_catalog.pg_get_expr(r.ev_qual, r.ev_class) LIKE '%check_option=local%'
|
|
18
|
+
) THEN 'LOCAL'
|
|
19
|
+
ELSE NULL
|
|
20
|
+
END
|
|
21
|
+
ELSE NULL
|
|
22
|
+
END AS check_option,
|
|
23
|
+
c.reloptions AS rel_options,
|
|
24
|
+
c.relacl AS privileges,
|
|
25
|
+
pg_catalog.obj_description(c.oid, 'pg_class') AS comment
|
|
26
|
+
FROM pg_catalog.pg_class c
|
|
27
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
28
|
+
WHERE c.relkind IN ('v', 'm')
|
|
29
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
30
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
31
|
+
ORDER BY n.nspname, c.relname
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const VIEW_COLUMNS_QUERY = `
|
|
35
|
+
SELECT n.nspname AS schema,
|
|
36
|
+
c.relname AS name,
|
|
37
|
+
a.attname AS column_name,
|
|
38
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) AS column_type,
|
|
39
|
+
NOT a.attnotnull AS nullable,
|
|
40
|
+
pg_catalog.pg_get_expr(d.adbin, d.adrelid) AS default_value
|
|
41
|
+
FROM pg_catalog.pg_class c
|
|
42
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
43
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
|
|
44
|
+
LEFT JOIN pg_catalog.pg_attrdef d ON d.adrelid = a.attrelid AND d.adnum = a.attnum
|
|
45
|
+
WHERE c.relkind = 'v'
|
|
46
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
47
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
48
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
49
|
+
ORDER BY n.nspname, c.relname, a.attnum
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const MATVIEW_EXTRA_QUERY = `
|
|
53
|
+
SELECT c.relname AS name,
|
|
54
|
+
n.nspname AS schema,
|
|
55
|
+
c.relispopulated AS is_populated,
|
|
56
|
+
ts.spcname AS tablespace,
|
|
57
|
+
c.reloptions AS storage_options,
|
|
58
|
+
c.relacl AS privileges
|
|
59
|
+
FROM pg_catalog.pg_class c
|
|
60
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
61
|
+
LEFT JOIN pg_catalog.pg_tablespace ts ON ts.oid = c.reltablespace
|
|
62
|
+
WHERE c.relkind = 'm'
|
|
63
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
64
|
+
AND n.nspname NOT LIKE 'pg_temp_%'
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const MATVIEW_COLUMNS_QUERY = `
|
|
68
|
+
SELECT n.nspname AS schema,
|
|
69
|
+
c.relname AS name,
|
|
70
|
+
a.attname AS column_name,
|
|
71
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod) AS column_type,
|
|
72
|
+
NOT a.attnotnull AS nullable
|
|
73
|
+
FROM pg_catalog.pg_class c
|
|
74
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
75
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
|
|
76
|
+
WHERE c.relkind = 'm'
|
|
77
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
78
|
+
AND 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, a.attnum
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {import('pg').Pool} pool
|
|
86
|
+
* @returns {Promise<{views:Array,materializedViews:Array}>}
|
|
87
|
+
*/
|
|
88
|
+
export async function queryViews(pool) {
|
|
89
|
+
const viewResult = await pool.query(VIEWS_QUERY);
|
|
90
|
+
const matResult = await pool.query(MATVIEW_EXTRA_QUERY);
|
|
91
|
+
const viewColsResult = await pool.query(VIEW_COLUMNS_QUERY);
|
|
92
|
+
const matColsResult = await pool.query(MATVIEW_COLUMNS_QUERY);
|
|
93
|
+
|
|
94
|
+
const matExtras = new Map();
|
|
95
|
+
for (const row of matResult.rows) {
|
|
96
|
+
matExtras.set(`${row.schema}.${row.name}`, row);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const viewColumns = new Map();
|
|
100
|
+
for (const row of viewColsResult.rows) {
|
|
101
|
+
const key = `${row.schema}.${row.name}`;
|
|
102
|
+
if (!viewColumns.has(key)) viewColumns.set(key, []);
|
|
103
|
+
viewColumns.get(key).push({
|
|
104
|
+
name: row.column_name,
|
|
105
|
+
type: row.column_type,
|
|
106
|
+
nullable: row.nullable,
|
|
107
|
+
default: row.default_value || undefined,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const matColumns = new Map();
|
|
112
|
+
for (const row of matColsResult.rows) {
|
|
113
|
+
const key = `${row.schema}.${row.name}`;
|
|
114
|
+
if (!matColumns.has(key)) matColumns.set(key, []);
|
|
115
|
+
matColumns.get(key).push({
|
|
116
|
+
name: row.column_name,
|
|
117
|
+
type: row.column_type,
|
|
118
|
+
nullable: row.nullable,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const views = [];
|
|
123
|
+
const materializedViews = [];
|
|
124
|
+
|
|
125
|
+
for (const row of viewResult.rows) {
|
|
126
|
+
const key = `${row.schema}.${row.name}`;
|
|
127
|
+
if (row.kind === 'VIEW') {
|
|
128
|
+
views.push({
|
|
129
|
+
...row,
|
|
130
|
+
columns: viewColumns.get(key) || [],
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
const extra = matExtras.get(key) || {};
|
|
134
|
+
materializedViews.push({
|
|
135
|
+
...row,
|
|
136
|
+
is_populated: extra.is_populated ?? true,
|
|
137
|
+
tablespace: extra.tablespace ?? null,
|
|
138
|
+
storage_options: extra.storage_options ?? null,
|
|
139
|
+
columns: matColumns.get(key) || [],
|
|
140
|
+
privileges: extra.privileges ?? null,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { views, materializedViews };
|
|
146
|
+
}
|