@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.
@@ -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 targetRow = property.isBaseProperty ? baseRow : row;
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
- const targetEntity = fieldValue;
2976
- const newTargetEntity = await createEntity(server, targetDataAccessor, {
2977
- routeContext,
2978
- entity: targetEntity,
2979
- });
2980
- newEntityOneRelationProps[property.code] = newTargetEntity;
2981
- targetRow[property.targetIdColumnName] = newTargetEntity["id"];
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
- targetRow[property.targetIdColumnName] = targetEntityId;
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
- targetRow[property.targetIdColumnName] = targetEntityId;
3016
+ rowToBeSaved[property.targetIdColumnName] = targetEntityId;
3007
3017
  }
3008
3018
  else {
3009
3019
  newEntityOneRelationProps[property.code] = null;
3010
- targetRow[property.targetIdColumnName] = null;
3020
+ rowToBeSaved[property.targetIdColumnName] = null;
3011
3021
  }
3012
3022
  }
3013
3023
  let newBaseRow;
3024
+ let baseDataAccessor;
3014
3025
  if (model.base) {
3015
- const baseDataAccessor = server.getDataAccessor({
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 targetRow = property.isBaseProperty ? baseRow : row;
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
- const targetEntity = fieldValue;
3208
- const newTargetEntity = await createEntity(server, targetDataAccessor, {
3209
- routeContext,
3210
- entity: targetEntity,
3211
- });
3212
- updatedEntityOneRelationProps[property.code] = newTargetEntity;
3213
- targetRow[property.targetIdColumnName] = newTargetEntity["id"];
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
- targetRow[property.targetIdColumnName] = targetEntityId;
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
- targetRow[property.targetIdColumnName] = targetEntityId;
3297
+ rowToBeSaved[property.targetIdColumnName] = targetEntityId;
3239
3298
  }
3240
3299
  else {
3241
3300
  updatedEntityOneRelationProps[property.code] = null;
3242
- targetRow[property.targetIdColumnName] = null;
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
- if (model.base && Object.keys(baseRow).length) {
3251
- const baseDataAccessor = server.getDataAccessor({
3309
+ let baseDataAccessor;
3310
+ if (model.base) {
3311
+ baseDataAccessor = server.getDataAccessor({
3252
3312
  singularCode: model.base,
3253
3313
  });
3254
- updatedBaseRow = await baseDataAccessor.updateById(id, updatedBaseRow);
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
- replacedFilters.push(replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter));
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
- throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
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
- throw new Error(`"exists" and "notExists" operators are not supported in index conditions.`);
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
- throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
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
- throw new Error(`Unknown field "${filterField}" in index conditions.`);
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 rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(model, index.conditions);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ruiapp/rapid-core",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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 targetRow = property.isBaseProperty ? baseRow : row;
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
- const targetEntity = fieldValue;
808
- const newTargetEntity = await createEntity(server, targetDataAccessor, {
809
- routeContext,
810
- entity: targetEntity,
811
- });
812
- newEntityOneRelationProps[property.code] = newTargetEntity;
813
- targetRow[property.targetIdColumnName!] = newTargetEntity["id"];
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
- targetRow[property.targetIdColumnName!] = targetEntityId;
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
- targetRow[property.targetIdColumnName!] = targetEntityId;
842
+ rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
841
843
  } else {
842
844
  newEntityOneRelationProps[property.code] = null;
843
- targetRow[property.targetIdColumnName!] = null;
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
- const baseDataAccessor = server.getDataAccessor({
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 targetRow = property.isBaseProperty ? baseRow : row;
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
- const targetEntity = fieldValue;
1064
- const newTargetEntity = await createEntity(server, targetDataAccessor, {
1065
- routeContext,
1066
- entity: targetEntity,
1067
- });
1068
- updatedEntityOneRelationProps[property.code] = newTargetEntity;
1069
- targetRow[property.targetIdColumnName!] = newTargetEntity["id"];
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
- targetRow[property.targetIdColumnName!] = targetEntityId;
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
- targetRow[property.targetIdColumnName!] = targetEntityId;
1150
+ rowToBeSaved[property.targetIdColumnName!] = targetEntityId;
1097
1151
  } else {
1098
1152
  updatedEntityOneRelationProps[property.code] = null;
1099
- targetRow[property.targetIdColumnName!] = null;
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
- if (model.base && Object.keys(baseRow).length) {
1109
- const baseDataAccessor = server.getDataAccessor({
1162
+ let baseDataAccessor: any;
1163
+ if (model.base) {
1164
+ baseDataAccessor = server.getDataAccessor({
1110
1165
  singularCode: model.base,
1111
1166
  });
1112
- updatedBaseRow = await baseDataAccessor.updateById(id, updatedBaseRow);
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
- replacedFilters.push(replaceModelIndexConditionEntityPropertyWithTableColumn(model, filter));
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
- export function replaceModelIndexConditionEntityPropertyWithTableColumn(model: RpdDataModel, filter: RpdDataModelIndexOptions): RowFilterOptions {
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
- throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
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
- throw new Error(`"exists" and "notExists" operators are not supported in index conditions.`);
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
- throw new Error(`Condition on many-relation property "${property.code}" is not supported.`);
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
- throw new Error(`Unknown field "${filterField}" in index conditions.`);
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 rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(model, index.conditions);
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 =