@ruiapp/rapid-core 0.1.73 → 0.1.74
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/bootstrapApplicationConfig.d.ts +82 -0
- package/dist/dataAccess/entityManager.d.ts +8 -1
- package/dist/index.js +265 -16
- package/dist/queryBuilder/queryBuilder.d.ts +5 -0
- package/dist/types.d.ts +18 -3
- package/package.json +1 -1
- package/src/bootstrapApplicationConfig.ts +602 -552
- package/src/dataAccess/entityManager.ts +107 -1
- package/src/plugins/metaManage/MetaManagePlugin.ts +65 -2
- package/src/queryBuilder/queryBuilder.ts +99 -17
- package/src/types.ts +617 -593
|
@@ -2,6 +2,78 @@ declare const _default: {
|
|
|
2
2
|
code: string;
|
|
3
3
|
name: string;
|
|
4
4
|
models: ({
|
|
5
|
+
maintainedBy: string;
|
|
6
|
+
namespace: string;
|
|
7
|
+
name: string;
|
|
8
|
+
singularCode: string;
|
|
9
|
+
pluralCode: string;
|
|
10
|
+
schema: string;
|
|
11
|
+
tableName: string;
|
|
12
|
+
properties: ({
|
|
13
|
+
name: string;
|
|
14
|
+
code: string;
|
|
15
|
+
columnName: string;
|
|
16
|
+
type: "integer";
|
|
17
|
+
required: true;
|
|
18
|
+
autoIncrement: true;
|
|
19
|
+
relation?: undefined;
|
|
20
|
+
targetSingularCode?: undefined;
|
|
21
|
+
selfIdColumnName?: undefined;
|
|
22
|
+
} | {
|
|
23
|
+
name: string;
|
|
24
|
+
code: string;
|
|
25
|
+
columnName: string;
|
|
26
|
+
type: "text";
|
|
27
|
+
required: true;
|
|
28
|
+
autoIncrement?: undefined;
|
|
29
|
+
relation?: undefined;
|
|
30
|
+
targetSingularCode?: undefined;
|
|
31
|
+
selfIdColumnName?: undefined;
|
|
32
|
+
} | {
|
|
33
|
+
name: string;
|
|
34
|
+
code: string;
|
|
35
|
+
columnName: string;
|
|
36
|
+
type: "text";
|
|
37
|
+
required: false;
|
|
38
|
+
autoIncrement?: undefined;
|
|
39
|
+
relation?: undefined;
|
|
40
|
+
targetSingularCode?: undefined;
|
|
41
|
+
selfIdColumnName?: undefined;
|
|
42
|
+
} | {
|
|
43
|
+
name: string;
|
|
44
|
+
code: string;
|
|
45
|
+
type: "relation[]";
|
|
46
|
+
relation: "many";
|
|
47
|
+
targetSingularCode: string;
|
|
48
|
+
selfIdColumnName: string;
|
|
49
|
+
columnName?: undefined;
|
|
50
|
+
required?: undefined;
|
|
51
|
+
autoIncrement?: undefined;
|
|
52
|
+
} | {
|
|
53
|
+
name: string;
|
|
54
|
+
code: string;
|
|
55
|
+
columnName: string;
|
|
56
|
+
type: "json";
|
|
57
|
+
required: false;
|
|
58
|
+
autoIncrement?: undefined;
|
|
59
|
+
relation?: undefined;
|
|
60
|
+
targetSingularCode?: undefined;
|
|
61
|
+
selfIdColumnName?: undefined;
|
|
62
|
+
})[];
|
|
63
|
+
indexes: ({
|
|
64
|
+
name: string;
|
|
65
|
+
properties: {
|
|
66
|
+
code: string;
|
|
67
|
+
}[];
|
|
68
|
+
unique: true;
|
|
69
|
+
} | {
|
|
70
|
+
name: string;
|
|
71
|
+
properties: {
|
|
72
|
+
code: string;
|
|
73
|
+
}[];
|
|
74
|
+
unique: false;
|
|
75
|
+
})[];
|
|
76
|
+
} | {
|
|
5
77
|
maintainedBy: string;
|
|
6
78
|
namespace: string;
|
|
7
79
|
name: string;
|
|
@@ -87,6 +159,13 @@ declare const _default: {
|
|
|
87
159
|
targetIdColumnName?: undefined;
|
|
88
160
|
defaultValue?: undefined;
|
|
89
161
|
})[];
|
|
162
|
+
indexes: {
|
|
163
|
+
name: string;
|
|
164
|
+
properties: {
|
|
165
|
+
code: string;
|
|
166
|
+
}[];
|
|
167
|
+
unique: true;
|
|
168
|
+
}[];
|
|
90
169
|
} | {
|
|
91
170
|
maintainedBy: string;
|
|
92
171
|
namespace: string;
|
|
@@ -162,6 +241,7 @@ declare const _default: {
|
|
|
162
241
|
autoIncrement?: undefined;
|
|
163
242
|
defaultValue?: undefined;
|
|
164
243
|
})[];
|
|
244
|
+
indexes?: undefined;
|
|
165
245
|
} | {
|
|
166
246
|
maintainedBy: string;
|
|
167
247
|
namespace: string;
|
|
@@ -237,6 +317,7 @@ declare const _default: {
|
|
|
237
317
|
targetIdColumnName?: undefined;
|
|
238
318
|
defaultValue?: undefined;
|
|
239
319
|
})[];
|
|
320
|
+
indexes?: undefined;
|
|
240
321
|
} | {
|
|
241
322
|
maintainedBy: string;
|
|
242
323
|
namespace: string;
|
|
@@ -274,6 +355,7 @@ declare const _default: {
|
|
|
274
355
|
required: true;
|
|
275
356
|
autoIncrement?: undefined;
|
|
276
357
|
})[];
|
|
358
|
+
indexes?: undefined;
|
|
277
359
|
})[];
|
|
278
360
|
routes: {
|
|
279
361
|
namespace: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AddEntityRelationsOptions, CountEntityOptions, CountEntityResult, CreateEntityOptions, DeleteEntityByIdOptions, FindEntityByIdOptions, FindEntityOptions, FindEntitySelectRelationOptions, IRpdDataAccessor, RemoveEntityRelationsOptions, RpdDataModel, RpdDataModelProperty, UpdateEntityByIdOptions } from "../types";
|
|
1
|
+
import { AddEntityRelationsOptions, CountEntityOptions, CountEntityResult, CreateEntityOptions, DeleteEntityByIdOptions, FindEntityByIdOptions, FindEntityOptions, FindEntitySelectRelationOptions, IRpdDataAccessor, RemoveEntityRelationsOptions, RpdDataModel, RpdDataModelIndex, RpdDataModelProperty, UpdateEntityByIdOptions } from "../types";
|
|
2
2
|
import { IRpdServer, RapidPlugin } from "../core/server";
|
|
3
|
+
import { RouteContext } from "../core/routeContext";
|
|
3
4
|
export type FindOneRelationEntitiesOptions = {
|
|
4
5
|
server: IRpdServer;
|
|
5
6
|
mainModel: RpdDataModel;
|
|
@@ -14,6 +15,12 @@ export type FindManyRelationEntitiesOptions = {
|
|
|
14
15
|
mainEntityIds: any[];
|
|
15
16
|
selectRelationOptions?: FindEntitySelectRelationOptions;
|
|
16
17
|
};
|
|
18
|
+
export type CheckEntityDuplicatedOptions = {
|
|
19
|
+
routeContext?: RouteContext;
|
|
20
|
+
entityId?: number;
|
|
21
|
+
entityToSave: any;
|
|
22
|
+
indexConfig: RpdDataModelIndex;
|
|
23
|
+
};
|
|
17
24
|
export default class EntityManager<TEntity = any> {
|
|
18
25
|
#private;
|
|
19
26
|
constructor(server: IRpdServer, dataAccessor: IRpdDataAccessor);
|
package/dist/index.js
CHANGED
|
@@ -236,6 +236,7 @@ class QueryBuilder {
|
|
|
236
236
|
builder: this,
|
|
237
237
|
params: [],
|
|
238
238
|
emitTableAlias: true,
|
|
239
|
+
paramToLiteral: false,
|
|
239
240
|
};
|
|
240
241
|
let { fields: columns, filters, orderBy, pagination } = options;
|
|
241
242
|
let command = "SELECT ";
|
|
@@ -288,6 +289,7 @@ class QueryBuilder {
|
|
|
288
289
|
builder: this,
|
|
289
290
|
params: [],
|
|
290
291
|
emitTableAlias: true,
|
|
292
|
+
paramToLiteral: false,
|
|
291
293
|
};
|
|
292
294
|
let { fields: columns, filters, orderBy, pagination } = options;
|
|
293
295
|
let command = "SELECT ";
|
|
@@ -344,6 +346,7 @@ class QueryBuilder {
|
|
|
344
346
|
builder: this,
|
|
345
347
|
params: [],
|
|
346
348
|
emitTableAlias: false,
|
|
349
|
+
paramToLiteral: false,
|
|
347
350
|
};
|
|
348
351
|
let { filters } = options;
|
|
349
352
|
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
@@ -363,6 +366,7 @@ class QueryBuilder {
|
|
|
363
366
|
builder: this,
|
|
364
367
|
params: [],
|
|
365
368
|
emitTableAlias: true,
|
|
369
|
+
paramToLiteral: false,
|
|
366
370
|
};
|
|
367
371
|
let { filters } = options;
|
|
368
372
|
let command = 'SELECT COUNT(*)::int as "count" FROM ';
|
|
@@ -383,6 +387,7 @@ class QueryBuilder {
|
|
|
383
387
|
builder: this,
|
|
384
388
|
params,
|
|
385
389
|
emitTableAlias: false,
|
|
390
|
+
paramToLiteral: false,
|
|
386
391
|
};
|
|
387
392
|
const { entity } = options;
|
|
388
393
|
let command = "INSERT INTO ";
|
|
@@ -420,6 +425,7 @@ class QueryBuilder {
|
|
|
420
425
|
builder: this,
|
|
421
426
|
params,
|
|
422
427
|
emitTableAlias: false,
|
|
428
|
+
paramToLiteral: false,
|
|
423
429
|
};
|
|
424
430
|
let { entity, filters } = options;
|
|
425
431
|
let command = "UPDATE ";
|
|
@@ -460,6 +466,7 @@ class QueryBuilder {
|
|
|
460
466
|
builder: this,
|
|
461
467
|
params,
|
|
462
468
|
emitTableAlias: false,
|
|
469
|
+
paramToLiteral: false,
|
|
463
470
|
};
|
|
464
471
|
let { filters } = options;
|
|
465
472
|
let command = "DELETE FROM ";
|
|
@@ -473,6 +480,17 @@ class QueryBuilder {
|
|
|
473
480
|
params: ctx.params,
|
|
474
481
|
};
|
|
475
482
|
}
|
|
483
|
+
buildFiltersExpression(model, filters) {
|
|
484
|
+
const params = [];
|
|
485
|
+
const ctx = {
|
|
486
|
+
model,
|
|
487
|
+
builder: this,
|
|
488
|
+
params,
|
|
489
|
+
emitTableAlias: false,
|
|
490
|
+
paramToLiteral: true,
|
|
491
|
+
};
|
|
492
|
+
return buildFiltersQuery(ctx, filters);
|
|
493
|
+
}
|
|
476
494
|
}
|
|
477
495
|
function buildFiltersQuery(ctx, filters) {
|
|
478
496
|
return buildFilterQuery(0, ctx, {
|
|
@@ -548,58 +566,99 @@ function buildInFilterQuery(ctx, filter) {
|
|
|
548
566
|
else {
|
|
549
567
|
command += " <> ";
|
|
550
568
|
}
|
|
551
|
-
ctx.
|
|
552
|
-
|
|
569
|
+
if (ctx.paramToLiteral) ;
|
|
570
|
+
else {
|
|
571
|
+
ctx.params.push(filter.value);
|
|
572
|
+
command += `ANY($${ctx.params.length}::${filter.itemType || "int"}[])`;
|
|
573
|
+
}
|
|
553
574
|
return command;
|
|
554
575
|
}
|
|
555
576
|
function buildContainsFilterQuery(ctx, filter) {
|
|
556
577
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
557
578
|
command += " LIKE ";
|
|
558
|
-
ctx.
|
|
559
|
-
|
|
579
|
+
if (ctx.paramToLiteral) ;
|
|
580
|
+
else {
|
|
581
|
+
ctx.params.push(`%${filter.value}%`);
|
|
582
|
+
command += "$" + ctx.params.length;
|
|
583
|
+
}
|
|
560
584
|
return command;
|
|
561
585
|
}
|
|
562
586
|
function buildNotContainsFilterQuery(ctx, filter) {
|
|
563
587
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
564
588
|
command += " NOT LIKE ";
|
|
565
|
-
ctx.
|
|
566
|
-
|
|
589
|
+
if (ctx.paramToLiteral) ;
|
|
590
|
+
else {
|
|
591
|
+
ctx.params.push(`%${filter.value}%`);
|
|
592
|
+
command += "$" + ctx.params.length;
|
|
593
|
+
}
|
|
567
594
|
return command;
|
|
568
595
|
}
|
|
569
596
|
function buildStartsWithFilterQuery(ctx, filter) {
|
|
570
597
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
571
598
|
command += " LIKE ";
|
|
572
|
-
ctx.
|
|
573
|
-
|
|
599
|
+
if (ctx.paramToLiteral) ;
|
|
600
|
+
else {
|
|
601
|
+
ctx.params.push(`${filter.value}%`);
|
|
602
|
+
command += "$" + ctx.params.length;
|
|
603
|
+
}
|
|
574
604
|
return command;
|
|
575
605
|
}
|
|
576
606
|
function buildNotStartsWithFilterQuery(ctx, filter) {
|
|
577
607
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
578
608
|
command += " NOT LIKE ";
|
|
579
|
-
ctx.
|
|
580
|
-
|
|
609
|
+
if (ctx.paramToLiteral) ;
|
|
610
|
+
else {
|
|
611
|
+
ctx.params.push(`${filter.value}%`);
|
|
612
|
+
command += "$" + ctx.params.length;
|
|
613
|
+
}
|
|
581
614
|
return command;
|
|
582
615
|
}
|
|
583
616
|
function buildEndsWithFilterQuery(ctx, filter) {
|
|
584
617
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
585
618
|
command += " LIKE ";
|
|
586
|
-
ctx.
|
|
587
|
-
|
|
619
|
+
if (ctx.paramToLiteral) ;
|
|
620
|
+
else {
|
|
621
|
+
ctx.params.push(`%${filter.value}`);
|
|
622
|
+
command += "$" + ctx.params.length;
|
|
623
|
+
}
|
|
588
624
|
return command;
|
|
589
625
|
}
|
|
590
626
|
function buildNotEndsWithFilterQuery(ctx, filter) {
|
|
591
627
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
592
628
|
command += " NOT LIKE ";
|
|
593
|
-
ctx.
|
|
594
|
-
|
|
629
|
+
if (ctx.paramToLiteral) ;
|
|
630
|
+
else {
|
|
631
|
+
ctx.params.push(`%${filter.value}`);
|
|
632
|
+
command += "$" + ctx.params.length;
|
|
633
|
+
}
|
|
595
634
|
return command;
|
|
596
635
|
}
|
|
597
636
|
function buildRelationalFilterQuery(ctx, filter) {
|
|
598
637
|
let command = ctx.builder.quoteColumn(ctx.model, filter.field, ctx.emitTableAlias);
|
|
599
638
|
command += relationalOperatorsMap.get(filter.operator);
|
|
600
|
-
ctx.
|
|
601
|
-
|
|
639
|
+
if (ctx.paramToLiteral) {
|
|
640
|
+
command += formatValueToSqlLiteral(filter.value);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
ctx.params.push(filter.value);
|
|
644
|
+
command += "$" + ctx.params.length;
|
|
645
|
+
}
|
|
602
646
|
return command;
|
|
647
|
+
}
|
|
648
|
+
function formatValueToSqlLiteral(value) {
|
|
649
|
+
if (lodash.isNull(value) || lodash.isUndefined(value)) {
|
|
650
|
+
return "null";
|
|
651
|
+
}
|
|
652
|
+
if (lodash.isString(value)) {
|
|
653
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
654
|
+
}
|
|
655
|
+
if (lodash.isBoolean(value)) {
|
|
656
|
+
return value ? "true" : "false";
|
|
657
|
+
}
|
|
658
|
+
if (lodash.isNumber(value)) {
|
|
659
|
+
return value.toString();
|
|
660
|
+
}
|
|
661
|
+
return `'${value.toString().replaceAll("'", "''")}'`;
|
|
603
662
|
}
|
|
604
663
|
|
|
605
664
|
class PluginManager {
|
|
@@ -1392,6 +1451,42 @@ var bootstrapApplicationConfig = {
|
|
|
1392
1451
|
targetSingularCode: "property",
|
|
1393
1452
|
selfIdColumnName: "model_id",
|
|
1394
1453
|
},
|
|
1454
|
+
{
|
|
1455
|
+
name: "indexes",
|
|
1456
|
+
code: "indexes",
|
|
1457
|
+
columnName: "indexes",
|
|
1458
|
+
type: "json",
|
|
1459
|
+
required: false,
|
|
1460
|
+
},
|
|
1461
|
+
],
|
|
1462
|
+
indexes: [
|
|
1463
|
+
{
|
|
1464
|
+
name: "meta_models_singular_code_uindex",
|
|
1465
|
+
properties: [
|
|
1466
|
+
{
|
|
1467
|
+
code: "singularCode",
|
|
1468
|
+
},
|
|
1469
|
+
],
|
|
1470
|
+
unique: true,
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "meta_models_table_name_uindex",
|
|
1474
|
+
properties: [
|
|
1475
|
+
{
|
|
1476
|
+
code: "tableName",
|
|
1477
|
+
},
|
|
1478
|
+
],
|
|
1479
|
+
unique: true,
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
name: "meta_models_name_index",
|
|
1483
|
+
properties: [
|
|
1484
|
+
{
|
|
1485
|
+
code: "name",
|
|
1486
|
+
},
|
|
1487
|
+
],
|
|
1488
|
+
unique: false,
|
|
1489
|
+
},
|
|
1395
1490
|
],
|
|
1396
1491
|
},
|
|
1397
1492
|
{
|
|
@@ -1549,6 +1644,20 @@ var bootstrapApplicationConfig = {
|
|
|
1549
1644
|
required: false,
|
|
1550
1645
|
},
|
|
1551
1646
|
],
|
|
1647
|
+
indexes: [
|
|
1648
|
+
{
|
|
1649
|
+
name: "meta_properties_model_id_code_uindex",
|
|
1650
|
+
properties: [
|
|
1651
|
+
{
|
|
1652
|
+
code: "model",
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
code: "code",
|
|
1656
|
+
},
|
|
1657
|
+
],
|
|
1658
|
+
unique: true,
|
|
1659
|
+
},
|
|
1660
|
+
],
|
|
1552
1661
|
},
|
|
1553
1662
|
{
|
|
1554
1663
|
maintainedBy: "metaManager",
|
|
@@ -2672,6 +2781,24 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2672
2781
|
sender: plugin,
|
|
2673
2782
|
routeContext,
|
|
2674
2783
|
});
|
|
2784
|
+
// check unique constraints
|
|
2785
|
+
if (!options.postponeUniquenessCheck) {
|
|
2786
|
+
if (model.indexes && model.indexes.length) {
|
|
2787
|
+
for (const indexConfig of model.indexes) {
|
|
2788
|
+
if (!indexConfig.unique) {
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
const duplicate = await willEntityDuplicate(server, dataAccessor, {
|
|
2792
|
+
routeContext: options.routeContext,
|
|
2793
|
+
entityToSave: entity,
|
|
2794
|
+
indexConfig,
|
|
2795
|
+
});
|
|
2796
|
+
if (duplicate) {
|
|
2797
|
+
throw new Error(getEntityDuplicatedErrorMessage(server, model, indexConfig));
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2675
2802
|
const oneRelationPropertiesToCreate = [];
|
|
2676
2803
|
const manyRelationPropertiesToCreate = [];
|
|
2677
2804
|
lodash.keys(entity).forEach((propertyCode) => {
|
|
@@ -2884,6 +3011,25 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
2884
3011
|
routeContext: options.routeContext,
|
|
2885
3012
|
});
|
|
2886
3013
|
changes = getEntityPartChanges(server, model, entity, entityToSave);
|
|
3014
|
+
// check unique constraints
|
|
3015
|
+
if (!options.postponeUniquenessCheck) {
|
|
3016
|
+
if (model.indexes && model.indexes.length) {
|
|
3017
|
+
for (const indexConfig of model.indexes) {
|
|
3018
|
+
if (!indexConfig.unique) {
|
|
3019
|
+
continue;
|
|
3020
|
+
}
|
|
3021
|
+
const duplicate = await willEntityDuplicate(server, dataAccessor, {
|
|
3022
|
+
routeContext: options.routeContext,
|
|
3023
|
+
entityId: id,
|
|
3024
|
+
entityToSave: changes,
|
|
3025
|
+
indexConfig,
|
|
3026
|
+
});
|
|
3027
|
+
if (duplicate) {
|
|
3028
|
+
throw new Error(getEntityDuplicatedErrorMessage(server, model, indexConfig));
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
2887
3033
|
const oneRelationPropertiesToUpdate = [];
|
|
2888
3034
|
const manyRelationPropertiesToUpdate = [];
|
|
2889
3035
|
lodash.keys(changes).forEach((propertyCode) => {
|
|
@@ -3065,6 +3211,55 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3065
3211
|
});
|
|
3066
3212
|
return updatedEntity;
|
|
3067
3213
|
}
|
|
3214
|
+
async function willEntityDuplicate(server, dataAccessor, options) {
|
|
3215
|
+
const { entityId, entityToSave, routeContext, indexConfig } = options;
|
|
3216
|
+
let filters = [];
|
|
3217
|
+
if (indexConfig.conditions) {
|
|
3218
|
+
filters = lodash.cloneDeep(indexConfig.conditions);
|
|
3219
|
+
}
|
|
3220
|
+
for (const propConfig of indexConfig.properties) {
|
|
3221
|
+
let propCode;
|
|
3222
|
+
if (lodash.isString(propConfig)) {
|
|
3223
|
+
propCode = propConfig;
|
|
3224
|
+
}
|
|
3225
|
+
else {
|
|
3226
|
+
propCode = propConfig.code;
|
|
3227
|
+
}
|
|
3228
|
+
if (!entityToSave.hasOwnProperty(propCode)) {
|
|
3229
|
+
// skip duplicate checking when any index prop missing in entityToSave.
|
|
3230
|
+
return false;
|
|
3231
|
+
}
|
|
3232
|
+
filters.push({
|
|
3233
|
+
operator: "eq",
|
|
3234
|
+
field: propCode,
|
|
3235
|
+
value: entityToSave[propCode],
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
const entityInDb = await findEntity(server, dataAccessor, {
|
|
3239
|
+
filters,
|
|
3240
|
+
routeContext,
|
|
3241
|
+
});
|
|
3242
|
+
if (entityId) {
|
|
3243
|
+
return entityInDb && entityInDb.Id !== entityId;
|
|
3244
|
+
}
|
|
3245
|
+
else {
|
|
3246
|
+
return !!entityInDb;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
function getEntityDuplicatedErrorMessage(server, model, indexConfig) {
|
|
3250
|
+
const propertyNames = indexConfig.properties.map((propConfig) => {
|
|
3251
|
+
let propCode;
|
|
3252
|
+
if (lodash.isString(propConfig)) {
|
|
3253
|
+
propCode = propConfig;
|
|
3254
|
+
}
|
|
3255
|
+
else {
|
|
3256
|
+
propCode = propConfig.code;
|
|
3257
|
+
}
|
|
3258
|
+
const prop = getEntityPropertyByCode(server, model, propCode);
|
|
3259
|
+
return prop.name;
|
|
3260
|
+
});
|
|
3261
|
+
return `已存在 ${propertyNames.join(", ")} 相同的记录。`;
|
|
3262
|
+
}
|
|
3068
3263
|
class EntityManager {
|
|
3069
3264
|
#server;
|
|
3070
3265
|
#dataAccessor;
|
|
@@ -4056,6 +4251,16 @@ async function syncDatabaseSchema(server, applicationConfig) {
|
|
|
4056
4251
|
await server.queryDatabaseObject(`ALTER TABLE ${queryBuilder.quoteTable(model)} ADD CONSTRAINT ${queryBuilder.quoteObject(expectedContraintName)} PRIMARY KEY (id);`, []);
|
|
4057
4252
|
}
|
|
4058
4253
|
}
|
|
4254
|
+
// generate indexes
|
|
4255
|
+
for (const model of applicationConfig.models) {
|
|
4256
|
+
if (!model.indexes || !model.indexes.length) {
|
|
4257
|
+
continue;
|
|
4258
|
+
}
|
|
4259
|
+
for (const index of model.indexes) {
|
|
4260
|
+
const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
|
|
4261
|
+
await server.tryQueryDatabaseObject(sqlCreateIndex, []);
|
|
4262
|
+
}
|
|
4263
|
+
}
|
|
4059
4264
|
}
|
|
4060
4265
|
function generateCreateColumnDDL(queryBuilder, options) {
|
|
4061
4266
|
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
@@ -4088,6 +4293,50 @@ function generateLinkTableDDL(queryBuilder, options) {
|
|
|
4088
4293
|
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null);`;
|
|
4089
4294
|
return columnDDL;
|
|
4090
4295
|
}
|
|
4296
|
+
function generateTableIndexDDL(queryBuilder, server, model, index) {
|
|
4297
|
+
let indexName = index.name;
|
|
4298
|
+
if (!indexName) {
|
|
4299
|
+
indexName = model.tableName;
|
|
4300
|
+
for (const indexProp of index.properties) {
|
|
4301
|
+
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
4302
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
4303
|
+
if (!isRelationProperty(property)) {
|
|
4304
|
+
indexName += "_" + property.columnName;
|
|
4305
|
+
}
|
|
4306
|
+
else if (isOneRelationProperty(property)) {
|
|
4307
|
+
indexName += "_" + property.targetIdColumnName;
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
indexName += index.unique ? "_uindex" : "_index";
|
|
4311
|
+
}
|
|
4312
|
+
const indexColumns = lodash.map(index.properties, (indexProp) => {
|
|
4313
|
+
let columnName;
|
|
4314
|
+
const propCode = lodash.isString(indexProp) ? indexProp : indexProp.code;
|
|
4315
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
4316
|
+
if (!isRelationProperty(property)) {
|
|
4317
|
+
columnName = property.columnName;
|
|
4318
|
+
}
|
|
4319
|
+
else if (isOneRelationProperty(property)) {
|
|
4320
|
+
columnName = property.targetIdColumnName;
|
|
4321
|
+
}
|
|
4322
|
+
if (lodash.isString(indexProp)) {
|
|
4323
|
+
return columnName;
|
|
4324
|
+
}
|
|
4325
|
+
if (indexProp.order === "desc") {
|
|
4326
|
+
return `${columnName} desc`;
|
|
4327
|
+
}
|
|
4328
|
+
return columnName;
|
|
4329
|
+
});
|
|
4330
|
+
let ddl = `CREATE ${index.unique ? "UNIQUE" : ""} INDEX ${indexName} `;
|
|
4331
|
+
ddl += `ON ${queryBuilder.quoteTable({
|
|
4332
|
+
schema: model.schema,
|
|
4333
|
+
tableName: model.tableName,
|
|
4334
|
+
})} (${indexColumns.join(", ")})`;
|
|
4335
|
+
if (index.conditions) {
|
|
4336
|
+
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, index.conditions)}`;
|
|
4337
|
+
}
|
|
4338
|
+
return ddl;
|
|
4339
|
+
}
|
|
4091
4340
|
const pgPropertyTypeColumnMap = {
|
|
4092
4341
|
integer: "int4",
|
|
4093
4342
|
long: "int8",
|
|
@@ -5,6 +5,10 @@ export interface BuildQueryContext {
|
|
|
5
5
|
builder: QueryBuilder;
|
|
6
6
|
params: any[];
|
|
7
7
|
emitTableAlias: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* emit parameter value to sql literal.
|
|
10
|
+
*/
|
|
11
|
+
paramToLiteral: boolean;
|
|
8
12
|
}
|
|
9
13
|
export interface InitQueryBuilderOptions {
|
|
10
14
|
dbDefaultSchema: string;
|
|
@@ -22,5 +26,6 @@ export default class QueryBuilder {
|
|
|
22
26
|
insert(model: RpdDataModel, options: CreateEntityOptions): DatabaseQuery;
|
|
23
27
|
update(model: RpdDataModel, options: UpdateRowOptions): DatabaseQuery;
|
|
24
28
|
delete(model: RpdDataModel, options: DeleteRowOptions): DatabaseQuery;
|
|
29
|
+
buildFiltersExpression(model: RpdDataModel, filters: RowFilterOptions[]): string;
|
|
25
30
|
}
|
|
26
31
|
export declare function buildFiltersQuery(ctx: BuildQueryContext, filters: RowFilterOptions[]): string;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RouteContext } from "./core/routeContext";
|
|
2
2
|
import { IRpdServer, RapidPlugin } from "./core/server";
|
|
3
|
-
import { ColumnSelectOptions, CountRowOptions, FindRowOptions } from "./dataAccess/dataAccessTypes";
|
|
3
|
+
import { ColumnSelectOptions, CountRowOptions, FindRowOptions, RowFilterOptions } from "./dataAccess/dataAccessTypes";
|
|
4
4
|
export type RapidServerConfig = {
|
|
5
5
|
baseUrl?: string;
|
|
6
6
|
sessionCookieName: string;
|
|
@@ -148,6 +148,7 @@ export interface QuoteTableOptions {
|
|
|
148
148
|
export interface IQueryBuilder {
|
|
149
149
|
quoteTable: (options: QuoteTableOptions) => string;
|
|
150
150
|
quoteObject: (name: string) => string;
|
|
151
|
+
buildFiltersExpression(model: RpdDataModel, filters: RowFilterOptions[]): any;
|
|
151
152
|
}
|
|
152
153
|
export interface RpdApplicationConfig {
|
|
153
154
|
code?: string;
|
|
@@ -180,6 +181,7 @@ export interface RpdDataModel {
|
|
|
180
181
|
*/
|
|
181
182
|
displayPropertyCode?: string;
|
|
182
183
|
properties: RpdDataModelProperty[];
|
|
184
|
+
indexes?: RpdDataModelIndex[];
|
|
183
185
|
extensions?: RpdDataModelExtension[];
|
|
184
186
|
permissionPolicies?: RpdDataModelPermissionPolicies;
|
|
185
187
|
}
|
|
@@ -320,6 +322,17 @@ export interface RpdDataModelExtension {
|
|
|
320
322
|
code: string;
|
|
321
323
|
config: any;
|
|
322
324
|
}
|
|
325
|
+
export interface RpdDataModelIndex {
|
|
326
|
+
name?: string;
|
|
327
|
+
unique?: boolean;
|
|
328
|
+
properties: RpdDataModelIndexPropertyConfig[];
|
|
329
|
+
conditions?: RpdDataModelIndexOptions[];
|
|
330
|
+
}
|
|
331
|
+
export type RpdDataModelIndexPropertyConfig = string | {
|
|
332
|
+
code: string;
|
|
333
|
+
order?: "asc" | "desc";
|
|
334
|
+
};
|
|
335
|
+
export type RpdDataModelIndexOptions = FindEntityRelationalFilterOptions | FindEntitySetFilterOptions | FindEntityLogicalFilterOptions<RpdDataModelIndexOptions> | FindEntityUnaryFilterOptions;
|
|
323
336
|
export type EventHandler<T = any> = (sender: RapidPlugin, payload: T) => void;
|
|
324
337
|
export interface RpdRoute {
|
|
325
338
|
name: string;
|
|
@@ -381,9 +394,9 @@ export interface FindEntitySetFilterOptions {
|
|
|
381
394
|
value: any[];
|
|
382
395
|
itemType?: string;
|
|
383
396
|
}
|
|
384
|
-
export interface FindEntityLogicalFilterOptions {
|
|
397
|
+
export interface FindEntityLogicalFilterOptions<TFilter = EntityFilterOptions> {
|
|
385
398
|
operator: EntityFilterLogicalOperators;
|
|
386
|
-
filters:
|
|
399
|
+
filters: TFilter[];
|
|
387
400
|
}
|
|
388
401
|
export interface FindEntityUnaryFilterOptions {
|
|
389
402
|
field: string;
|
|
@@ -421,6 +434,7 @@ export interface DeleteEntityByIdOptions {
|
|
|
421
434
|
export interface CreateEntityOptions {
|
|
422
435
|
routeContext?: RouteContext;
|
|
423
436
|
entity: any;
|
|
437
|
+
postponeUniquenessCheck?: boolean;
|
|
424
438
|
}
|
|
425
439
|
export interface UpdateEntityOptions {
|
|
426
440
|
routeContext?: RouteContext;
|
|
@@ -435,6 +449,7 @@ export interface UpdateEntityByIdOptions {
|
|
|
435
449
|
type: string;
|
|
436
450
|
};
|
|
437
451
|
stateProperties?: string[];
|
|
452
|
+
postponeUniquenessCheck?: boolean;
|
|
438
453
|
}
|
|
439
454
|
export interface DeleteEntityOptions {
|
|
440
455
|
routeContext?: RouteContext;
|