@ruiapp/rapid-core 0.8.7 → 0.8.9
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/dist/index.js +296 -272
- package/dist/plugins/metaManage/MetaManagePlugin.d.ts +6 -0
- package/dist/plugins/metaManage/services/MetaService.d.ts +7 -0
- package/package.json +1 -1
- package/src/plugins/metaManage/MetaManagePlugin.ts +28 -360
- package/src/plugins/metaManage/services/MetaService.ts +367 -0
- package/src/queryBuilder/queryBuilder.ts +6 -6
package/dist/index.js
CHANGED
|
@@ -614,7 +614,7 @@ function buildRangeFilterQuery(ctx, filter) {
|
|
|
614
614
|
}
|
|
615
615
|
function buildContainsFilterQuery(ctx, filter) {
|
|
616
616
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
617
|
-
command += "
|
|
617
|
+
command += " ILIKE ";
|
|
618
618
|
if (ctx.paramToLiteral) ;
|
|
619
619
|
else {
|
|
620
620
|
ctx.params.push(`%${filter.value}%`);
|
|
@@ -624,7 +624,7 @@ function buildContainsFilterQuery(ctx, filter) {
|
|
|
624
624
|
}
|
|
625
625
|
function buildNotContainsFilterQuery(ctx, filter) {
|
|
626
626
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
627
|
-
command += " NOT
|
|
627
|
+
command += " NOT ILIKE ";
|
|
628
628
|
if (ctx.paramToLiteral) ;
|
|
629
629
|
else {
|
|
630
630
|
ctx.params.push(`%${filter.value}%`);
|
|
@@ -634,7 +634,7 @@ function buildNotContainsFilterQuery(ctx, filter) {
|
|
|
634
634
|
}
|
|
635
635
|
function buildStartsWithFilterQuery(ctx, filter) {
|
|
636
636
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
637
|
-
command += "
|
|
637
|
+
command += " ILIKE ";
|
|
638
638
|
if (ctx.paramToLiteral) ;
|
|
639
639
|
else {
|
|
640
640
|
ctx.params.push(`${filter.value}%`);
|
|
@@ -644,7 +644,7 @@ function buildStartsWithFilterQuery(ctx, filter) {
|
|
|
644
644
|
}
|
|
645
645
|
function buildNotStartsWithFilterQuery(ctx, filter) {
|
|
646
646
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
647
|
-
command += " NOT
|
|
647
|
+
command += " NOT ILIKE ";
|
|
648
648
|
if (ctx.paramToLiteral) ;
|
|
649
649
|
else {
|
|
650
650
|
ctx.params.push(`${filter.value}%`);
|
|
@@ -654,7 +654,7 @@ function buildNotStartsWithFilterQuery(ctx, filter) {
|
|
|
654
654
|
}
|
|
655
655
|
function buildEndsWithFilterQuery(ctx, filter) {
|
|
656
656
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
657
|
-
command += "
|
|
657
|
+
command += " ILIKE ";
|
|
658
658
|
if (ctx.paramToLiteral) ;
|
|
659
659
|
else {
|
|
660
660
|
ctx.params.push(`%${filter.value}`);
|
|
@@ -664,7 +664,7 @@ function buildEndsWithFilterQuery(ctx, filter) {
|
|
|
664
664
|
}
|
|
665
665
|
function buildNotEndsWithFilterQuery(ctx, filter) {
|
|
666
666
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
667
|
-
command += " NOT
|
|
667
|
+
command += " NOT ILIKE ";
|
|
668
668
|
if (ctx.paramToLiteral) ;
|
|
669
669
|
else {
|
|
670
670
|
ctx.params.push(`%${filter.value}`);
|
|
@@ -5171,10 +5171,293 @@ function replaceModelIndexConditionEntityPropertyWithTableColumn(logger, model,
|
|
|
5171
5171
|
};
|
|
5172
5172
|
}
|
|
5173
5173
|
|
|
5174
|
+
function generateCreateColumnDDL(queryBuilder, options) {
|
|
5175
|
+
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
5176
|
+
columnDDL += ` ${queryBuilder.quoteObject(options.name)}`;
|
|
5177
|
+
if (options.type === "integer" && options.autoIncrement) {
|
|
5178
|
+
columnDDL += ` serial`;
|
|
5179
|
+
}
|
|
5180
|
+
else {
|
|
5181
|
+
const columnType = pgPropertyTypeColumnMap[options.type];
|
|
5182
|
+
if (!columnType) {
|
|
5183
|
+
throw new Error(`Property type "${options.type}" is not supported.`);
|
|
5184
|
+
}
|
|
5185
|
+
columnDDL += ` ${columnType}`;
|
|
5186
|
+
}
|
|
5187
|
+
if (options.notNull) {
|
|
5188
|
+
columnDDL += " NOT NULL";
|
|
5189
|
+
}
|
|
5190
|
+
if (options.defaultValue) {
|
|
5191
|
+
columnDDL += ` DEFAULT ${options.defaultValue}`;
|
|
5192
|
+
}
|
|
5193
|
+
return columnDDL;
|
|
5194
|
+
}
|
|
5195
|
+
function generateLinkTableDDL(queryBuilder, options) {
|
|
5196
|
+
let columnDDL = `CREATE TABLE ${queryBuilder.quoteTable({
|
|
5197
|
+
schema: options.linkSchema,
|
|
5198
|
+
tableName: options.linkTableName,
|
|
5199
|
+
})} (`;
|
|
5200
|
+
columnDDL += `id serial not null,`;
|
|
5201
|
+
columnDDL += `${queryBuilder.quoteObject(options.selfIdColumnName)} integer not null,`;
|
|
5202
|
+
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null);`;
|
|
5203
|
+
return columnDDL;
|
|
5204
|
+
}
|
|
5205
|
+
function generateTableIndexDDL(queryBuilder, server, model, index) {
|
|
5206
|
+
let indexName = index.name;
|
|
5207
|
+
if (!indexName) {
|
|
5208
|
+
indexName = model.tableName;
|
|
5209
|
+
for (const indexProp of index.properties) {
|
|
5210
|
+
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
5211
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
5212
|
+
if (!isRelationProperty(property)) {
|
|
5213
|
+
indexName += "_" + property.columnName;
|
|
5214
|
+
}
|
|
5215
|
+
else if (isOneRelationProperty(property)) {
|
|
5216
|
+
indexName += "_" + property.targetIdColumnName;
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
indexName += index.unique ? "_uindex" : "_index";
|
|
5220
|
+
}
|
|
5221
|
+
const indexColumns = lodash.map(index.properties, (indexProp) => {
|
|
5222
|
+
let columnName;
|
|
5223
|
+
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
5224
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
5225
|
+
if (!isRelationProperty(property)) {
|
|
5226
|
+
columnName = property.columnName;
|
|
5227
|
+
}
|
|
5228
|
+
else if (isOneRelationProperty(property)) {
|
|
5229
|
+
columnName = property.targetIdColumnName;
|
|
5230
|
+
}
|
|
5231
|
+
if (lodash.isString(indexProp)) {
|
|
5232
|
+
return columnName;
|
|
5233
|
+
}
|
|
5234
|
+
if (indexProp.order === "desc") {
|
|
5235
|
+
return `${columnName} desc`;
|
|
5236
|
+
}
|
|
5237
|
+
return columnName;
|
|
5238
|
+
});
|
|
5239
|
+
let ddl = `CREATE ${index.unique ? "UNIQUE" : ""} INDEX ${indexName} `;
|
|
5240
|
+
ddl += `ON ${queryBuilder.quoteTable({
|
|
5241
|
+
schema: model.schema,
|
|
5242
|
+
tableName: model.tableName,
|
|
5243
|
+
})} (${indexColumns.join(", ")})`;
|
|
5244
|
+
if (index.conditions) {
|
|
5245
|
+
const logger = server.getLogger();
|
|
5246
|
+
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
5247
|
+
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
5248
|
+
}
|
|
5249
|
+
return ddl;
|
|
5250
|
+
}
|
|
5251
|
+
class MetaService {
|
|
5252
|
+
#server;
|
|
5253
|
+
constructor(server) {
|
|
5254
|
+
this.#server = server;
|
|
5255
|
+
}
|
|
5256
|
+
async syncDatabaseSchema(applicationConfig) {
|
|
5257
|
+
const server = this.#server;
|
|
5258
|
+
const logger = server.getLogger();
|
|
5259
|
+
logger.info("Synchronizing database schema...");
|
|
5260
|
+
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
5261
|
+
const tablesInDb = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
5262
|
+
const { queryBuilder } = server;
|
|
5263
|
+
for (const model of applicationConfig.models) {
|
|
5264
|
+
logger.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
|
|
5265
|
+
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
5266
|
+
const expectedTableName = model.tableName;
|
|
5267
|
+
const tableInDb = lodash.find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
|
|
5268
|
+
if (!tableInDb) {
|
|
5269
|
+
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
5270
|
+
}
|
|
5271
|
+
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
5272
|
+
await server.tryQueryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
5276
|
+
FROM information_schema.columns c
|
|
5277
|
+
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
5278
|
+
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
5279
|
+
const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
5280
|
+
for (const model of applicationConfig.models) {
|
|
5281
|
+
logger.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
5282
|
+
for (const property of model.properties) {
|
|
5283
|
+
let columnDDL = "";
|
|
5284
|
+
if (isRelationProperty(property)) {
|
|
5285
|
+
if (property.relation === "one") {
|
|
5286
|
+
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
5287
|
+
if (!targetModel) {
|
|
5288
|
+
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
5289
|
+
}
|
|
5290
|
+
const columnInDb = lodash.find(columnsInDb, {
|
|
5291
|
+
table_schema: model.schema || "public",
|
|
5292
|
+
table_name: model.tableName,
|
|
5293
|
+
column_name: property.targetIdColumnName,
|
|
5294
|
+
});
|
|
5295
|
+
if (!columnInDb) {
|
|
5296
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5297
|
+
schema: model.schema,
|
|
5298
|
+
tableName: model.tableName,
|
|
5299
|
+
name: property.targetIdColumnName,
|
|
5300
|
+
type: "integer",
|
|
5301
|
+
autoIncrement: false,
|
|
5302
|
+
notNull: property.required,
|
|
5303
|
+
});
|
|
5304
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
5305
|
+
}
|
|
5306
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
5307
|
+
await server.tryQueryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.targetIdColumnName)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
else if (property.relation === "many") {
|
|
5311
|
+
if (property.linkTableName) {
|
|
5312
|
+
const tableInDb = lodash.find(tablesInDb, {
|
|
5313
|
+
table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema,
|
|
5314
|
+
table_name: property.linkTableName,
|
|
5315
|
+
});
|
|
5316
|
+
if (!tableInDb) {
|
|
5317
|
+
columnDDL = generateLinkTableDDL(queryBuilder, {
|
|
5318
|
+
linkSchema: property.linkSchema,
|
|
5319
|
+
linkTableName: property.linkTableName,
|
|
5320
|
+
targetIdColumnName: property.targetIdColumnName,
|
|
5321
|
+
selfIdColumnName: property.selfIdColumnName,
|
|
5322
|
+
});
|
|
5323
|
+
}
|
|
5324
|
+
const contraintName = `${property.linkTableName}_pk`;
|
|
5325
|
+
columnDDL += `ALTER TABLE ${queryBuilder.quoteTable({
|
|
5326
|
+
schema: property.linkSchema,
|
|
5327
|
+
tableName: property.linkTableName,
|
|
5328
|
+
})} ADD CONSTRAINT ${queryBuilder.quoteObject(contraintName)} PRIMARY KEY (id);`;
|
|
5329
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
5330
|
+
}
|
|
5331
|
+
else {
|
|
5332
|
+
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
5333
|
+
if (!targetModel) {
|
|
5334
|
+
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
5335
|
+
continue;
|
|
5336
|
+
}
|
|
5337
|
+
const columnInDb = lodash.find(columnsInDb, {
|
|
5338
|
+
table_schema: targetModel.schema || "public",
|
|
5339
|
+
table_name: targetModel.tableName,
|
|
5340
|
+
column_name: property.selfIdColumnName,
|
|
5341
|
+
});
|
|
5342
|
+
if (!columnInDb) {
|
|
5343
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5344
|
+
schema: targetModel.schema,
|
|
5345
|
+
tableName: targetModel.tableName,
|
|
5346
|
+
name: property.selfIdColumnName || "",
|
|
5347
|
+
type: "integer",
|
|
5348
|
+
autoIncrement: false,
|
|
5349
|
+
notNull: property.required,
|
|
5350
|
+
});
|
|
5351
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
else {
|
|
5356
|
+
continue;
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
else {
|
|
5360
|
+
const columnName = property.columnName || property.code;
|
|
5361
|
+
const columnInDb = lodash.find(columnsInDb, {
|
|
5362
|
+
table_schema: model.schema || "public",
|
|
5363
|
+
table_name: model.tableName,
|
|
5364
|
+
column_name: columnName,
|
|
5365
|
+
});
|
|
5366
|
+
if (!columnInDb) {
|
|
5367
|
+
// create column if not exists
|
|
5368
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5369
|
+
schema: model.schema,
|
|
5370
|
+
tableName: model.tableName,
|
|
5371
|
+
name: columnName,
|
|
5372
|
+
type: property.type,
|
|
5373
|
+
autoIncrement: property.autoIncrement,
|
|
5374
|
+
notNull: property.required,
|
|
5375
|
+
defaultValue: property.defaultValue,
|
|
5376
|
+
});
|
|
5377
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
5378
|
+
}
|
|
5379
|
+
else {
|
|
5380
|
+
const expectedColumnType = pgPropertyTypeColumnMap[property.type];
|
|
5381
|
+
if (columnInDb.udt_name !== expectedColumnType) {
|
|
5382
|
+
const sqlAlterColumnType = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} type ${expectedColumnType}`;
|
|
5383
|
+
await server.tryQueryDatabaseObject(sqlAlterColumnType);
|
|
5384
|
+
}
|
|
5385
|
+
if (property.defaultValue) {
|
|
5386
|
+
if (!columnInDb.column_default) {
|
|
5387
|
+
const sqlSetColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set default ${property.defaultValue}`;
|
|
5388
|
+
await server.tryQueryDatabaseObject(sqlSetColumnDefault);
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
else {
|
|
5392
|
+
if (columnInDb.column_default && !property.autoIncrement) {
|
|
5393
|
+
const sqlDropColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop default`;
|
|
5394
|
+
await server.tryQueryDatabaseObject(sqlDropColumnDefault);
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
if (property.required) {
|
|
5398
|
+
if (columnInDb.is_nullable === "YES") {
|
|
5399
|
+
const sqlSetColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set not null`;
|
|
5400
|
+
await server.tryQueryDatabaseObject(sqlSetColumnNotNull);
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
else {
|
|
5404
|
+
if (columnInDb.is_nullable === "NO") {
|
|
5405
|
+
const sqlDropColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop not null`;
|
|
5406
|
+
await server.tryQueryDatabaseObject(sqlDropColumnNotNull);
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
5411
|
+
await server.tryQueryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.columnName || property.code)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
const sqlQueryConstraints = `SELECT table_schema, table_name, constraint_type, constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'PRIMARY KEY';`;
|
|
5417
|
+
const constraintsInDb = await server.queryDatabaseObject(sqlQueryConstraints);
|
|
5418
|
+
for (const model of applicationConfig.models) {
|
|
5419
|
+
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
5420
|
+
const expectedTableName = model.tableName;
|
|
5421
|
+
const expectedContraintName = `${expectedTableName}_pk`;
|
|
5422
|
+
logger.debug(`Checking pk for '${expectedTableSchema}.${expectedTableName}'...`);
|
|
5423
|
+
const constraintInDb = lodash.find(constraintsInDb, {
|
|
5424
|
+
table_schema: expectedTableSchema,
|
|
5425
|
+
table_name: expectedTableName,
|
|
5426
|
+
constraint_type: "PRIMARY KEY",
|
|
5427
|
+
constraint_name: expectedContraintName,
|
|
5428
|
+
});
|
|
5429
|
+
if (!constraintInDb) {
|
|
5430
|
+
await server.queryDatabaseObject(`ALTER TABLE ${queryBuilder.quoteTable(model)} ADD CONSTRAINT ${queryBuilder.quoteObject(expectedContraintName)} PRIMARY KEY (id);`, []);
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
// generate indexes
|
|
5434
|
+
for (const model of applicationConfig.models) {
|
|
5435
|
+
if (!model.indexes || !model.indexes.length) {
|
|
5436
|
+
continue;
|
|
5437
|
+
}
|
|
5438
|
+
logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
|
|
5439
|
+
for (const index of model.indexes) {
|
|
5440
|
+
const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
|
|
5441
|
+
await server.tryQueryDatabaseObject(sqlCreateIndex, []);
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5174
5447
|
/**
|
|
5175
5448
|
* Meta manager plugin
|
|
5176
5449
|
*/
|
|
5177
5450
|
class MetaManager {
|
|
5451
|
+
#metaService;
|
|
5452
|
+
#syncDatabaseSchemaOnLoaded;
|
|
5453
|
+
constructor(options) {
|
|
5454
|
+
if (!options) {
|
|
5455
|
+
options = {
|
|
5456
|
+
syncDatabaseSchemaOnLoaded: true,
|
|
5457
|
+
};
|
|
5458
|
+
}
|
|
5459
|
+
this.#syncDatabaseSchemaOnLoaded = options.syncDatabaseSchemaOnLoaded;
|
|
5460
|
+
}
|
|
5178
5461
|
get code() {
|
|
5179
5462
|
return "metaManager";
|
|
5180
5463
|
}
|
|
@@ -5212,8 +5495,14 @@ class MetaManager {
|
|
|
5212
5495
|
logger.crit("Failed to load meta of models.", { error });
|
|
5213
5496
|
}
|
|
5214
5497
|
}
|
|
5498
|
+
async configureServices(server, applicationConfig) {
|
|
5499
|
+
this.#metaService = new MetaService(server);
|
|
5500
|
+
server.registerService("metaService", this.#metaService);
|
|
5501
|
+
}
|
|
5215
5502
|
async onApplicationLoaded(server, applicationConfig) {
|
|
5216
|
-
|
|
5503
|
+
if (this.#syncDatabaseSchemaOnLoaded) {
|
|
5504
|
+
await this.#metaService.syncDatabaseSchema(applicationConfig);
|
|
5505
|
+
}
|
|
5217
5506
|
}
|
|
5218
5507
|
}
|
|
5219
5508
|
async function handleEntityCreateEvent(server, sender, payload) {
|
|
@@ -5281,271 +5570,6 @@ function listDataDictionaries(server) {
|
|
|
5281
5570
|
return dataDictionaryManager.findEntities({
|
|
5282
5571
|
properties: properties.map((item) => item.code),
|
|
5283
5572
|
});
|
|
5284
|
-
}
|
|
5285
|
-
async function syncDatabaseSchema(server, applicationConfig) {
|
|
5286
|
-
const logger = server.getLogger();
|
|
5287
|
-
logger.info("Synchronizing database schema...");
|
|
5288
|
-
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
5289
|
-
const tablesInDb = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
5290
|
-
const { queryBuilder } = server;
|
|
5291
|
-
for (const model of applicationConfig.models) {
|
|
5292
|
-
logger.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
|
|
5293
|
-
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
5294
|
-
const expectedTableName = model.tableName;
|
|
5295
|
-
const tableInDb = lodash.find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
|
|
5296
|
-
if (!tableInDb) {
|
|
5297
|
-
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
5298
|
-
}
|
|
5299
|
-
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
5300
|
-
await server.tryQueryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
5301
|
-
}
|
|
5302
|
-
}
|
|
5303
|
-
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
5304
|
-
FROM information_schema.columns c
|
|
5305
|
-
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
5306
|
-
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
5307
|
-
const columnsInDb = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
5308
|
-
for (const model of applicationConfig.models) {
|
|
5309
|
-
logger.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
5310
|
-
for (const property of model.properties) {
|
|
5311
|
-
let columnDDL = "";
|
|
5312
|
-
if (isRelationProperty(property)) {
|
|
5313
|
-
if (property.relation === "one") {
|
|
5314
|
-
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
5315
|
-
if (!targetModel) {
|
|
5316
|
-
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
5317
|
-
}
|
|
5318
|
-
const columnInDb = lodash.find(columnsInDb, {
|
|
5319
|
-
table_schema: model.schema || "public",
|
|
5320
|
-
table_name: model.tableName,
|
|
5321
|
-
column_name: property.targetIdColumnName,
|
|
5322
|
-
});
|
|
5323
|
-
if (!columnInDb) {
|
|
5324
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5325
|
-
schema: model.schema,
|
|
5326
|
-
tableName: model.tableName,
|
|
5327
|
-
name: property.targetIdColumnName,
|
|
5328
|
-
type: "integer",
|
|
5329
|
-
autoIncrement: false,
|
|
5330
|
-
notNull: property.required,
|
|
5331
|
-
});
|
|
5332
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
5333
|
-
}
|
|
5334
|
-
if (!columnInDb || columnInDb.description != property.name) {
|
|
5335
|
-
await server.tryQueryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.targetIdColumnName)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
5336
|
-
}
|
|
5337
|
-
}
|
|
5338
|
-
else if (property.relation === "many") {
|
|
5339
|
-
if (property.linkTableName) {
|
|
5340
|
-
const tableInDb = lodash.find(tablesInDb, {
|
|
5341
|
-
table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema,
|
|
5342
|
-
table_name: property.linkTableName,
|
|
5343
|
-
});
|
|
5344
|
-
if (!tableInDb) {
|
|
5345
|
-
columnDDL = generateLinkTableDDL(queryBuilder, {
|
|
5346
|
-
linkSchema: property.linkSchema,
|
|
5347
|
-
linkTableName: property.linkTableName,
|
|
5348
|
-
targetIdColumnName: property.targetIdColumnName,
|
|
5349
|
-
selfIdColumnName: property.selfIdColumnName,
|
|
5350
|
-
});
|
|
5351
|
-
}
|
|
5352
|
-
const contraintName = `${property.linkTableName}_pk`;
|
|
5353
|
-
columnDDL += `ALTER TABLE ${queryBuilder.quoteTable({
|
|
5354
|
-
schema: property.linkSchema,
|
|
5355
|
-
tableName: property.linkTableName,
|
|
5356
|
-
})} ADD CONSTRAINT ${queryBuilder.quoteObject(contraintName)} PRIMARY KEY (id);`;
|
|
5357
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
5358
|
-
}
|
|
5359
|
-
else {
|
|
5360
|
-
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
5361
|
-
if (!targetModel) {
|
|
5362
|
-
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
5363
|
-
continue;
|
|
5364
|
-
}
|
|
5365
|
-
const columnInDb = lodash.find(columnsInDb, {
|
|
5366
|
-
table_schema: targetModel.schema || "public",
|
|
5367
|
-
table_name: targetModel.tableName,
|
|
5368
|
-
column_name: property.selfIdColumnName,
|
|
5369
|
-
});
|
|
5370
|
-
if (!columnInDb) {
|
|
5371
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5372
|
-
schema: targetModel.schema,
|
|
5373
|
-
tableName: targetModel.tableName,
|
|
5374
|
-
name: property.selfIdColumnName || "",
|
|
5375
|
-
type: "integer",
|
|
5376
|
-
autoIncrement: false,
|
|
5377
|
-
notNull: property.required,
|
|
5378
|
-
});
|
|
5379
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
}
|
|
5383
|
-
else {
|
|
5384
|
-
continue;
|
|
5385
|
-
}
|
|
5386
|
-
}
|
|
5387
|
-
else {
|
|
5388
|
-
const columnName = property.columnName || property.code;
|
|
5389
|
-
const columnInDb = lodash.find(columnsInDb, {
|
|
5390
|
-
table_schema: model.schema || "public",
|
|
5391
|
-
table_name: model.tableName,
|
|
5392
|
-
column_name: columnName,
|
|
5393
|
-
});
|
|
5394
|
-
if (!columnInDb) {
|
|
5395
|
-
// create column if not exists
|
|
5396
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
5397
|
-
schema: model.schema,
|
|
5398
|
-
tableName: model.tableName,
|
|
5399
|
-
name: columnName,
|
|
5400
|
-
type: property.type,
|
|
5401
|
-
autoIncrement: property.autoIncrement,
|
|
5402
|
-
notNull: property.required,
|
|
5403
|
-
defaultValue: property.defaultValue,
|
|
5404
|
-
});
|
|
5405
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
5406
|
-
}
|
|
5407
|
-
else {
|
|
5408
|
-
const expectedColumnType = pgPropertyTypeColumnMap[property.type];
|
|
5409
|
-
if (columnInDb.udt_name !== expectedColumnType) {
|
|
5410
|
-
const sqlAlterColumnType = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} type ${expectedColumnType}`;
|
|
5411
|
-
await server.tryQueryDatabaseObject(sqlAlterColumnType);
|
|
5412
|
-
}
|
|
5413
|
-
if (property.defaultValue) {
|
|
5414
|
-
if (!columnInDb.column_default) {
|
|
5415
|
-
const sqlSetColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set default ${property.defaultValue}`;
|
|
5416
|
-
await server.tryQueryDatabaseObject(sqlSetColumnDefault);
|
|
5417
|
-
}
|
|
5418
|
-
}
|
|
5419
|
-
else {
|
|
5420
|
-
if (columnInDb.column_default && !property.autoIncrement) {
|
|
5421
|
-
const sqlDropColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop default`;
|
|
5422
|
-
await server.tryQueryDatabaseObject(sqlDropColumnDefault);
|
|
5423
|
-
}
|
|
5424
|
-
}
|
|
5425
|
-
if (property.required) {
|
|
5426
|
-
if (columnInDb.is_nullable === "YES") {
|
|
5427
|
-
const sqlSetColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set not null`;
|
|
5428
|
-
await server.tryQueryDatabaseObject(sqlSetColumnNotNull);
|
|
5429
|
-
}
|
|
5430
|
-
}
|
|
5431
|
-
else {
|
|
5432
|
-
if (columnInDb.is_nullable === "NO") {
|
|
5433
|
-
const sqlDropColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop not null`;
|
|
5434
|
-
await server.tryQueryDatabaseObject(sqlDropColumnNotNull);
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
|
-
}
|
|
5438
|
-
if (!columnInDb || columnInDb.description != property.name) {
|
|
5439
|
-
await server.tryQueryDatabaseObject(`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(property.columnName || property.code)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`, []);
|
|
5440
|
-
}
|
|
5441
|
-
}
|
|
5442
|
-
}
|
|
5443
|
-
}
|
|
5444
|
-
const sqlQueryConstraints = `SELECT table_schema, table_name, constraint_type, constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'PRIMARY KEY';`;
|
|
5445
|
-
const constraintsInDb = await server.queryDatabaseObject(sqlQueryConstraints);
|
|
5446
|
-
for (const model of applicationConfig.models) {
|
|
5447
|
-
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
5448
|
-
const expectedTableName = model.tableName;
|
|
5449
|
-
const expectedContraintName = `${expectedTableName}_pk`;
|
|
5450
|
-
logger.debug(`Checking pk for '${expectedTableSchema}.${expectedTableName}'...`);
|
|
5451
|
-
const constraintInDb = lodash.find(constraintsInDb, {
|
|
5452
|
-
table_schema: expectedTableSchema,
|
|
5453
|
-
table_name: expectedTableName,
|
|
5454
|
-
constraint_type: "PRIMARY KEY",
|
|
5455
|
-
constraint_name: expectedContraintName,
|
|
5456
|
-
});
|
|
5457
|
-
if (!constraintInDb) {
|
|
5458
|
-
await server.queryDatabaseObject(`ALTER TABLE ${queryBuilder.quoteTable(model)} ADD CONSTRAINT ${queryBuilder.quoteObject(expectedContraintName)} PRIMARY KEY (id);`, []);
|
|
5459
|
-
}
|
|
5460
|
-
}
|
|
5461
|
-
// generate indexes
|
|
5462
|
-
for (const model of applicationConfig.models) {
|
|
5463
|
-
if (!model.indexes || !model.indexes.length) {
|
|
5464
|
-
continue;
|
|
5465
|
-
}
|
|
5466
|
-
logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
|
|
5467
|
-
for (const index of model.indexes) {
|
|
5468
|
-
const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
|
|
5469
|
-
await server.tryQueryDatabaseObject(sqlCreateIndex, []);
|
|
5470
|
-
}
|
|
5471
|
-
}
|
|
5472
|
-
}
|
|
5473
|
-
function generateCreateColumnDDL(queryBuilder, options) {
|
|
5474
|
-
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
5475
|
-
columnDDL += ` ${queryBuilder.quoteObject(options.name)}`;
|
|
5476
|
-
if (options.type === "integer" && options.autoIncrement) {
|
|
5477
|
-
columnDDL += ` serial`;
|
|
5478
|
-
}
|
|
5479
|
-
else {
|
|
5480
|
-
const columnType = pgPropertyTypeColumnMap[options.type];
|
|
5481
|
-
if (!columnType) {
|
|
5482
|
-
throw new Error(`Property type "${options.type}" is not supported.`);
|
|
5483
|
-
}
|
|
5484
|
-
columnDDL += ` ${columnType}`;
|
|
5485
|
-
}
|
|
5486
|
-
if (options.notNull) {
|
|
5487
|
-
columnDDL += " NOT NULL";
|
|
5488
|
-
}
|
|
5489
|
-
if (options.defaultValue) {
|
|
5490
|
-
columnDDL += ` DEFAULT ${options.defaultValue}`;
|
|
5491
|
-
}
|
|
5492
|
-
return columnDDL;
|
|
5493
|
-
}
|
|
5494
|
-
function generateLinkTableDDL(queryBuilder, options) {
|
|
5495
|
-
let columnDDL = `CREATE TABLE ${queryBuilder.quoteTable({
|
|
5496
|
-
schema: options.linkSchema,
|
|
5497
|
-
tableName: options.linkTableName,
|
|
5498
|
-
})} (`;
|
|
5499
|
-
columnDDL += `id serial not null,`;
|
|
5500
|
-
columnDDL += `${queryBuilder.quoteObject(options.selfIdColumnName)} integer not null,`;
|
|
5501
|
-
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null);`;
|
|
5502
|
-
return columnDDL;
|
|
5503
|
-
}
|
|
5504
|
-
function generateTableIndexDDL(queryBuilder, server, model, index) {
|
|
5505
|
-
let indexName = index.name;
|
|
5506
|
-
if (!indexName) {
|
|
5507
|
-
indexName = model.tableName;
|
|
5508
|
-
for (const indexProp of index.properties) {
|
|
5509
|
-
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
5510
|
-
const property = getEntityPropertyByCode(server, model, propCode);
|
|
5511
|
-
if (!isRelationProperty(property)) {
|
|
5512
|
-
indexName += "_" + property.columnName;
|
|
5513
|
-
}
|
|
5514
|
-
else if (isOneRelationProperty(property)) {
|
|
5515
|
-
indexName += "_" + property.targetIdColumnName;
|
|
5516
|
-
}
|
|
5517
|
-
}
|
|
5518
|
-
indexName += index.unique ? "_uindex" : "_index";
|
|
5519
|
-
}
|
|
5520
|
-
const indexColumns = lodash.map(index.properties, (indexProp) => {
|
|
5521
|
-
let columnName;
|
|
5522
|
-
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
5523
|
-
const property = getEntityPropertyByCode(server, model, propCode);
|
|
5524
|
-
if (!isRelationProperty(property)) {
|
|
5525
|
-
columnName = property.columnName;
|
|
5526
|
-
}
|
|
5527
|
-
else if (isOneRelationProperty(property)) {
|
|
5528
|
-
columnName = property.targetIdColumnName;
|
|
5529
|
-
}
|
|
5530
|
-
if (lodash.isString(indexProp)) {
|
|
5531
|
-
return columnName;
|
|
5532
|
-
}
|
|
5533
|
-
if (indexProp.order === "desc") {
|
|
5534
|
-
return `${columnName} desc`;
|
|
5535
|
-
}
|
|
5536
|
-
return columnName;
|
|
5537
|
-
});
|
|
5538
|
-
let ddl = `CREATE ${index.unique ? "UNIQUE" : ""} INDEX ${indexName} `;
|
|
5539
|
-
ddl += `ON ${queryBuilder.quoteTable({
|
|
5540
|
-
schema: model.schema,
|
|
5541
|
-
tableName: model.tableName,
|
|
5542
|
-
})} (${indexColumns.join(", ")})`;
|
|
5543
|
-
if (index.conditions) {
|
|
5544
|
-
const logger = server.getLogger();
|
|
5545
|
-
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
5546
|
-
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
5547
|
-
}
|
|
5548
|
-
return ddl;
|
|
5549
5573
|
}
|
|
5550
5574
|
|
|
5551
5575
|
function mergeInput(defaultInput, input, fixedInput) {
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { RpdApplicationConfig } from "../../types";
|
|
5
5
|
import { IRpdServer, RapidPlugin, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "../../core/server";
|
|
6
|
+
export type MetaManagerInitOptions = {
|
|
7
|
+
syncDatabaseSchemaOnLoaded?: boolean;
|
|
8
|
+
};
|
|
6
9
|
declare class MetaManager implements RapidPlugin {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(options?: MetaManagerInitOptions);
|
|
7
12
|
get code(): string;
|
|
8
13
|
get description(): string;
|
|
9
14
|
get extendingAbilities(): RpdServerPluginExtendingAbilities[];
|
|
@@ -12,6 +17,7 @@ declare class MetaManager implements RapidPlugin {
|
|
|
12
17
|
registerActionHandlers(server: IRpdServer): Promise<any>;
|
|
13
18
|
registerEventHandlers(server: IRpdServer): Promise<any>;
|
|
14
19
|
configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
20
|
+
configureServices(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
15
21
|
onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any>;
|
|
16
22
|
}
|
|
17
23
|
export default MetaManager;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IRpdServer } from "../../../core/server";
|
|
2
|
+
import { RpdApplicationConfig } from "../../../types";
|
|
3
|
+
export default class MetaService {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(server: IRpdServer);
|
|
6
|
+
syncDatabaseSchema(applicationConfig: RpdApplicationConfig): Promise<void>;
|
|
7
|
+
}
|