@mikro-orm/sql 7.0.2 → 7.0.3-dev.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/AbstractSqlConnection.d.ts +58 -94
- package/AbstractSqlConnection.js +238 -235
- package/AbstractSqlDriver.d.ts +155 -411
- package/AbstractSqlDriver.js +1937 -2061
- package/AbstractSqlPlatform.d.ts +73 -83
- package/AbstractSqlPlatform.js +158 -162
- package/PivotCollectionPersister.d.ts +15 -33
- package/PivotCollectionPersister.js +160 -158
- package/SqlEntityManager.d.ts +22 -67
- package/SqlEntityManager.js +38 -54
- package/SqlEntityRepository.d.ts +14 -14
- package/SqlEntityRepository.js +23 -23
- package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +12 -12
- package/dialects/mssql/MsSqlNativeQueryBuilder.js +194 -192
- package/dialects/mysql/BaseMySqlPlatform.d.ts +45 -64
- package/dialects/mysql/BaseMySqlPlatform.js +131 -134
- package/dialects/mysql/MySqlExceptionConverter.d.ts +6 -6
- package/dialects/mysql/MySqlExceptionConverter.js +77 -91
- package/dialects/mysql/MySqlNativeQueryBuilder.d.ts +3 -3
- package/dialects/mysql/MySqlNativeQueryBuilder.js +69 -66
- package/dialects/mysql/MySqlSchemaHelper.d.ts +39 -39
- package/dialects/mysql/MySqlSchemaHelper.js +319 -327
- package/dialects/oracledb/OracleDialect.d.ts +52 -81
- package/dialects/oracledb/OracleDialect.js +149 -155
- package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +12 -12
- package/dialects/oracledb/OracleNativeQueryBuilder.js +236 -232
- package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +105 -108
- package/dialects/postgresql/BasePostgreSqlPlatform.js +350 -351
- package/dialects/postgresql/FullTextType.d.ts +6 -10
- package/dialects/postgresql/FullTextType.js +51 -51
- package/dialects/postgresql/PostgreSqlExceptionConverter.d.ts +5 -5
- package/dialects/postgresql/PostgreSqlExceptionConverter.js +43 -55
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.d.ts +1 -1
- package/dialects/postgresql/PostgreSqlNativeQueryBuilder.js +4 -4
- package/dialects/postgresql/PostgreSqlSchemaHelper.d.ts +82 -102
- package/dialects/postgresql/PostgreSqlSchemaHelper.js +683 -711
- package/dialects/sqlite/BaseSqliteConnection.d.ts +5 -3
- package/dialects/sqlite/BaseSqliteConnection.js +19 -21
- package/dialects/sqlite/NodeSqliteDialect.d.ts +1 -1
- package/dialects/sqlite/NodeSqliteDialect.js +23 -23
- package/dialects/sqlite/SqliteDriver.d.ts +1 -1
- package/dialects/sqlite/SqliteDriver.js +3 -3
- package/dialects/sqlite/SqliteExceptionConverter.d.ts +6 -6
- package/dialects/sqlite/SqliteExceptionConverter.js +51 -67
- package/dialects/sqlite/SqliteNativeQueryBuilder.d.ts +2 -2
- package/dialects/sqlite/SqliteNativeQueryBuilder.js +7 -7
- package/dialects/sqlite/SqlitePlatform.d.ts +72 -63
- package/dialects/sqlite/SqlitePlatform.js +139 -139
- package/dialects/sqlite/SqliteSchemaHelper.d.ts +60 -70
- package/dialects/sqlite/SqliteSchemaHelper.js +520 -533
- package/package.json +2 -2
- package/plugin/index.d.ts +35 -42
- package/plugin/index.js +36 -43
- package/plugin/transformer.d.ts +94 -117
- package/plugin/transformer.js +881 -890
- package/query/ArrayCriteriaNode.d.ts +4 -4
- package/query/ArrayCriteriaNode.js +18 -18
- package/query/CriteriaNode.d.ts +25 -35
- package/query/CriteriaNode.js +123 -133
- package/query/CriteriaNodeFactory.d.ts +6 -49
- package/query/CriteriaNodeFactory.js +94 -97
- package/query/NativeQueryBuilder.d.ts +118 -118
- package/query/NativeQueryBuilder.js +480 -484
- package/query/ObjectCriteriaNode.d.ts +12 -12
- package/query/ObjectCriteriaNode.js +282 -298
- package/query/QueryBuilder.d.ts +904 -1546
- package/query/QueryBuilder.js +2145 -2270
- package/query/QueryBuilderHelper.d.ts +72 -153
- package/query/QueryBuilderHelper.js +1028 -1079
- package/query/ScalarCriteriaNode.d.ts +3 -3
- package/query/ScalarCriteriaNode.js +46 -53
- package/query/enums.d.ts +14 -14
- package/query/enums.js +14 -14
- package/query/raw.d.ts +6 -16
- package/query/raw.js +10 -10
- package/schema/DatabaseSchema.d.ts +50 -73
- package/schema/DatabaseSchema.js +307 -331
- package/schema/DatabaseTable.d.ts +73 -96
- package/schema/DatabaseTable.js +927 -1012
- package/schema/SchemaComparator.d.ts +54 -58
- package/schema/SchemaComparator.js +719 -745
- package/schema/SchemaHelper.d.ts +95 -109
- package/schema/SchemaHelper.js +659 -675
- package/schema/SqlSchemaGenerator.d.ts +58 -78
- package/schema/SqlSchemaGenerator.js +501 -535
- package/typings.d.ts +266 -380
|
@@ -1,256 +1,250 @@
|
|
|
1
|
-
import { DeferMode, EnumType, Type, Utils } from '@mikro-orm/core';
|
|
1
|
+
import { DeferMode, EnumType, Type, Utils, } from '@mikro-orm/core';
|
|
2
2
|
import { SchemaHelper } from '../../schema/SchemaHelper.js';
|
|
3
3
|
/** PostGIS system views that should be automatically ignored */
|
|
4
4
|
const POSTGIS_VIEWS = ['geography_columns', 'geometry_columns'];
|
|
5
5
|
export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`(select pg_catalog.obj_description(c.oid) from pg_catalog.pg_class c
|
|
6
|
+
static DEFAULT_VALUES = {
|
|
7
|
+
'now()': ['now()', 'current_timestamp'],
|
|
8
|
+
'current_timestamp(?)': ['current_timestamp(?)'],
|
|
9
|
+
"('now'::text)::timestamp(?) with time zone": ['current_timestamp(?)'],
|
|
10
|
+
"('now'::text)::timestamp(?) without time zone": ['current_timestamp(?)'],
|
|
11
|
+
'null::character varying': ['null'],
|
|
12
|
+
'null::timestamp with time zone': ['null'],
|
|
13
|
+
'null::timestamp without time zone': ['null'],
|
|
14
|
+
};
|
|
15
|
+
getSchemaBeginning(charset, disableForeignKeys) {
|
|
16
|
+
if (disableForeignKeys) {
|
|
17
|
+
return `set names '${charset}';\n${this.disableForeignKeysSQL()}\n\n`;
|
|
18
|
+
}
|
|
19
|
+
return `set names '${charset}';\n\n`;
|
|
20
|
+
}
|
|
21
|
+
getCreateDatabaseSQL(name) {
|
|
22
|
+
return `create database ${this.quote(name)}`;
|
|
23
|
+
}
|
|
24
|
+
getListTablesSQL() {
|
|
25
|
+
return (`select table_name, table_schema as schema_name, ` +
|
|
26
|
+
`(select pg_catalog.obj_description(c.oid) from pg_catalog.pg_class c
|
|
28
27
|
where c.oid = (select ('"' || table_schema || '"."' || table_name || '"')::regclass::oid) and c.relname = table_name) as table_comment ` +
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
/* v8 ignore next - pg_get_indexdef always returns balanced parentheses */
|
|
250
|
-
return '';
|
|
251
|
-
}
|
|
252
|
-
async getAllColumns(connection, tablesBySchemas, nativeEnums) {
|
|
253
|
-
const sql = `select table_schema as schema_name, table_name, column_name,
|
|
28
|
+
`from information_schema.tables ` +
|
|
29
|
+
`where ${this.getIgnoredNamespacesConditionSQL('table_schema')} ` +
|
|
30
|
+
`and table_name != 'geometry_columns' and table_name != 'spatial_ref_sys' and table_type != 'VIEW' ` +
|
|
31
|
+
`and table_name not in (select inhrelid::regclass::text from pg_inherits) ` +
|
|
32
|
+
`order by table_name`);
|
|
33
|
+
}
|
|
34
|
+
getIgnoredViewsCondition() {
|
|
35
|
+
return POSTGIS_VIEWS.map(v => `table_name != '${v}'`).join(' and ');
|
|
36
|
+
}
|
|
37
|
+
getListViewsSQL() {
|
|
38
|
+
return (`select table_name as view_name, table_schema as schema_name, view_definition ` +
|
|
39
|
+
`from information_schema.views ` +
|
|
40
|
+
`where ${this.getIgnoredNamespacesConditionSQL('table_schema')} ` +
|
|
41
|
+
`and ${this.getIgnoredViewsCondition()} ` +
|
|
42
|
+
`order by table_name`);
|
|
43
|
+
}
|
|
44
|
+
async loadViews(schema, connection) {
|
|
45
|
+
const views = await connection.execute(this.getListViewsSQL());
|
|
46
|
+
for (const view of views) {
|
|
47
|
+
const definition = view.view_definition?.trim().replace(/;$/, '') ?? '';
|
|
48
|
+
if (definition) {
|
|
49
|
+
schema.addView(view.view_name, view.schema_name, definition);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getListMaterializedViewsSQL() {
|
|
54
|
+
return (`select matviewname as view_name, schemaname as schema_name, definition as view_definition ` +
|
|
55
|
+
`from pg_matviews ` +
|
|
56
|
+
`where ${this.getIgnoredNamespacesConditionSQL('schemaname')} ` +
|
|
57
|
+
`order by matviewname`);
|
|
58
|
+
}
|
|
59
|
+
async loadMaterializedViews(schema, connection, schemaName) {
|
|
60
|
+
const views = await connection.execute(this.getListMaterializedViewsSQL());
|
|
61
|
+
for (const view of views) {
|
|
62
|
+
const definition = view.view_definition?.trim().replace(/;$/, '') ?? '';
|
|
63
|
+
if (definition) {
|
|
64
|
+
schema.addView(view.view_name, view.schema_name, definition, true);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
createMaterializedView(name, schema, definition, withData = true) {
|
|
69
|
+
const viewName = this.quote(this.getTableName(name, schema));
|
|
70
|
+
const dataClause = withData ? ' with data' : ' with no data';
|
|
71
|
+
return `create materialized view ${viewName} as ${definition}${dataClause}`;
|
|
72
|
+
}
|
|
73
|
+
dropMaterializedViewIfExists(name, schema) {
|
|
74
|
+
return `drop materialized view if exists ${this.quote(this.getTableName(name, schema))} cascade`;
|
|
75
|
+
}
|
|
76
|
+
refreshMaterializedView(name, schema, concurrently = false) {
|
|
77
|
+
const concurrent = concurrently ? ' concurrently' : '';
|
|
78
|
+
return `refresh materialized view${concurrent} ${this.quote(this.getTableName(name, schema))}`;
|
|
79
|
+
}
|
|
80
|
+
async getNamespaces(connection) {
|
|
81
|
+
const sql = `select schema_name from information_schema.schemata ` +
|
|
82
|
+
`where ${this.getIgnoredNamespacesConditionSQL()} ` +
|
|
83
|
+
`order by schema_name`;
|
|
84
|
+
const res = await connection.execute(sql);
|
|
85
|
+
return res.map(row => row.schema_name);
|
|
86
|
+
}
|
|
87
|
+
getIgnoredNamespacesConditionSQL(column = 'schema_name') {
|
|
88
|
+
const ignored = [
|
|
89
|
+
'information_schema',
|
|
90
|
+
'tiger',
|
|
91
|
+
'topology',
|
|
92
|
+
/* v8 ignore next */
|
|
93
|
+
...(this.platform.getConfig().get('schemaGenerator').ignoreSchema ?? []),
|
|
94
|
+
]
|
|
95
|
+
.map(s => this.platform.quoteValue(s))
|
|
96
|
+
.join(', ');
|
|
97
|
+
const ignoredPrefixes = ['pg_', 'crdb_', '_timescaledb_'].map(p => `"${column}" not like '${p}%'`).join(' and ');
|
|
98
|
+
return `${ignoredPrefixes} and "${column}" not in (${ignored})`;
|
|
99
|
+
}
|
|
100
|
+
async loadInformationSchema(schema, connection, tables, schemas) {
|
|
101
|
+
schemas ??= tables.length === 0 ? [schema.name] : tables.map(t => t.schema_name);
|
|
102
|
+
const nativeEnums = await this.getNativeEnumDefinitions(connection, schemas);
|
|
103
|
+
schema.setNativeEnums(nativeEnums);
|
|
104
|
+
if (tables.length === 0) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const tablesBySchema = this.getTablesGroupedBySchemas(tables);
|
|
108
|
+
const columns = await this.getAllColumns(connection, tablesBySchema, nativeEnums);
|
|
109
|
+
const indexes = await this.getAllIndexes(connection, tables);
|
|
110
|
+
const checks = await this.getAllChecks(connection, tablesBySchema);
|
|
111
|
+
const fks = await this.getAllForeignKeys(connection, tablesBySchema);
|
|
112
|
+
for (const t of tables) {
|
|
113
|
+
const key = this.getTableKey(t);
|
|
114
|
+
const table = schema.addTable(t.table_name, t.schema_name, t.table_comment);
|
|
115
|
+
const pks = await this.getPrimaryKeys(connection, indexes[key], table.name, table.schema);
|
|
116
|
+
const enums = this.getEnumDefinitions(checks[key] ?? []);
|
|
117
|
+
if (columns[key]) {
|
|
118
|
+
table.init(columns[key], indexes[key], checks[key], pks, fks[key], enums);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async getAllIndexes(connection, tables) {
|
|
123
|
+
const sql = this.getIndexesSQL(tables);
|
|
124
|
+
const unquote = (str) => str.replace(/['"`]/g, '');
|
|
125
|
+
const allIndexes = await connection.execute(sql);
|
|
126
|
+
const ret = {};
|
|
127
|
+
for (const index of allIndexes) {
|
|
128
|
+
const key = this.getTableKey(index);
|
|
129
|
+
// Extract INCLUDE columns from expression first, to filter them from key columns
|
|
130
|
+
const includeMatch = index.expression?.match(/include\s*\(([^)]+)\)/i);
|
|
131
|
+
const includeColumns = includeMatch ? includeMatch[1].split(',').map((col) => unquote(col.trim())) : [];
|
|
132
|
+
// Filter out INCLUDE columns from the column definitions to get only key columns
|
|
133
|
+
const keyColumnDefs = index.index_def.filter((col) => !includeColumns.includes(unquote(col)));
|
|
134
|
+
// Parse sort order and NULLS ordering from the full expression
|
|
135
|
+
// pg_get_indexdef individual columns don't include sort modifiers, so we parse from full expression
|
|
136
|
+
const columns = this.parseIndexColumnsFromExpression(index.expression, keyColumnDefs, unquote);
|
|
137
|
+
const columnNames = columns.map(col => col.name);
|
|
138
|
+
const hasAdvancedColumnOptions = columns.some(col => col.sort || col.nulls || col.collation);
|
|
139
|
+
const indexDef = {
|
|
140
|
+
columnNames,
|
|
141
|
+
composite: columnNames.length > 1,
|
|
142
|
+
// JSON columns can have unique index but not unique constraint, and we need to distinguish those, so we can properly drop them
|
|
143
|
+
constraint: index.contype === 'u',
|
|
144
|
+
keyName: index.constraint_name,
|
|
145
|
+
unique: index.unique,
|
|
146
|
+
primary: index.primary,
|
|
147
|
+
};
|
|
148
|
+
// Add columns array if there are advanced options
|
|
149
|
+
if (hasAdvancedColumnOptions) {
|
|
150
|
+
indexDef.columns = columns;
|
|
151
|
+
}
|
|
152
|
+
if (index.condeferrable) {
|
|
153
|
+
indexDef.deferMode = index.condeferred ? DeferMode.INITIALLY_DEFERRED : DeferMode.INITIALLY_IMMEDIATE;
|
|
154
|
+
}
|
|
155
|
+
if (index.index_def.some((col) => /[(): ,"'`]/.exec(col)) || index.expression?.match(/ where /i)) {
|
|
156
|
+
indexDef.expression = index.expression;
|
|
157
|
+
}
|
|
158
|
+
if (index.deferrable) {
|
|
159
|
+
indexDef.deferMode = index.initially_deferred ? DeferMode.INITIALLY_DEFERRED : DeferMode.INITIALLY_IMMEDIATE;
|
|
160
|
+
}
|
|
161
|
+
// Extract fillFactor from reloptions
|
|
162
|
+
if (index.reloptions) {
|
|
163
|
+
const fillFactorMatch = index.reloptions.find((opt) => opt.startsWith('fillfactor='));
|
|
164
|
+
if (fillFactorMatch) {
|
|
165
|
+
indexDef.fillFactor = parseInt(fillFactorMatch.split('=')[1], 10);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Add INCLUDE columns (already extracted above)
|
|
169
|
+
if (includeColumns.length > 0) {
|
|
170
|
+
indexDef.include = includeColumns;
|
|
171
|
+
}
|
|
172
|
+
// Add index type if not btree (the default)
|
|
173
|
+
if (index.index_type && index.index_type !== 'btree') {
|
|
174
|
+
indexDef.type = index.index_type;
|
|
175
|
+
}
|
|
176
|
+
ret[key] ??= [];
|
|
177
|
+
ret[key].push(indexDef);
|
|
178
|
+
}
|
|
179
|
+
return ret;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Parses column definitions from the full CREATE INDEX expression.
|
|
183
|
+
* Since pg_get_indexdef(oid, col_num, true) doesn't include sort modifiers,
|
|
184
|
+
* we extract them from the full expression instead.
|
|
185
|
+
*
|
|
186
|
+
* We use columnDefs (from individual pg_get_indexdef calls) as the source
|
|
187
|
+
* of column names, and find their modifiers in the expression.
|
|
188
|
+
*/
|
|
189
|
+
parseIndexColumnsFromExpression(expression, columnDefs, unquote) {
|
|
190
|
+
// Extract just the column list from the expression (between first parens after USING)
|
|
191
|
+
// Pattern: ... USING method (...columns...) [INCLUDE (...)] [WHERE ...]
|
|
192
|
+
// Note: pg_get_indexdef always returns a valid expression with USING clause
|
|
193
|
+
const usingMatch = /using\s+\w+\s*\(/i.exec(expression);
|
|
194
|
+
const startIdx = usingMatch.index + usingMatch[0].length - 1; // Position of opening (
|
|
195
|
+
const columnsStr = this.extractParenthesizedContent(expression, startIdx);
|
|
196
|
+
// Use the column names from columnDefs and find their modifiers in the expression
|
|
197
|
+
return columnDefs.map(colDef => {
|
|
198
|
+
const name = unquote(colDef);
|
|
199
|
+
const result = { name };
|
|
200
|
+
// Find this column in the expression and extract modifiers
|
|
201
|
+
// Create a pattern that matches the column name (quoted or unquoted) followed by modifiers
|
|
202
|
+
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
203
|
+
const colPattern = new RegExp(`"?${escapedName}"?\\s*([^,)]*?)(?:,|$)`, 'i');
|
|
204
|
+
const colMatch = columnsStr.match(colPattern);
|
|
205
|
+
if (colMatch) {
|
|
206
|
+
const modifiers = colMatch[1];
|
|
207
|
+
// Extract sort order (PostgreSQL omits ASC in output as it's the default)
|
|
208
|
+
if (/\bdesc\b/i.test(modifiers)) {
|
|
209
|
+
result.sort = 'DESC';
|
|
210
|
+
}
|
|
211
|
+
// Extract NULLS ordering
|
|
212
|
+
const nullsMatch = /nulls\s+(first|last)/i.exec(modifiers);
|
|
213
|
+
if (nullsMatch) {
|
|
214
|
+
result.nulls = nullsMatch[1].toUpperCase();
|
|
215
|
+
}
|
|
216
|
+
// Extract collation
|
|
217
|
+
const collateMatch = /collate\s+"?([^"\s,)]+)"?/i.exec(modifiers);
|
|
218
|
+
if (collateMatch) {
|
|
219
|
+
result.collation = collateMatch[1];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Extracts the content inside parentheses starting at the given position.
|
|
227
|
+
* Handles nested parentheses correctly.
|
|
228
|
+
*/
|
|
229
|
+
extractParenthesizedContent(str, startIdx) {
|
|
230
|
+
let depth = 0;
|
|
231
|
+
const start = startIdx + 1;
|
|
232
|
+
for (let i = startIdx; i < str.length; i++) {
|
|
233
|
+
if (str[i] === '(') {
|
|
234
|
+
depth++;
|
|
235
|
+
}
|
|
236
|
+
else if (str[i] === ')') {
|
|
237
|
+
depth--;
|
|
238
|
+
if (depth === 0) {
|
|
239
|
+
return str.slice(start, i);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/* v8 ignore next - pg_get_indexdef always returns balanced parentheses */
|
|
244
|
+
return '';
|
|
245
|
+
}
|
|
246
|
+
async getAllColumns(connection, tablesBySchemas, nativeEnums) {
|
|
247
|
+
const sql = `select table_schema as schema_name, table_name, column_name,
|
|
254
248
|
column_default,
|
|
255
249
|
is_nullable,
|
|
256
250
|
udt_name,
|
|
@@ -269,93 +263,89 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
269
263
|
join pg_attribute pga on pgc.oid = pga.attrelid and cols.column_name = pga.attname
|
|
270
264
|
where (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(table_schema = ${this.platform.quoteValue(schema)} and table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}))`).join(' or ')})
|
|
271
265
|
order by ordinal_position`;
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return ret;
|
|
356
|
-
}
|
|
357
|
-
async getAllForeignKeys(connection, tablesBySchemas) {
|
|
358
|
-
const sql = `select nsp1.nspname schema_name, cls1.relname table_name, nsp2.nspname referenced_schema_name,
|
|
266
|
+
const allColumns = await connection.execute(sql);
|
|
267
|
+
const str = (val) => (val != null ? '' + val : val);
|
|
268
|
+
const ret = {};
|
|
269
|
+
for (const col of allColumns) {
|
|
270
|
+
const mappedType = connection.getPlatform().getMappedType(col.data_type);
|
|
271
|
+
const increments = (col.column_default?.includes('nextval') || col.is_identity === 'YES') &&
|
|
272
|
+
connection.getPlatform().isNumericColumn(mappedType);
|
|
273
|
+
const key = this.getTableKey(col);
|
|
274
|
+
ret[key] ??= [];
|
|
275
|
+
let type = col.data_type.toLowerCase() === 'array' ? col.udt_name.replace(/^_(.*)$/, '$1[]') : col.udt_name;
|
|
276
|
+
if (col.data_type === 'USER-DEFINED' &&
|
|
277
|
+
col.udt_schema &&
|
|
278
|
+
col.udt_schema !== this.platform.getDefaultSchemaName()) {
|
|
279
|
+
type = `${col.udt_schema}.${type}`;
|
|
280
|
+
}
|
|
281
|
+
if (type === 'bpchar') {
|
|
282
|
+
type = 'char';
|
|
283
|
+
}
|
|
284
|
+
if (type === 'vector' && col.length == null && col.custom_length != null && col.custom_length !== -1) {
|
|
285
|
+
col.length = col.custom_length;
|
|
286
|
+
}
|
|
287
|
+
if (col.length != null && !type.endsWith(`(${col.length})`) && !['text', 'date'].includes(type)) {
|
|
288
|
+
type += `(${col.length})`;
|
|
289
|
+
}
|
|
290
|
+
if (type === 'numeric' && col.numeric_precision != null && col.numeric_scale != null) {
|
|
291
|
+
type += `(${col.numeric_precision},${col.numeric_scale})`;
|
|
292
|
+
}
|
|
293
|
+
const length = this.inferLengthFromColumnType(type) === -1 ? -1 : col.length;
|
|
294
|
+
const column = {
|
|
295
|
+
name: col.column_name,
|
|
296
|
+
type,
|
|
297
|
+
mappedType,
|
|
298
|
+
length,
|
|
299
|
+
precision: col.numeric_precision,
|
|
300
|
+
scale: col.numeric_scale,
|
|
301
|
+
nullable: col.is_nullable === 'YES',
|
|
302
|
+
default: str(this.normalizeDefaultValue(col.column_default, col.length)),
|
|
303
|
+
unsigned: increments,
|
|
304
|
+
autoincrement: increments,
|
|
305
|
+
generated: col.is_identity === 'YES'
|
|
306
|
+
? col.identity_generation === 'BY DEFAULT'
|
|
307
|
+
? 'by default as identity'
|
|
308
|
+
: 'identity'
|
|
309
|
+
: col.generation_expression
|
|
310
|
+
? col.generation_expression + ' stored'
|
|
311
|
+
: undefined,
|
|
312
|
+
comment: col.column_comment,
|
|
313
|
+
};
|
|
314
|
+
if (nativeEnums?.[column.type]) {
|
|
315
|
+
column.mappedType = Type.getType(EnumType);
|
|
316
|
+
column.nativeEnumName = column.type;
|
|
317
|
+
column.enumItems = nativeEnums[column.type]?.items;
|
|
318
|
+
}
|
|
319
|
+
ret[key].push(column);
|
|
320
|
+
}
|
|
321
|
+
return ret;
|
|
322
|
+
}
|
|
323
|
+
async getAllChecks(connection, tablesBySchemas) {
|
|
324
|
+
const sql = this.getChecksSQL(tablesBySchemas);
|
|
325
|
+
const allChecks = await connection.execute(sql);
|
|
326
|
+
const ret = {};
|
|
327
|
+
const seen = new Set();
|
|
328
|
+
for (const check of allChecks) {
|
|
329
|
+
const key = this.getTableKey(check);
|
|
330
|
+
const dedupeKey = `${key}:${check.name}`;
|
|
331
|
+
if (seen.has(dedupeKey)) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
seen.add(dedupeKey);
|
|
335
|
+
ret[key] ??= [];
|
|
336
|
+
const m = /^check \(\((.*)\)\)$/is.exec(check.expression);
|
|
337
|
+
const def = m?.[1].replace(/\((.*?)\)::\w+/g, '$1');
|
|
338
|
+
ret[key].push({
|
|
339
|
+
name: check.name,
|
|
340
|
+
columnName: check.column_name,
|
|
341
|
+
definition: check.expression,
|
|
342
|
+
expression: def,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return ret;
|
|
346
|
+
}
|
|
347
|
+
async getAllForeignKeys(connection, tablesBySchemas) {
|
|
348
|
+
const sql = `select nsp1.nspname schema_name, cls1.relname table_name, nsp2.nspname referenced_schema_name,
|
|
359
349
|
cls2.relname referenced_table_name, a.attname column_name, af.attname referenced_column_name, conname constraint_name,
|
|
360
350
|
confupdtype update_rule, confdeltype delete_rule, array_position(con.conkey,a.attnum) as ord, condeferrable, condeferred,
|
|
361
351
|
pg_get_constraintdef(con.oid) as constraint_def
|
|
@@ -369,360 +359,342 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
369
359
|
where (${[...tablesBySchemas.entries()].map(([schema, tables]) => `(cls1.relname in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}) and nsp1.nspname = ${this.platform.quoteValue(schema)})`).join(' or ')})
|
|
370
360
|
and confrelid > 0
|
|
371
361
|
order by nsp1.nspname, cls1.relname, constraint_name, ord`;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
`select t.typname as enum_name, n.nspname as schema_name, array_agg(e.enumlabel order by e.enumsortorder) as enum_value
|
|
362
|
+
const allFks = await connection.execute(sql);
|
|
363
|
+
const ret = {};
|
|
364
|
+
function mapReferentialIntegrity(value, def) {
|
|
365
|
+
const match = ['n', 'd'].includes(value) && /ON DELETE (SET (NULL|DEFAULT) \(.*?\))/.exec(def);
|
|
366
|
+
if (match) {
|
|
367
|
+
return match[1];
|
|
368
|
+
}
|
|
369
|
+
/* v8 ignore next */
|
|
370
|
+
switch (value) {
|
|
371
|
+
case 'r':
|
|
372
|
+
return 'RESTRICT';
|
|
373
|
+
case 'c':
|
|
374
|
+
return 'CASCADE';
|
|
375
|
+
case 'n':
|
|
376
|
+
return 'SET NULL';
|
|
377
|
+
case 'd':
|
|
378
|
+
return 'SET DEFAULT';
|
|
379
|
+
case 'a':
|
|
380
|
+
default:
|
|
381
|
+
return 'NO ACTION';
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
for (const fk of allFks) {
|
|
385
|
+
fk.update_rule = mapReferentialIntegrity(fk.update_rule, fk.constraint_def);
|
|
386
|
+
fk.delete_rule = mapReferentialIntegrity(fk.delete_rule, fk.constraint_def);
|
|
387
|
+
if (fk.condeferrable) {
|
|
388
|
+
fk.defer_mode = fk.condeferred ? DeferMode.INITIALLY_DEFERRED : DeferMode.INITIALLY_IMMEDIATE;
|
|
389
|
+
}
|
|
390
|
+
const key = this.getTableKey(fk);
|
|
391
|
+
ret[key] ??= [];
|
|
392
|
+
ret[key].push(fk);
|
|
393
|
+
}
|
|
394
|
+
Object.keys(ret).forEach(key => {
|
|
395
|
+
const [schemaName, tableName] = key.split('.');
|
|
396
|
+
ret[key] = this.mapForeignKeys(ret[key], tableName, schemaName);
|
|
397
|
+
});
|
|
398
|
+
return ret;
|
|
399
|
+
}
|
|
400
|
+
async getNativeEnumDefinitions(connection, schemas) {
|
|
401
|
+
const uniqueSchemas = Utils.unique(schemas);
|
|
402
|
+
const res = await connection.execute(`select t.typname as enum_name, n.nspname as schema_name, array_agg(e.enumlabel order by e.enumsortorder) as enum_value
|
|
414
403
|
from pg_type t
|
|
415
404
|
join pg_enum e on t.oid = e.enumtypid
|
|
416
405
|
join pg_catalog.pg_namespace n on n.oid = t.typnamespace
|
|
417
406
|
where n.nspname in (${Array(uniqueSchemas.length).fill('?').join(', ')})
|
|
418
|
-
group by t.typname, n.nspname`,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
407
|
+
group by t.typname, n.nspname`, uniqueSchemas);
|
|
408
|
+
return res.reduce((o, row) => {
|
|
409
|
+
let name = row.enum_name;
|
|
410
|
+
if (row.schema_name && row.schema_name !== this.platform.getDefaultSchemaName()) {
|
|
411
|
+
name = row.schema_name + '.' + name;
|
|
412
|
+
}
|
|
413
|
+
let items = row.enum_value;
|
|
414
|
+
if (!Array.isArray(items)) {
|
|
415
|
+
items = this.platform.unmarshallArray(row.enum_value);
|
|
416
|
+
}
|
|
417
|
+
o[name] = {
|
|
418
|
+
name: row.enum_name,
|
|
419
|
+
schema: row.schema_name,
|
|
420
|
+
items,
|
|
421
|
+
};
|
|
422
|
+
return o;
|
|
423
|
+
}, {});
|
|
424
|
+
}
|
|
425
|
+
getCreateNativeEnumSQL(name, values, schema) {
|
|
426
|
+
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
427
|
+
name = schema + '.' + name;
|
|
428
|
+
}
|
|
429
|
+
return `create type ${this.quote(name)} as enum (${values.map(value => this.platform.quoteValue(value)).join(', ')})`;
|
|
430
|
+
}
|
|
431
|
+
getDropNativeEnumSQL(name, schema) {
|
|
432
|
+
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
433
|
+
name = schema + '.' + name;
|
|
434
|
+
}
|
|
435
|
+
return `drop type ${this.quote(name)}`;
|
|
436
|
+
}
|
|
437
|
+
getAlterNativeEnumSQL(name, schema, value, items, oldItems) {
|
|
438
|
+
if (schema && schema !== this.platform.getDefaultSchemaName()) {
|
|
439
|
+
name = schema + '.' + name;
|
|
440
|
+
}
|
|
441
|
+
let suffix = '';
|
|
442
|
+
if (items && value && oldItems) {
|
|
443
|
+
const position = items.indexOf(value);
|
|
444
|
+
if (position > 0) {
|
|
445
|
+
suffix = ` after ${this.platform.quoteValue(items[position - 1])}`;
|
|
446
|
+
}
|
|
447
|
+
else if (items.length > 1 && oldItems.length > 0) {
|
|
448
|
+
suffix = ` before ${this.platform.quoteValue(oldItems[0])}`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return `alter type ${this.quote(name)} add value if not exists ${this.platform.quoteValue(value)}${suffix}`;
|
|
452
|
+
}
|
|
453
|
+
getEnumDefinitions(checks) {
|
|
454
|
+
return checks.reduce((o, item) => {
|
|
455
|
+
// check constraints are defined as one of:
|
|
456
|
+
// `CHECK ((type = ANY (ARRAY['local'::text, 'global'::text])))`
|
|
457
|
+
// `CHECK (("columnName" = ANY (ARRAY['local'::text, 'global'::text])))`
|
|
458
|
+
// `CHECK (((enum_test)::text = ANY ((ARRAY['a'::character varying, 'b'::character varying, 'c'::character varying])::text[])))`
|
|
459
|
+
// `CHECK ((("enumTest")::text = ANY ((ARRAY['a'::character varying, 'b'::character varying, 'c'::character varying])::text[])))`
|
|
460
|
+
// `CHECK ((type = 'a'::text))`
|
|
461
|
+
const m1 = item.definition?.match(/check \(\(\("?(\w+)"?\)::/i) || item.definition?.match(/check \(\("?(\w+)"? = /i);
|
|
462
|
+
const m2 = item.definition?.match(/\(array\[(.*)]\)/i) || item.definition?.match(/ = (.*)\)/i);
|
|
463
|
+
if (item.columnName && m1 && m2) {
|
|
464
|
+
const m3 = m2[1].match(/('[^']*'::text)/g);
|
|
465
|
+
let items;
|
|
466
|
+
/* v8 ignore next */
|
|
467
|
+
if (m3) {
|
|
468
|
+
items = m3.map((item) => /^\(?'(.*)'/.exec(item.trim())?.[1]);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
items = m2[1].split(',').map((item) => /^\(?'(.*)'/.exec(item.trim())?.[1]);
|
|
472
|
+
}
|
|
473
|
+
items = items.filter(item => item !== undefined);
|
|
474
|
+
if (items.length > 0) {
|
|
475
|
+
o[item.columnName] = items;
|
|
476
|
+
item.expression = `${this.quote(item.columnName)} in ('${items.join("', '")}')`;
|
|
477
|
+
item.definition = `check (${item.expression})`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return o;
|
|
481
|
+
}, {});
|
|
482
|
+
}
|
|
483
|
+
createTableColumn(column, table) {
|
|
484
|
+
const pk = table.getPrimaryKey();
|
|
485
|
+
const compositePK = pk?.composite;
|
|
486
|
+
const primaryKey = !this.hasNonDefaultPrimaryKeyName(table);
|
|
487
|
+
const col = [this.quote(column.name)];
|
|
488
|
+
if (column.autoincrement && !column.generated && !compositePK) {
|
|
489
|
+
col.push(column.mappedType.getColumnType({ autoincrement: true }, this.platform));
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
let columnType = column.type;
|
|
493
|
+
if (column.nativeEnumName) {
|
|
494
|
+
const parts = column.type.split('.');
|
|
495
|
+
if (parts.length === 2 && parts[0] === '*') {
|
|
496
|
+
columnType = `${table.schema}.${parts[1]}`;
|
|
497
|
+
}
|
|
498
|
+
if (columnType.endsWith('[]')) {
|
|
499
|
+
columnType = this.quote(columnType.substring(0, columnType.length - 2)) + '[]';
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
columnType = this.quote(columnType);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (column.generated === 'by default as identity') {
|
|
506
|
+
columnType += ` generated ${column.generated}`;
|
|
507
|
+
}
|
|
508
|
+
else if (column.generated) {
|
|
509
|
+
columnType += ` generated always as ${column.generated}`;
|
|
510
|
+
}
|
|
511
|
+
col.push(columnType);
|
|
512
|
+
Utils.runIfNotEmpty(() => col.push('null'), column.nullable);
|
|
513
|
+
Utils.runIfNotEmpty(() => col.push('not null'), !column.nullable);
|
|
514
|
+
}
|
|
515
|
+
if (column.autoincrement && !compositePK) {
|
|
516
|
+
Utils.runIfNotEmpty(() => col.push('primary key'), primaryKey && column.primary);
|
|
517
|
+
}
|
|
518
|
+
const useDefault = column.default != null && column.default !== 'null' && !column.autoincrement;
|
|
519
|
+
Utils.runIfNotEmpty(() => col.push(`default ${column.default}`), useDefault);
|
|
520
|
+
return col.join(' ');
|
|
521
|
+
}
|
|
522
|
+
getPreAlterTable(tableDiff, safe) {
|
|
523
|
+
const ret = [];
|
|
524
|
+
const parts = tableDiff.name.split('.');
|
|
525
|
+
const tableName = parts.pop();
|
|
526
|
+
const schemaName = parts.pop();
|
|
479
527
|
/* v8 ignore next */
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
ret
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
for (const column of table.getColumns()) {
|
|
652
|
-
if (column.comment) {
|
|
653
|
-
const comment = this.platform.quoteValue(this.processComment(column.comment));
|
|
654
|
-
sql.push(`comment on column ${table.getQuotedName()}.${this.quote(column.name)} is ${comment}`);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return sql;
|
|
658
|
-
}
|
|
659
|
-
getDatabaseExistsSQL(name) {
|
|
660
|
-
return `select 1 from pg_database where datname = '${name}'`;
|
|
661
|
-
}
|
|
662
|
-
getDatabaseNotExistsError(dbName) {
|
|
663
|
-
return `database ${this.quote(dbName)} does not exist`;
|
|
664
|
-
}
|
|
665
|
-
getManagementDbName() {
|
|
666
|
-
return this.platform.getConfig().get('schemaGenerator', {}).managementDbName ?? 'postgres';
|
|
667
|
-
}
|
|
668
|
-
disableForeignKeysSQL() {
|
|
669
|
-
return `set session_replication_role = 'replica';`;
|
|
670
|
-
}
|
|
671
|
-
enableForeignKeysSQL() {
|
|
672
|
-
return `set session_replication_role = 'origin';`;
|
|
673
|
-
}
|
|
674
|
-
getRenameIndexSQL(tableName, index, oldIndexName) {
|
|
675
|
-
oldIndexName = this.quote(oldIndexName);
|
|
676
|
-
const keyName = this.quote(index.keyName);
|
|
677
|
-
return [`alter index ${oldIndexName} rename to ${keyName}`];
|
|
678
|
-
}
|
|
679
|
-
dropIndex(table, index, oldIndexName = index.keyName) {
|
|
680
|
-
if (index.primary || (index.unique && index.constraint)) {
|
|
681
|
-
return `alter table ${this.quote(table)} drop constraint ${this.quote(oldIndexName)}`;
|
|
682
|
-
}
|
|
683
|
-
return `drop index ${this.quote(oldIndexName)}`;
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Build the column list for a PostgreSQL index.
|
|
687
|
-
*/
|
|
688
|
-
getIndexColumns(index) {
|
|
689
|
-
if (index.columns?.length) {
|
|
690
|
-
return index.columns
|
|
691
|
-
.map(col => {
|
|
692
|
-
let colDef = this.quote(col.name);
|
|
693
|
-
// PostgreSQL supports collation with double quotes
|
|
694
|
-
if (col.collation) {
|
|
695
|
-
colDef += ` collate ${this.quote(col.collation)}`;
|
|
696
|
-
}
|
|
697
|
-
// PostgreSQL supports sort order
|
|
698
|
-
if (col.sort) {
|
|
699
|
-
colDef += ` ${col.sort}`;
|
|
700
|
-
}
|
|
701
|
-
// PostgreSQL supports NULLS FIRST/LAST
|
|
702
|
-
if (col.nulls) {
|
|
703
|
-
colDef += ` nulls ${col.nulls}`;
|
|
704
|
-
}
|
|
705
|
-
return colDef;
|
|
706
|
-
})
|
|
707
|
-
.join(', ');
|
|
708
|
-
}
|
|
709
|
-
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* PostgreSQL-specific index options like fill factor.
|
|
713
|
-
*/
|
|
714
|
-
getCreateIndexSuffix(index) {
|
|
715
|
-
const withOptions = [];
|
|
716
|
-
if (index.fillFactor != null) {
|
|
717
|
-
withOptions.push(`fillfactor = ${index.fillFactor}`);
|
|
718
|
-
}
|
|
719
|
-
if (withOptions.length > 0) {
|
|
720
|
-
return ` with (${withOptions.join(', ')})`;
|
|
721
|
-
}
|
|
722
|
-
return super.getCreateIndexSuffix(index);
|
|
723
|
-
}
|
|
724
|
-
getIndexesSQL(tables) {
|
|
725
|
-
return `select indrelid::regclass as table_name, ns.nspname as schema_name, relname as constraint_name, idx.indisunique as unique, idx.indisprimary as primary, contype, condeferrable, condeferred,
|
|
528
|
+
const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
|
|
529
|
+
const quotedName = this.quote(name);
|
|
530
|
+
// detect that the column was an enum before and remove the check constraint in such case here
|
|
531
|
+
const changedEnums = Object.values(tableDiff.changedColumns).filter(col => col.fromColumn.mappedType instanceof EnumType);
|
|
532
|
+
for (const col of changedEnums) {
|
|
533
|
+
if (!col.fromColumn.nativeEnumName && col.column.nativeEnumName && col.fromColumn.default) {
|
|
534
|
+
ret.push(`alter table ${quotedName} alter column "${col.column.name}" drop default`);
|
|
535
|
+
}
|
|
536
|
+
if (col.fromColumn.nativeEnumName && !col.column.nativeEnumName && col.fromColumn.default) {
|
|
537
|
+
ret.push(`alter table ${quotedName} alter column "${col.column.name}" drop default`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// changing uuid column type requires to cast it to text first
|
|
541
|
+
const uuids = Object.values(tableDiff.changedColumns).filter(col => col.changedProperties.has('type') && col.fromColumn.type === 'uuid');
|
|
542
|
+
for (const col of uuids) {
|
|
543
|
+
ret.push(`alter table ${quotedName} alter column "${col.column.name}" type text using ("${col.column.name}"::text)`);
|
|
544
|
+
}
|
|
545
|
+
for (const { column } of Object.values(tableDiff.changedColumns).filter(diff => diff.changedProperties.has('autoincrement'))) {
|
|
546
|
+
if (!column.autoincrement && column.default == null) {
|
|
547
|
+
ret.push(`alter table ${quotedName} alter column ${this.quote(column.name)} drop default`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return ret;
|
|
551
|
+
}
|
|
552
|
+
castColumn(name, type) {
|
|
553
|
+
if (type === 'uuid') {
|
|
554
|
+
type = 'text::uuid';
|
|
555
|
+
}
|
|
556
|
+
return ` using (${this.quote(name)}::${type})`;
|
|
557
|
+
}
|
|
558
|
+
dropForeignKey(tableName, constraintName) {
|
|
559
|
+
return `alter table ${this.quote(tableName)} drop constraint ${this.quote(constraintName)}`;
|
|
560
|
+
}
|
|
561
|
+
getPostAlterTable(tableDiff, safe) {
|
|
562
|
+
const ret = [];
|
|
563
|
+
const parts = tableDiff.name.split('.');
|
|
564
|
+
const tableName = parts.pop();
|
|
565
|
+
const schemaName = parts.pop();
|
|
566
|
+
/* v8 ignore next */
|
|
567
|
+
const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
|
|
568
|
+
const quotedName = this.quote(name);
|
|
569
|
+
// detect that the column was an enum before and remove the check constraint in such a case here
|
|
570
|
+
const changedEnums = Object.values(tableDiff.changedColumns).filter(col => col.fromColumn.mappedType instanceof EnumType);
|
|
571
|
+
for (const col of changedEnums) {
|
|
572
|
+
if (!col.fromColumn.nativeEnumName && col.column.nativeEnumName && col.column.default) {
|
|
573
|
+
ret.push(`alter table ${quotedName} alter column "${col.column.name}" set default ${col.column.default}`);
|
|
574
|
+
}
|
|
575
|
+
if (col.fromColumn.nativeEnumName && !col.column.nativeEnumName && col.column.default) {
|
|
576
|
+
ret.push(`alter table ${quotedName} alter column "${col.column.name}" set default ${col.column.default}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (const { column } of Object.values(tableDiff.changedColumns).filter(diff => diff.changedProperties.has('autoincrement'))) {
|
|
580
|
+
ret.push(...this.getAlterColumnAutoincrement(tableName, column, schemaName));
|
|
581
|
+
}
|
|
582
|
+
return ret;
|
|
583
|
+
}
|
|
584
|
+
getAlterColumnAutoincrement(tableName, column, schemaName) {
|
|
585
|
+
const ret = [];
|
|
586
|
+
/* v8 ignore next */
|
|
587
|
+
const name = (schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName;
|
|
588
|
+
if (column.autoincrement) {
|
|
589
|
+
const seqName = this.platform.getIndexName(tableName, [column.name], 'sequence');
|
|
590
|
+
ret.push(`create sequence if not exists ${this.quote(seqName)}`);
|
|
591
|
+
ret.push(`select setval('${seqName}', (select max(${this.quote(column.name)}) from ${this.quote(name)}))`);
|
|
592
|
+
ret.push(`alter table ${this.quote(name)} alter column ${this.quote(column.name)} set default nextval('${seqName}')`);
|
|
593
|
+
}
|
|
594
|
+
return ret;
|
|
595
|
+
}
|
|
596
|
+
getChangeColumnCommentSQL(tableName, to, schemaName) {
|
|
597
|
+
const name = this.quote((schemaName && schemaName !== this.platform.getDefaultSchemaName() ? schemaName + '.' : '') + tableName);
|
|
598
|
+
const value = to.comment ? this.platform.quoteValue(to.comment) : 'null';
|
|
599
|
+
return `comment on column ${name}.${this.quote(to.name)} is ${value}`;
|
|
600
|
+
}
|
|
601
|
+
alterTableComment(table, comment) {
|
|
602
|
+
return `comment on table ${table.getQuotedName()} is ${this.platform.quoteValue(comment ?? '')}`;
|
|
603
|
+
}
|
|
604
|
+
normalizeDefaultValue(defaultValue, length) {
|
|
605
|
+
if (!defaultValue || typeof defaultValue !== 'string') {
|
|
606
|
+
return super.normalizeDefaultValue(defaultValue, length, PostgreSqlSchemaHelper.DEFAULT_VALUES);
|
|
607
|
+
}
|
|
608
|
+
const match = /^'(.*)'::(.*)$/.exec(defaultValue);
|
|
609
|
+
if (match) {
|
|
610
|
+
if (match[2] === 'integer') {
|
|
611
|
+
return +match[1];
|
|
612
|
+
}
|
|
613
|
+
return `'${match[1]}'`;
|
|
614
|
+
}
|
|
615
|
+
return super.normalizeDefaultValue(defaultValue, length, PostgreSqlSchemaHelper.DEFAULT_VALUES);
|
|
616
|
+
}
|
|
617
|
+
appendComments(table) {
|
|
618
|
+
const sql = [];
|
|
619
|
+
if (table.comment) {
|
|
620
|
+
const comment = this.platform.quoteValue(this.processComment(table.comment));
|
|
621
|
+
sql.push(`comment on table ${table.getQuotedName()} is ${comment}`);
|
|
622
|
+
}
|
|
623
|
+
for (const column of table.getColumns()) {
|
|
624
|
+
if (column.comment) {
|
|
625
|
+
const comment = this.platform.quoteValue(this.processComment(column.comment));
|
|
626
|
+
sql.push(`comment on column ${table.getQuotedName()}.${this.quote(column.name)} is ${comment}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return sql;
|
|
630
|
+
}
|
|
631
|
+
getDatabaseExistsSQL(name) {
|
|
632
|
+
return `select 1 from pg_database where datname = '${name}'`;
|
|
633
|
+
}
|
|
634
|
+
getDatabaseNotExistsError(dbName) {
|
|
635
|
+
return `database ${this.quote(dbName)} does not exist`;
|
|
636
|
+
}
|
|
637
|
+
getManagementDbName() {
|
|
638
|
+
return this.platform.getConfig().get('schemaGenerator', {}).managementDbName ?? 'postgres';
|
|
639
|
+
}
|
|
640
|
+
disableForeignKeysSQL() {
|
|
641
|
+
return `set session_replication_role = 'replica';`;
|
|
642
|
+
}
|
|
643
|
+
enableForeignKeysSQL() {
|
|
644
|
+
return `set session_replication_role = 'origin';`;
|
|
645
|
+
}
|
|
646
|
+
getRenameIndexSQL(tableName, index, oldIndexName) {
|
|
647
|
+
oldIndexName = this.quote(oldIndexName);
|
|
648
|
+
const keyName = this.quote(index.keyName);
|
|
649
|
+
return [`alter index ${oldIndexName} rename to ${keyName}`];
|
|
650
|
+
}
|
|
651
|
+
dropIndex(table, index, oldIndexName = index.keyName) {
|
|
652
|
+
if (index.primary || (index.unique && index.constraint)) {
|
|
653
|
+
return `alter table ${this.quote(table)} drop constraint ${this.quote(oldIndexName)}`;
|
|
654
|
+
}
|
|
655
|
+
return `drop index ${this.quote(oldIndexName)}`;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Build the column list for a PostgreSQL index.
|
|
659
|
+
*/
|
|
660
|
+
getIndexColumns(index) {
|
|
661
|
+
if (index.columns?.length) {
|
|
662
|
+
return index.columns
|
|
663
|
+
.map(col => {
|
|
664
|
+
let colDef = this.quote(col.name);
|
|
665
|
+
// PostgreSQL supports collation with double quotes
|
|
666
|
+
if (col.collation) {
|
|
667
|
+
colDef += ` collate ${this.quote(col.collation)}`;
|
|
668
|
+
}
|
|
669
|
+
// PostgreSQL supports sort order
|
|
670
|
+
if (col.sort) {
|
|
671
|
+
colDef += ` ${col.sort}`;
|
|
672
|
+
}
|
|
673
|
+
// PostgreSQL supports NULLS FIRST/LAST
|
|
674
|
+
if (col.nulls) {
|
|
675
|
+
colDef += ` nulls ${col.nulls}`;
|
|
676
|
+
}
|
|
677
|
+
return colDef;
|
|
678
|
+
})
|
|
679
|
+
.join(', ');
|
|
680
|
+
}
|
|
681
|
+
return index.columnNames.map(c => this.quote(c)).join(', ');
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* PostgreSQL-specific index options like fill factor.
|
|
685
|
+
*/
|
|
686
|
+
getCreateIndexSuffix(index) {
|
|
687
|
+
const withOptions = [];
|
|
688
|
+
if (index.fillFactor != null) {
|
|
689
|
+
withOptions.push(`fillfactor = ${index.fillFactor}`);
|
|
690
|
+
}
|
|
691
|
+
if (withOptions.length > 0) {
|
|
692
|
+
return ` with (${withOptions.join(', ')})`;
|
|
693
|
+
}
|
|
694
|
+
return super.getCreateIndexSuffix(index);
|
|
695
|
+
}
|
|
696
|
+
getIndexesSQL(tables) {
|
|
697
|
+
return `select indrelid::regclass as table_name, ns.nspname as schema_name, relname as constraint_name, idx.indisunique as unique, idx.indisprimary as primary, contype, condeferrable, condeferred,
|
|
726
698
|
array(
|
|
727
699
|
select pg_get_indexdef(idx.indexrelid, k + 1, true)
|
|
728
700
|
from generate_subscripts(idx.indkey, 1) as k
|
|
@@ -740,37 +712,37 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
|
|
|
740
712
|
left join pg_constraint as c on c.conname = i.relname
|
|
741
713
|
where indrelid in (${tables.map(t => `${this.platform.quoteValue(`${this.quote(t.schema_name)}.${this.quote(t.table_name)}`)}::regclass`).join(', ')})
|
|
742
714
|
order by relname`;
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
715
|
+
}
|
|
716
|
+
getChecksSQL(tablesBySchemas) {
|
|
717
|
+
return `select ccu.table_name as table_name, ccu.table_schema as schema_name, pgc.conname as name, conrelid::regclass as table_from, ccu.column_name as column_name, pg_get_constraintdef(pgc.oid) as expression
|
|
746
718
|
from pg_constraint pgc
|
|
747
719
|
join pg_namespace nsp on nsp.oid = pgc.connamespace
|
|
748
720
|
join pg_class cls on pgc.conrelid = cls.oid
|
|
749
721
|
join information_schema.constraint_column_usage ccu on pgc.conname = ccu.constraint_name and nsp.nspname = ccu.constraint_schema and cls.relname = ccu.table_name
|
|
750
722
|
where contype = 'c' and (${[...tablesBySchemas.entries()].map(([schema, tables]) => `ccu.table_name in (${tables.map(t => this.platform.quoteValue(t.table_name)).join(',')}) and ccu.table_schema = ${this.platform.quoteValue(schema)}`).join(' or ')})
|
|
751
723
|
order by pgc.conname`;
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
724
|
+
}
|
|
725
|
+
inferLengthFromColumnType(type) {
|
|
726
|
+
const match = /^(\w+(?:\s+\w+)*)\s*(?:\(\s*(\d+)\s*\)|$)/.exec(type);
|
|
727
|
+
if (!match) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (!match[2]) {
|
|
731
|
+
switch (match[1]) {
|
|
732
|
+
case 'character varying':
|
|
733
|
+
case 'varchar':
|
|
734
|
+
case 'bpchar':
|
|
735
|
+
case 'char':
|
|
736
|
+
case 'character':
|
|
737
|
+
return -1;
|
|
738
|
+
case 'interval':
|
|
739
|
+
case 'time':
|
|
740
|
+
case 'timestamp':
|
|
741
|
+
case 'timestamptz':
|
|
742
|
+
return this.platform.getDefaultDateTimeLength();
|
|
743
|
+
}
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
return +match[2];
|
|
747
|
+
}
|
|
776
748
|
}
|