@ruiapp/rapid-core 0.2.5 → 0.2.7
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/helpers/filterHelper.d.ts +3 -2
- package/dist/index.js +135 -35
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/bootstrapApplicationConfig.ts +8 -0
- package/src/dataAccess/entityManager.ts +116 -26
- package/src/helpers/filterHelper.ts +21 -8
- package/src/plugins/metaManage/MetaManagePlugin.ts +2 -1
- package/src/types.ts +5 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EntityFilterOptions, RpdDataModel, RpdDataModelIndexOptions } from "../types";
|
|
2
2
|
import { RowFilterOptions } from "../dataAccess/dataAccessTypes";
|
|
3
|
+
import { Logger } from "../facilities/log/LogFacility";
|
|
3
4
|
export declare function removeFiltersWithNullValue(filters?: EntityFilterOptions[]): EntityFilterOptions[];
|
|
4
|
-
export declare function convertModelIndexConditionsToRowFilterOptions(model: RpdDataModel, filters: RpdDataModelIndexOptions[]): RowFilterOptions[];
|
|
5
|
-
export declare function replaceModelIndexConditionEntityPropertyWithTableColumn(model: RpdDataModel, filter: RpdDataModelIndexOptions): RowFilterOptions;
|
|
5
|
+
export declare function convertModelIndexConditionsToRowFilterOptions(logger: Logger, model: RpdDataModel, filters: RpdDataModelIndexOptions[]): RowFilterOptions[];
|
|
6
|
+
export declare function replaceModelIndexConditionEntityPropertyWithTableColumn(logger: Logger, model: RpdDataModel, filter: RpdDataModelIndexOptions): RowFilterOptions;
|
package/dist/index.js
CHANGED
|
@@ -1649,6 +1649,14 @@ var bootstrapApplicationConfig = {
|
|
|
1649
1649
|
type: "text",
|
|
1650
1650
|
required: false,
|
|
1651
1651
|
},
|
|
1652
|
+
{
|
|
1653
|
+
name: "readonly",
|
|
1654
|
+
code: "readonly",
|
|
1655
|
+
columnName: "readonly",
|
|
1656
|
+
type: "boolean",
|
|
1657
|
+
required: true,
|
|
1658
|
+
defaultValue: "false",
|
|
1659
|
+
},
|
|
1652
1660
|
{
|
|
1653
1661
|
name: "config",
|
|
1654
1662
|
code: "config",
|
|
@@ -2964,7 +2972,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2964
2972
|
const newEntityOneRelationProps = {};
|
|
2965
2973
|
// save one-relation properties
|
|
2966
2974
|
for (const property of oneRelationPropertiesToCreate) {
|
|
2967
|
-
const
|
|
2975
|
+
const rowToBeSaved = property.isBaseProperty ? baseRow : row;
|
|
2968
2976
|
const fieldValue = entity[property.code];
|
|
2969
2977
|
const targetDataAccessor = server.getDataAccessor({
|
|
2970
2978
|
singularCode: property.targetSingularCode,
|
|
@@ -2972,13 +2980,15 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2972
2980
|
if (lodash.isObject(fieldValue)) {
|
|
2973
2981
|
const targetEntityId = fieldValue["id"];
|
|
2974
2982
|
if (!targetEntityId) {
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2983
|
+
if (!property.selfIdColumnName) {
|
|
2984
|
+
const targetEntity = fieldValue;
|
|
2985
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
2986
|
+
routeContext,
|
|
2987
|
+
entity: targetEntity,
|
|
2988
|
+
});
|
|
2989
|
+
newEntityOneRelationProps[property.code] = newTargetEntity;
|
|
2990
|
+
rowToBeSaved[property.targetIdColumnName] = newTargetEntity["id"];
|
|
2991
|
+
}
|
|
2982
2992
|
}
|
|
2983
2993
|
else {
|
|
2984
2994
|
const targetEntity = await findById(server, targetDataAccessor, {
|
|
@@ -2989,7 +2999,7 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
2989
2999
|
throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
|
|
2990
3000
|
}
|
|
2991
3001
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
2992
|
-
|
|
3002
|
+
rowToBeSaved[property.targetIdColumnName] = targetEntityId;
|
|
2993
3003
|
}
|
|
2994
3004
|
}
|
|
2995
3005
|
else if (lodash.isNumber(fieldValue) || lodash.isString(fieldValue)) {
|
|
@@ -3003,16 +3013,17 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
3003
3013
|
throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
|
|
3004
3014
|
}
|
|
3005
3015
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
3006
|
-
|
|
3016
|
+
rowToBeSaved[property.targetIdColumnName] = targetEntityId;
|
|
3007
3017
|
}
|
|
3008
3018
|
else {
|
|
3009
3019
|
newEntityOneRelationProps[property.code] = null;
|
|
3010
|
-
|
|
3020
|
+
rowToBeSaved[property.targetIdColumnName] = null;
|
|
3011
3021
|
}
|
|
3012
3022
|
}
|
|
3013
3023
|
let newBaseRow;
|
|
3024
|
+
let baseDataAccessor;
|
|
3014
3025
|
if (model.base) {
|
|
3015
|
-
|
|
3026
|
+
baseDataAccessor = server.getDataAccessor({
|
|
3016
3027
|
singularCode: model.base,
|
|
3017
3028
|
});
|
|
3018
3029
|
newBaseRow = await baseDataAccessor.create(baseRow);
|
|
@@ -3020,6 +3031,35 @@ async function createEntity(server, dataAccessor, options, plugin) {
|
|
|
3020
3031
|
}
|
|
3021
3032
|
const newRow = await dataAccessor.create(row);
|
|
3022
3033
|
const newEntity = mapDbRowToEntity(server, model, Object.assign({}, newBaseRow, newRow, newEntityOneRelationProps), true);
|
|
3034
|
+
// save one-relation properties that has selfIdColumnName
|
|
3035
|
+
for (const property of oneRelationPropertiesToCreate) {
|
|
3036
|
+
const fieldValue = entity[property.code];
|
|
3037
|
+
const targetDataAccessor = server.getDataAccessor({
|
|
3038
|
+
singularCode: property.targetSingularCode,
|
|
3039
|
+
});
|
|
3040
|
+
if (lodash.isObject(fieldValue)) {
|
|
3041
|
+
const targetEntityId = fieldValue["id"];
|
|
3042
|
+
if (!targetEntityId) {
|
|
3043
|
+
if (property.selfIdColumnName) {
|
|
3044
|
+
const targetEntity = fieldValue;
|
|
3045
|
+
targetEntity[property.selfIdColumnName] = newEntity.id;
|
|
3046
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
3047
|
+
routeContext,
|
|
3048
|
+
entity: targetEntity,
|
|
3049
|
+
});
|
|
3050
|
+
let dataAccessorOfMainEntity = dataAccessor;
|
|
3051
|
+
if (property.isBaseProperty) {
|
|
3052
|
+
dataAccessorOfMainEntity = baseDataAccessor;
|
|
3053
|
+
}
|
|
3054
|
+
const relationFieldChanges = {
|
|
3055
|
+
[property.targetIdColumnName]: newTargetEntity.id,
|
|
3056
|
+
};
|
|
3057
|
+
await dataAccessorOfMainEntity.updateById(newEntity.id, relationFieldChanges);
|
|
3058
|
+
newEntity[property.code] = newTargetEntity;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3023
3063
|
// save many-relation properties
|
|
3024
3064
|
for (const property of manyRelationPropertiesToCreate) {
|
|
3025
3065
|
newEntity[property.code] = [];
|
|
@@ -3157,6 +3197,23 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3157
3197
|
routeContext: options.routeContext,
|
|
3158
3198
|
});
|
|
3159
3199
|
changes = getEntityPartChanges(server, model, entity, entityToSave);
|
|
3200
|
+
// check readonly properties
|
|
3201
|
+
Object.keys(changes).forEach((propertyName) => {
|
|
3202
|
+
let isReadonlyProperty = false;
|
|
3203
|
+
const property = getEntityPropertyByCode(server, model, propertyName);
|
|
3204
|
+
if (property && property.readonly) {
|
|
3205
|
+
isReadonlyProperty = true;
|
|
3206
|
+
}
|
|
3207
|
+
else {
|
|
3208
|
+
const oneRelationProperty = getEntityProperty(server, model, (item) => item.relation === "one" && item.targetIdColumnName === propertyName);
|
|
3209
|
+
if (oneRelationProperty && oneRelationProperty.readonly) {
|
|
3210
|
+
isReadonlyProperty = true;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
if (isReadonlyProperty) {
|
|
3214
|
+
throw new Error(`Updating "${property.name}" property is not allowed because it's readonly.`);
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3160
3217
|
// check unique constraints
|
|
3161
3218
|
if (!options.postponeUniquenessCheck) {
|
|
3162
3219
|
if (model.indexes && model.indexes.length) {
|
|
@@ -3196,7 +3253,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3196
3253
|
const { row, baseRow } = mapEntityToDbRow(server, model, changes);
|
|
3197
3254
|
const updatedEntityOneRelationProps = {};
|
|
3198
3255
|
for (const property of oneRelationPropertiesToUpdate) {
|
|
3199
|
-
const
|
|
3256
|
+
const rowToBeSaved = property.isBaseProperty ? baseRow : row;
|
|
3200
3257
|
const fieldValue = changes[property.code];
|
|
3201
3258
|
const targetDataAccessor = server.getDataAccessor({
|
|
3202
3259
|
singularCode: property.targetSingularCode,
|
|
@@ -3204,13 +3261,15 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3204
3261
|
if (lodash.isObject(fieldValue)) {
|
|
3205
3262
|
const targetEntityId = fieldValue["id"];
|
|
3206
3263
|
if (!targetEntityId) {
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3264
|
+
if (!property.selfIdColumnName) {
|
|
3265
|
+
const targetEntity = fieldValue;
|
|
3266
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
3267
|
+
routeContext,
|
|
3268
|
+
entity: targetEntity,
|
|
3269
|
+
});
|
|
3270
|
+
updatedEntityOneRelationProps[property.code] = newTargetEntity;
|
|
3271
|
+
rowToBeSaved[property.targetIdColumnName] = newTargetEntity["id"];
|
|
3272
|
+
}
|
|
3214
3273
|
}
|
|
3215
3274
|
else {
|
|
3216
3275
|
const targetEntity = await findById(server, targetDataAccessor, {
|
|
@@ -3221,7 +3280,7 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3221
3280
|
throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
|
|
3222
3281
|
}
|
|
3223
3282
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
3224
|
-
|
|
3283
|
+
rowToBeSaved[property.targetIdColumnName] = targetEntityId;
|
|
3225
3284
|
}
|
|
3226
3285
|
}
|
|
3227
3286
|
else if (lodash.isNumber(fieldValue) || lodash.isString(fieldValue)) {
|
|
@@ -3235,11 +3294,11 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3235
3294
|
throw newEntityOperationError(`Create ${model.singularCode} entity failed. Property '${property.code}' was invalid. Related ${property.targetSingularCode} entity with id '${targetEntityId}' was not found.`);
|
|
3236
3295
|
}
|
|
3237
3296
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
3238
|
-
|
|
3297
|
+
rowToBeSaved[property.targetIdColumnName] = targetEntityId;
|
|
3239
3298
|
}
|
|
3240
3299
|
else {
|
|
3241
3300
|
updatedEntityOneRelationProps[property.code] = null;
|
|
3242
|
-
|
|
3301
|
+
rowToBeSaved[property.targetIdColumnName] = null;
|
|
3243
3302
|
}
|
|
3244
3303
|
}
|
|
3245
3304
|
let updatedRow = row;
|
|
@@ -3247,14 +3306,47 @@ async function updateEntityById(server, dataAccessor, options, plugin) {
|
|
|
3247
3306
|
updatedRow = await dataAccessor.updateById(id, row);
|
|
3248
3307
|
}
|
|
3249
3308
|
let updatedBaseRow = baseRow;
|
|
3250
|
-
|
|
3251
|
-
|
|
3309
|
+
let baseDataAccessor;
|
|
3310
|
+
if (model.base) {
|
|
3311
|
+
baseDataAccessor = server.getDataAccessor({
|
|
3252
3312
|
singularCode: model.base,
|
|
3253
3313
|
});
|
|
3254
|
-
|
|
3314
|
+
if (Object.keys(baseRow).length) {
|
|
3315
|
+
updatedBaseRow = await baseDataAccessor.updateById(id, updatedBaseRow);
|
|
3316
|
+
}
|
|
3255
3317
|
}
|
|
3256
3318
|
let updatedEntity = mapDbRowToEntity(server, model, { ...updatedRow, ...updatedBaseRow, ...updatedEntityOneRelationProps }, true);
|
|
3257
3319
|
updatedEntity = Object.assign({}, entity, updatedEntity);
|
|
3320
|
+
// save one-relation properties that has selfIdColumnName
|
|
3321
|
+
for (const property of oneRelationPropertiesToUpdate) {
|
|
3322
|
+
const fieldValue = changes[property.code];
|
|
3323
|
+
const targetDataAccessor = server.getDataAccessor({
|
|
3324
|
+
singularCode: property.targetSingularCode,
|
|
3325
|
+
});
|
|
3326
|
+
if (lodash.isObject(fieldValue)) {
|
|
3327
|
+
const targetEntityId = fieldValue["id"];
|
|
3328
|
+
if (!targetEntityId) {
|
|
3329
|
+
if (property.selfIdColumnName) {
|
|
3330
|
+
const targetEntity = fieldValue;
|
|
3331
|
+
targetEntity[property.selfIdColumnName] = updatedEntity.id;
|
|
3332
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
3333
|
+
routeContext,
|
|
3334
|
+
entity: targetEntity,
|
|
3335
|
+
});
|
|
3336
|
+
let dataAccessorOfMainEntity = dataAccessor;
|
|
3337
|
+
if (property.isBaseProperty) {
|
|
3338
|
+
dataAccessorOfMainEntity = baseDataAccessor;
|
|
3339
|
+
}
|
|
3340
|
+
const relationFieldChanges = {
|
|
3341
|
+
[property.targetIdColumnName]: newTargetEntity.id,
|
|
3342
|
+
};
|
|
3343
|
+
await dataAccessorOfMainEntity.updateById(updatedEntity.id, relationFieldChanges);
|
|
3344
|
+
updatedEntity[property.code] = newTargetEntity;
|
|
3345
|
+
changes[property.code] = newTargetEntity;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3258
3350
|
// save many-relation properties
|
|
3259
3351
|
for (const property of manyRelationPropertiesToUpdate) {
|
|
3260
3352
|
const relatedEntities = [];
|
|
@@ -4215,7 +4307,7 @@ function transformFilterWithSubFilters(filter) {
|
|
|
4215
4307
|
filter.filters = subFilters;
|
|
4216
4308
|
return filter;
|
|
4217
4309
|
}
|
|
4218
|
-
function convertModelIndexConditionsToRowFilterOptions(model, filters) {
|
|
4310
|
+
function convertModelIndexConditionsToRowFilterOptions(logger, model, filters) {
|
|
4219
4311
|
if (!filters || !filters.length) {
|
|
4220
4312
|
return [];
|
|
4221
4313
|
}
|
|
@@ -4225,16 +4317,19 @@ function convertModelIndexConditionsToRowFilterOptions(model, filters) {
|
|
|
4225
4317
|
if (operator === "and" || operator === "or") {
|
|
4226
4318
|
replacedFilters.push({
|
|
4227
4319
|
operator: operator,
|
|
4228
|
-
filters: convertModelIndexConditionsToRowFilterOptions(model, filter.filters),
|
|
4320
|
+
filters: convertModelIndexConditionsToRowFilterOptions(logger, model, filter.filters),
|
|
4229
4321
|
});
|
|
4230
4322
|
}
|
|
4231
4323
|
else {
|
|
4232
|
-
|
|
4324
|
+
const replacedFilter = replaceModelIndexConditionEntityPropertyWithTableColumn(logger, model, filter);
|
|
4325
|
+
if (replacedFilter) {
|
|
4326
|
+
replacedFilters.push(replacedFilter);
|
|
4327
|
+
}
|
|
4233
4328
|
}
|
|
4234
4329
|
}
|
|
4235
4330
|
return replacedFilters;
|
|
4236
4331
|
}
|
|
4237
|
-
function replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter) {
|
|
4332
|
+
function replaceModelIndexConditionEntityPropertyWithTableColumn(logger, model, filter) {
|
|
4238
4333
|
const { operator } = filter;
|
|
4239
4334
|
const filterField = filter.field;
|
|
4240
4335
|
let property = getEntityOwnPropertyByCode(model, filterField);
|
|
@@ -4248,14 +4343,16 @@ function replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter)
|
|
|
4248
4343
|
}
|
|
4249
4344
|
}
|
|
4250
4345
|
else if (isManyRelationProperty(property)) {
|
|
4251
|
-
|
|
4346
|
+
logger.warn(`Index condition on many-relation property "${property.code}" is not supported.`);
|
|
4347
|
+
return null;
|
|
4252
4348
|
}
|
|
4253
4349
|
else {
|
|
4254
4350
|
columnName = property.columnName || property.code;
|
|
4255
4351
|
}
|
|
4256
4352
|
}
|
|
4257
4353
|
else if (operator === "exists" || operator === "notExists") {
|
|
4258
|
-
|
|
4354
|
+
logger.warn(`"exists" and "notExists" operators are not supported in index conditions.`);
|
|
4355
|
+
return null;
|
|
4259
4356
|
}
|
|
4260
4357
|
else {
|
|
4261
4358
|
property = getEntityOwnProperty(model, (property) => {
|
|
@@ -4271,7 +4368,8 @@ function replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter)
|
|
|
4271
4368
|
});
|
|
4272
4369
|
if (property) {
|
|
4273
4370
|
if (isManyRelationProperty(property)) {
|
|
4274
|
-
|
|
4371
|
+
logger.warn(`Index condition on many-relation property "${property.code}" is not supported.`);
|
|
4372
|
+
return null;
|
|
4275
4373
|
}
|
|
4276
4374
|
columnName = property.targetIdColumnName;
|
|
4277
4375
|
if (lodash.isPlainObject(filterValue)) {
|
|
@@ -4279,7 +4377,8 @@ function replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter)
|
|
|
4279
4377
|
}
|
|
4280
4378
|
}
|
|
4281
4379
|
else {
|
|
4282
|
-
|
|
4380
|
+
logger.warn(`Unknown field "${filterField}" in index conditions.`);
|
|
4381
|
+
return null;
|
|
4283
4382
|
}
|
|
4284
4383
|
}
|
|
4285
4384
|
}
|
|
@@ -4651,7 +4750,8 @@ function generateTableIndexDDL(queryBuilder, server, model, index) {
|
|
|
4651
4750
|
tableName: model.tableName,
|
|
4652
4751
|
})} (${indexColumns.join(", ")})`;
|
|
4653
4752
|
if (index.conditions) {
|
|
4654
|
-
const
|
|
4753
|
+
const logger = server.getLogger();
|
|
4754
|
+
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
4655
4755
|
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
4656
4756
|
}
|
|
4657
4757
|
return ddl;
|
package/dist/types.d.ts
CHANGED
|
@@ -272,6 +272,10 @@ export interface RpdDataModelProperty {
|
|
|
272
272
|
* 当设置了 linkTableName 时,可以设置关联关系表所在的 Schema。
|
|
273
273
|
*/
|
|
274
274
|
linkSchema?: string;
|
|
275
|
+
/**
|
|
276
|
+
* 当设置为`true`时,仅允许在创建时设置此属性的值,不允许更新。
|
|
277
|
+
*/
|
|
278
|
+
readonly?: boolean;
|
|
275
279
|
}
|
|
276
280
|
export type RpdDataPropertyTypes = "integer" | "long" | "float" | "double" | "decimal" | "text" | "boolean" | "date" | "time" | "datetime" | "json" | "relation" | "relation[]" | "option" | "option[]" | "file" | "file[]" | "image" | "image[]";
|
|
277
281
|
/**
|
package/package.json
CHANGED
|
@@ -229,6 +229,14 @@ export default {
|
|
|
229
229
|
type: "text",
|
|
230
230
|
required: false,
|
|
231
231
|
},
|
|
232
|
+
{
|
|
233
|
+
name: "readonly",
|
|
234
|
+
code: "readonly",
|
|
235
|
+
columnName: "readonly",
|
|
236
|
+
type: "boolean",
|
|
237
|
+
required: true,
|
|
238
|
+
defaultValue: "false",
|
|
239
|
+
},
|
|
232
240
|
{
|
|
233
241
|
name: "config",
|
|
234
242
|
code: "config",
|
|
@@ -796,7 +796,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
796
796
|
const newEntityOneRelationProps = {};
|
|
797
797
|
// save one-relation properties
|
|
798
798
|
for (const property of oneRelationPropertiesToCreate) {
|
|
799
|
-
const
|
|
799
|
+
const rowToBeSaved = property.isBaseProperty ? baseRow : row;
|
|
800
800
|
const fieldValue = entity[property.code];
|
|
801
801
|
const targetDataAccessor = server.getDataAccessor({
|
|
802
802
|
singularCode: property.targetSingularCode!,
|
|
@@ -804,13 +804,15 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
804
804
|
if (isObject(fieldValue)) {
|
|
805
805
|
const targetEntityId = fieldValue["id"];
|
|
806
806
|
if (!targetEntityId) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
807
|
+
if (!property.selfIdColumnName) {
|
|
808
|
+
const targetEntity = fieldValue;
|
|
809
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
810
|
+
routeContext,
|
|
811
|
+
entity: targetEntity,
|
|
812
|
+
});
|
|
813
|
+
newEntityOneRelationProps[property.code] = newTargetEntity;
|
|
814
|
+
rowToBeSaved[property.targetIdColumnName!] = newTargetEntity["id"];
|
|
815
|
+
}
|
|
814
816
|
} else {
|
|
815
817
|
const targetEntity = await findById(server, targetDataAccessor, {
|
|
816
818
|
id: targetEntityId,
|
|
@@ -822,7 +824,7 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
822
824
|
);
|
|
823
825
|
}
|
|
824
826
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
825
|
-
|
|
827
|
+
rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
|
|
826
828
|
}
|
|
827
829
|
} else if (isNumber(fieldValue) || isString(fieldValue)) {
|
|
828
830
|
// fieldValue is id;
|
|
@@ -837,16 +839,17 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
837
839
|
);
|
|
838
840
|
}
|
|
839
841
|
newEntityOneRelationProps[property.code] = targetEntity;
|
|
840
|
-
|
|
842
|
+
rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
|
|
841
843
|
} else {
|
|
842
844
|
newEntityOneRelationProps[property.code] = null;
|
|
843
|
-
|
|
845
|
+
rowToBeSaved[property.targetIdColumnName!] = null;
|
|
844
846
|
}
|
|
845
847
|
}
|
|
846
848
|
|
|
847
849
|
let newBaseRow: any;
|
|
850
|
+
let baseDataAccessor: any;
|
|
848
851
|
if (model.base) {
|
|
849
|
-
|
|
852
|
+
baseDataAccessor = server.getDataAccessor({
|
|
850
853
|
singularCode: model.base,
|
|
851
854
|
});
|
|
852
855
|
newBaseRow = await baseDataAccessor.create(baseRow);
|
|
@@ -856,6 +859,38 @@ async function createEntity(server: IRpdServer, dataAccessor: IRpdDataAccessor,
|
|
|
856
859
|
const newRow = await dataAccessor.create(row);
|
|
857
860
|
const newEntity = mapDbRowToEntity(server, model, Object.assign({}, newBaseRow, newRow, newEntityOneRelationProps), true);
|
|
858
861
|
|
|
862
|
+
// save one-relation properties that has selfIdColumnName
|
|
863
|
+
for (const property of oneRelationPropertiesToCreate) {
|
|
864
|
+
const fieldValue = entity[property.code];
|
|
865
|
+
const targetDataAccessor = server.getDataAccessor({
|
|
866
|
+
singularCode: property.targetSingularCode!,
|
|
867
|
+
});
|
|
868
|
+
if (isObject(fieldValue)) {
|
|
869
|
+
const targetEntityId = fieldValue["id"];
|
|
870
|
+
if (!targetEntityId) {
|
|
871
|
+
if (property.selfIdColumnName) {
|
|
872
|
+
const targetEntity = fieldValue;
|
|
873
|
+
targetEntity[property.selfIdColumnName] = newEntity.id;
|
|
874
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
875
|
+
routeContext,
|
|
876
|
+
entity: targetEntity,
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
let dataAccessorOfMainEntity = dataAccessor;
|
|
880
|
+
if (property.isBaseProperty) {
|
|
881
|
+
dataAccessorOfMainEntity = baseDataAccessor;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const relationFieldChanges = {
|
|
885
|
+
[property.targetIdColumnName]: newTargetEntity.id,
|
|
886
|
+
};
|
|
887
|
+
await dataAccessorOfMainEntity.updateById(newEntity.id, relationFieldChanges);
|
|
888
|
+
newEntity[property.code] = newTargetEntity;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
859
894
|
// save many-relation properties
|
|
860
895
|
for (const property of manyRelationPropertiesToCreate) {
|
|
861
896
|
newEntity[property.code] = [];
|
|
@@ -1008,6 +1043,23 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1008
1043
|
|
|
1009
1044
|
changes = getEntityPartChanges(server, model, entity, entityToSave);
|
|
1010
1045
|
|
|
1046
|
+
// check readonly properties
|
|
1047
|
+
Object.keys(changes).forEach((propertyName) => {
|
|
1048
|
+
let isReadonlyProperty = false;
|
|
1049
|
+
const property = getEntityPropertyByCode(server, model, propertyName);
|
|
1050
|
+
if (property && property.readonly) {
|
|
1051
|
+
isReadonlyProperty = true;
|
|
1052
|
+
} else {
|
|
1053
|
+
const oneRelationProperty = getEntityProperty(server, model, (item) => item.relation === "one" && item.targetIdColumnName === propertyName);
|
|
1054
|
+
if (oneRelationProperty && oneRelationProperty.readonly) {
|
|
1055
|
+
isReadonlyProperty = true;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (isReadonlyProperty) {
|
|
1059
|
+
throw new Error(`Updating "${property.name}" property is not allowed because it's readonly.`);
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1011
1063
|
// check unique constraints
|
|
1012
1064
|
if (!options.postponeUniquenessCheck) {
|
|
1013
1065
|
if (model.indexes && model.indexes.length) {
|
|
@@ -1051,7 +1103,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1051
1103
|
|
|
1052
1104
|
const updatedEntityOneRelationProps = {};
|
|
1053
1105
|
for (const property of oneRelationPropertiesToUpdate) {
|
|
1054
|
-
const
|
|
1106
|
+
const rowToBeSaved = property.isBaseProperty ? baseRow : row;
|
|
1055
1107
|
const fieldValue = changes[property.code];
|
|
1056
1108
|
const targetDataAccessor = server.getDataAccessor({
|
|
1057
1109
|
singularCode: property.targetSingularCode!,
|
|
@@ -1060,13 +1112,15 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1060
1112
|
if (isObject(fieldValue)) {
|
|
1061
1113
|
const targetEntityId = fieldValue["id"];
|
|
1062
1114
|
if (!targetEntityId) {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1115
|
+
if (!property.selfIdColumnName) {
|
|
1116
|
+
const targetEntity = fieldValue;
|
|
1117
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
1118
|
+
routeContext,
|
|
1119
|
+
entity: targetEntity,
|
|
1120
|
+
});
|
|
1121
|
+
updatedEntityOneRelationProps[property.code] = newTargetEntity;
|
|
1122
|
+
rowToBeSaved[property.targetIdColumnName!] = newTargetEntity["id"];
|
|
1123
|
+
}
|
|
1070
1124
|
} else {
|
|
1071
1125
|
const targetEntity = await findById(server, targetDataAccessor, {
|
|
1072
1126
|
id: targetEntityId,
|
|
@@ -1078,7 +1132,7 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1078
1132
|
);
|
|
1079
1133
|
}
|
|
1080
1134
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
1081
|
-
|
|
1135
|
+
rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
|
|
1082
1136
|
}
|
|
1083
1137
|
} else if (isNumber(fieldValue) || isString(fieldValue)) {
|
|
1084
1138
|
// fieldValue is id;
|
|
@@ -1093,10 +1147,10 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1093
1147
|
);
|
|
1094
1148
|
}
|
|
1095
1149
|
updatedEntityOneRelationProps[property.code] = targetEntity;
|
|
1096
|
-
|
|
1150
|
+
rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
|
|
1097
1151
|
} else {
|
|
1098
1152
|
updatedEntityOneRelationProps[property.code] = null;
|
|
1099
|
-
|
|
1153
|
+
rowToBeSaved[property.targetIdColumnName!] = null;
|
|
1100
1154
|
}
|
|
1101
1155
|
}
|
|
1102
1156
|
|
|
@@ -1105,16 +1159,52 @@ async function updateEntityById(server: IRpdServer, dataAccessor: IRpdDataAccess
|
|
|
1105
1159
|
updatedRow = await dataAccessor.updateById(id, row);
|
|
1106
1160
|
}
|
|
1107
1161
|
let updatedBaseRow = baseRow;
|
|
1108
|
-
|
|
1109
|
-
|
|
1162
|
+
let baseDataAccessor: any;
|
|
1163
|
+
if (model.base) {
|
|
1164
|
+
baseDataAccessor = server.getDataAccessor({
|
|
1110
1165
|
singularCode: model.base,
|
|
1111
1166
|
});
|
|
1112
|
-
|
|
1167
|
+
if (Object.keys(baseRow).length) {
|
|
1168
|
+
updatedBaseRow = await baseDataAccessor.updateById(id, updatedBaseRow);
|
|
1169
|
+
}
|
|
1113
1170
|
}
|
|
1114
1171
|
|
|
1115
1172
|
let updatedEntity = mapDbRowToEntity(server, model, { ...updatedRow, ...updatedBaseRow, ...updatedEntityOneRelationProps }, true);
|
|
1116
1173
|
updatedEntity = Object.assign({}, entity, updatedEntity);
|
|
1117
1174
|
|
|
1175
|
+
// save one-relation properties that has selfIdColumnName
|
|
1176
|
+
for (const property of oneRelationPropertiesToUpdate) {
|
|
1177
|
+
const fieldValue = changes[property.code];
|
|
1178
|
+
const targetDataAccessor = server.getDataAccessor({
|
|
1179
|
+
singularCode: property.targetSingularCode!,
|
|
1180
|
+
});
|
|
1181
|
+
if (isObject(fieldValue)) {
|
|
1182
|
+
const targetEntityId = fieldValue["id"];
|
|
1183
|
+
if (!targetEntityId) {
|
|
1184
|
+
if (property.selfIdColumnName) {
|
|
1185
|
+
const targetEntity = fieldValue;
|
|
1186
|
+
targetEntity[property.selfIdColumnName] = updatedEntity.id;
|
|
1187
|
+
const newTargetEntity = await createEntity(server, targetDataAccessor, {
|
|
1188
|
+
routeContext,
|
|
1189
|
+
entity: targetEntity,
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
let dataAccessorOfMainEntity = dataAccessor;
|
|
1193
|
+
if (property.isBaseProperty) {
|
|
1194
|
+
dataAccessorOfMainEntity = baseDataAccessor;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const relationFieldChanges = {
|
|
1198
|
+
[property.targetIdColumnName]: newTargetEntity.id,
|
|
1199
|
+
};
|
|
1200
|
+
await dataAccessorOfMainEntity.updateById(updatedEntity.id, relationFieldChanges);
|
|
1201
|
+
updatedEntity[property.code] = newTargetEntity;
|
|
1202
|
+
changes[property.code] = newTargetEntity;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1118
1208
|
// save many-relation properties
|
|
1119
1209
|
for (const property of manyRelationPropertiesToUpdate) {
|
|
1120
1210
|
const relatedEntities: any[] = [];
|
|
@@ -12,6 +12,7 @@ import { getEntityOwnProperty, getEntityOwnPropertyByCode, isManyRelationPropert
|
|
|
12
12
|
import { IRpdServer } from "~/core/server";
|
|
13
13
|
import { isPlainObject } from "lodash";
|
|
14
14
|
import { RowFilterOptions } from "~/dataAccess/dataAccessTypes";
|
|
15
|
+
import { Logger } from "~/facilities/log/LogFacility";
|
|
15
16
|
|
|
16
17
|
export function removeFiltersWithNullValue(filters?: EntityFilterOptions[]) {
|
|
17
18
|
const result: EntityFilterOptions[] = [];
|
|
@@ -58,7 +59,7 @@ function transformFilterWithSubFilters(
|
|
|
58
59
|
return filter;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
export function convertModelIndexConditionsToRowFilterOptions(model: RpdDataModel, filters: RpdDataModelIndexOptions[]) {
|
|
62
|
+
export function convertModelIndexConditionsToRowFilterOptions(logger: Logger, model: RpdDataModel, filters: RpdDataModelIndexOptions[]) {
|
|
62
63
|
if (!filters || !filters.length) {
|
|
63
64
|
return [];
|
|
64
65
|
}
|
|
@@ -69,15 +70,23 @@ export function convertModelIndexConditionsToRowFilterOptions(model: RpdDataMode
|
|
|
69
70
|
if (operator === "and" || operator === "or") {
|
|
70
71
|
replacedFilters.push({
|
|
71
72
|
operator: operator,
|
|
72
|
-
filters: convertModelIndexConditionsToRowFilterOptions(model, filter.filters),
|
|
73
|
+
filters: convertModelIndexConditionsToRowFilterOptions(logger, model, filter.filters),
|
|
73
74
|
});
|
|
74
75
|
} else {
|
|
75
|
-
|
|
76
|
+
const replacedFilter = replaceModelIndexConditionEntityPropertyWithTableColumn(logger, model, filter);
|
|
77
|
+
if (replacedFilter) {
|
|
78
|
+
replacedFilters.push(replacedFilter);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
return replacedFilters;
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
|
|
85
|
+
export function replaceModelIndexConditionEntityPropertyWithTableColumn(
|
|
86
|
+
logger: Logger,
|
|
87
|
+
model: RpdDataModel,
|
|
88
|
+
filter: RpdDataModelIndexOptions,
|
|
89
|
+
): RowFilterOptions {
|
|
81
90
|
const { operator } = filter;
|
|
82
91
|
const filterField = (filter as EntityNonRelationPropertyFilterOptions).field;
|
|
83
92
|
let property: RpdDataModelProperty = getEntityOwnPropertyByCode(model, filterField);
|
|
@@ -92,12 +101,14 @@ export function replaceModelIndexConditionEntityPropertyWithTableColumn(model: R
|
|
|
92
101
|
filterValue = filterValue.id;
|
|
93
102
|
}
|
|
94
103
|
} else if (isManyRelationProperty(property)) {
|
|
95
|
-
|
|
104
|
+
logger.warn(`Index condition on many-relation property "${property.code}" is not supported.`);
|
|
105
|
+
return null;
|
|
96
106
|
} else {
|
|
97
107
|
columnName = property.columnName || property.code;
|
|
98
108
|
}
|
|
99
109
|
} else if ((operator as any) === "exists" || (operator as any) === "notExists") {
|
|
100
|
-
|
|
110
|
+
logger.warn(`"exists" and "notExists" operators are not supported in index conditions.`);
|
|
111
|
+
return null;
|
|
101
112
|
} else {
|
|
102
113
|
property = getEntityOwnProperty(model, (property) => {
|
|
103
114
|
return property.columnName === filterField;
|
|
@@ -113,14 +124,16 @@ export function replaceModelIndexConditionEntityPropertyWithTableColumn(model: R
|
|
|
113
124
|
|
|
114
125
|
if (property) {
|
|
115
126
|
if (isManyRelationProperty(property)) {
|
|
116
|
-
|
|
127
|
+
logger.warn(`Index condition on many-relation property "${property.code}" is not supported.`);
|
|
128
|
+
return null;
|
|
117
129
|
}
|
|
118
130
|
columnName = property.targetIdColumnName;
|
|
119
131
|
if (isPlainObject(filterValue)) {
|
|
120
132
|
filterValue = filterValue.id;
|
|
121
133
|
}
|
|
122
134
|
} else {
|
|
123
|
-
|
|
135
|
+
logger.warn(`Unknown field "${filterField}" in index conditions.`);
|
|
136
|
+
return null;
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
}
|
|
@@ -495,7 +495,8 @@ function generateTableIndexDDL(queryBuilder: IQueryBuilder, server: IRpdServer,
|
|
|
495
495
|
})} (${indexColumns.join(", ")})`;
|
|
496
496
|
|
|
497
497
|
if (index.conditions) {
|
|
498
|
-
const
|
|
498
|
+
const logger = server.getLogger();
|
|
499
|
+
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
499
500
|
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
500
501
|
}
|
|
501
502
|
|
package/src/types.ts
CHANGED
|
@@ -302,6 +302,11 @@ export interface RpdDataModelProperty {
|
|
|
302
302
|
* 当设置了 linkTableName 时,可以设置关联关系表所在的 Schema。
|
|
303
303
|
*/
|
|
304
304
|
linkSchema?: string;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 当设置为`true`时,仅允许在创建时设置此属性的值,不允许更新。
|
|
308
|
+
*/
|
|
309
|
+
readonly?: boolean;
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
export type RpdDataPropertyTypes =
|