This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 6a90ce3f1 FINERACT-1969 SQL Injection exception thrown even if no 
insecure string is in the input
6a90ce3f1 is described below

commit 6a90ce3f1020727b5116de7e2c4afb0b74555809
Author: jmarta <[email protected]>
AuthorDate: Mon Aug 28 13:31:29 2023 +0200

    FINERACT-1969 SQL Injection exception thrown even if no insecure string is 
in the input
---
 .../commands/service/CommandWrapperBuilder.java    |    6 +-
 .../infrastructure/core/service/DateUtils.java     |    9 +
 .../database/DatabaseSpecificSQLGenerator.java     |   15 +
 .../dataqueries/api/DatatablesApiResource.java     |   14 +-
 ...eleteOneToManyDatatableEntryCommandHandler.java |   15 +-
 ...DeleteOneToOneDatatableEntryCommandHandler.java |   15 +-
 ...ityDatatableChecksWritePlatformServiceImpl.java |    1 -
 .../service/ReadWriteNonCoreDataService.java       |    4 +-
 .../service/ReadWriteNonCoreDataServiceImpl.java   | 1546 ++++++++------------
 .../survey/api/SurveyApiResource.java              |    2 +-
 .../fineract/portfolio/search/SearchConstants.java |    3 +
 .../portfolio/search/service/SearchUtil.java       |    3 +-
 ...AccountTransactionDatatableIntegrationTest.java |   19 +-
 .../EntityDatatableChecksIntegrationTest.java      |    8 +-
 .../common/system/DatatableHelper.java             |    2 +-
 .../datatable/DatatableAdvancedQueryTest.java      |    4 +-
 .../datatable/DatatableIntegrationTest.java        |    8 +-
 17 files changed, 702 insertions(+), 972 deletions(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index 9c14256ff..08af2792f 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -718,19 +718,19 @@ public class CommandWrapperBuilder {
         return this;
     }
 
-    public CommandWrapperBuilder createDatatable(final String datatable, final 
Long apptableId, final Long datatableId) {
+    public CommandWrapperBuilder createDatatableEntry(final String datatable, 
final Long apptableId, final Long datatableId) {
         this.actionName = "CREATE";
         commonDatatableSettings(datatable, apptableId, datatableId);
         return this;
     }
 
-    public CommandWrapperBuilder updateDatatable(final String datatable, final 
Long apptableId, final Long datatableId) {
+    public CommandWrapperBuilder updateDatatableEntry(final String datatable, 
final Long apptableId, final Long datatableId) {
         this.actionName = "UPDATE";
         commonDatatableSettings(datatable, apptableId, datatableId);
         return this;
     }
 
-    public CommandWrapperBuilder deleteDatatable(final String datatable, final 
Long apptableId, final Long datatableId) {
+    public CommandWrapperBuilder deleteDatatableEntry(final String datatable, 
final Long apptableId, final Long datatableId) {
         this.actionName = "DELETE";
         commonDatatableSettings(datatable, apptableId, datatableId);
         return this;
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
index 159e54c17..1056bde38 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java
@@ -24,6 +24,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.time.temporal.ChronoUnit;
@@ -78,6 +79,14 @@ public final class DateUtils {
         return 
LocalDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.SECONDS);
     }
 
+    public static LocalDateTime getAuditLocalDateTime() {
+        return LocalDateTime.now(ZoneId.of("UTC"));
+    }
+
+    public static OffsetDateTime getAuditOffsetDateTime() {
+        return OffsetDateTime.now(ZoneOffset.UTC);
+    }
+
     public static boolean isDateInTheFuture(final LocalDate localDate) {
         return localDate.isAfter(getBusinessLocalDate());
     }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
index 115af018c..4c6a23429 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseSpecificSQLGenerator.java
@@ -248,4 +248,19 @@ public class DatabaseSpecificSQLGenerator {
         return orderBy + orders.stream().map(e -> String.join(" ", 
alias(escape(e.getProperty()), alias), e.getDirection().name()))
                 .collect(Collectors.joining(", "));
     }
+
+    public String buildInsert(@NotNull String definition, Collection<String> 
fields) {
+        if (fields == null || fields.isEmpty()) {
+            return "";
+        }
+        return "INSERT INTO " + escape(definition) + '(' + 
fields.stream().map(this::escape).collect(Collectors.joining(", "))
+                + ") VALUES (?" + ", ?".repeat(fields.size() - 1) + ')';
+    }
+
+    public String buildUpdate(@NotNull String definition, Collection<String> 
fields) {
+        if (fields == null || fields.isEmpty()) {
+            return "";
+        }
+        return "UPDATE " + escape(definition) + " SET " + 
fields.stream().map(e -> escape(e) + " = ?").collect(Collectors.joining(", "));
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
index f7c0919ff..38ddae0d4 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/api/DatatablesApiResource.java
@@ -314,7 +314,7 @@ public class DatatablesApiResource {
             @Parameter(hidden = true) final String apiRequestBodyAsJson) {
 
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .createDatatable(datatable, apptableId, null) //
+                .createDatatableEntry(datatable, apptableId, null) //
                 .withJson(apiRequestBodyAsJson) //
                 .build();
 
@@ -336,7 +336,7 @@ public class DatatablesApiResource {
             @Parameter(hidden = true) final String apiRequestBodyAsJson) {
 
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .updateDatatable(datatable, apptableId, null) //
+                .updateDatatableEntry(datatable, apptableId, null) //
                 .withJson(apiRequestBodyAsJson) //
                 .build();
 
@@ -359,7 +359,7 @@ public class DatatablesApiResource {
             @Parameter(hidden = true) final String apiRequestBodyAsJson) {
 
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .updateDatatable(datatable, apptableId, datatableId) //
+                .updateDatatableEntry(datatable, apptableId, datatableId) //
                 .withJson(apiRequestBodyAsJson) //
                 .build();
 
@@ -380,7 +380,7 @@ public class DatatablesApiResource {
             @PathParam("apptableId") @Parameter(description = "apptableId") 
final Long apptableId) {
 
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .deleteDatatable(datatable, apptableId, null) //
+                .deleteDatatableEntry(datatable, apptableId, null) //
                 .build();
 
         final CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
@@ -396,13 +396,11 @@ public class DatatablesApiResource {
             + "\n")
     @ApiResponses({
             @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
DatatablesApiResourceSwagger.DeleteDataTablesDatatableAppTableIdDatatableIdResponse.class)))
 })
-    public String deleteDatatableEntries(
-            @PathParam("datatable") @Parameter(description = "datatable", 
example = "{}") final String datatable,
+    public String deleteDatatableEntry(@PathParam("datatable") 
@Parameter(description = "datatable", example = "{}") final String datatable,
             @PathParam("apptableId") @Parameter(description = "apptableId") 
final Long apptableId,
             @PathParam("datatableId") @Parameter(description = "datatableId") 
final Long datatableId) {
-
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .deleteDatatable(datatable, apptableId, datatableId) //
+                .deleteDatatableEntry(datatable, apptableId, datatableId) //
                 .build();
 
         final CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
index 6c916fbc3..ee89b87aa 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToManyDatatableEntryCommandHandler.java
@@ -21,7 +21,6 @@ package 
org.apache.fineract.infrastructure.dataqueries.handler;
 import org.apache.fineract.commands.handler.NewCommandSourceHandler;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import 
org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -40,18 +39,6 @@ public class DeleteOneToManyDatatableEntryCommandHandler 
implements NewCommandSo
     @Transactional
     @Override
     public CommandProcessingResult processCommand(final JsonCommand command) {
-
-        final CommandProcessingResult commandProcessingResult = 
this.writePlatformService.deleteDatatableEntry(command.entityName(),
-                command.entityId(), command.subentityId());
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(command.entityId()) //
-                .withOfficeId(commandProcessingResult.getOfficeId()) //
-                .withGroupId(commandProcessingResult.getGroupId()) //
-                .withClientId(commandProcessingResult.getClientId()) //
-                .withSavingsId(commandProcessingResult.getSavingsId()) //
-                .withLoanId(commandProcessingResult.getLoanId()) //
-                .build();
+        return 
this.writePlatformService.deleteDatatableEntry(command.entityName(), 
command.entityId(), command.subentityId(), command);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
index 705dc4e16..ca67ea8fe 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/handler/DeleteOneToOneDatatableEntryCommandHandler.java
@@ -21,7 +21,6 @@ package 
org.apache.fineract.infrastructure.dataqueries.handler;
 import org.apache.fineract.commands.handler.NewCommandSourceHandler;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import 
org.apache.fineract.infrastructure.dataqueries.service.ReadWriteNonCoreDataService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -40,18 +39,6 @@ public class DeleteOneToOneDatatableEntryCommandHandler 
implements NewCommandSou
     @Transactional
     @Override
     public CommandProcessingResult processCommand(final JsonCommand command) {
-
-        final CommandProcessingResult commandProcessingResult = 
this.writePlatformService.deleteDatatableEntries(command.entityName(),
-                command.entityId());
-
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(command.entityId()) //
-                .withOfficeId(commandProcessingResult.getOfficeId()) //
-                .withGroupId(commandProcessingResult.getGroupId()) //
-                .withClientId(commandProcessingResult.getClientId()) //
-                .withSavingsId(commandProcessingResult.getSavingsId()) //
-                .withLoanId(commandProcessingResult.getLoanId()) //
-                .build();
+        return 
this.writePlatformService.deleteDatatableEntries(command.entityName(), 
command.entityId(), command);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksWritePlatformServiceImpl.java
index dc42431a4..e943d0677 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/EntityDatatableChecksWritePlatformServiceImpl.java
@@ -272,5 +272,4 @@ public class EntityDatatableChecksWritePlatformServiceImpl 
implements EntityData
         throw new 
PlatformDataIntegrityException("error.msg.report.unknown.data.integrity.issue",
                 "Unknown data integrity issue with resource: " + 
realCause.getMessage());
     }
-
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
index cef0a1f4f..685a9f013 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataService.java
@@ -68,9 +68,9 @@ public interface ReadWriteNonCoreDataService {
 
     CommandProcessingResult updateDatatableEntryOneToMany(String datatable, 
Long appTableId, Long datatableId, JsonCommand command);
 
-    CommandProcessingResult deleteDatatableEntries(String datatable, Long 
appTableId);
+    CommandProcessingResult deleteDatatableEntries(String datatable, Long 
appTableId, JsonCommand command);
 
-    CommandProcessingResult deleteDatatableEntry(String datatable, Long 
appTableId, Long datatableId);
+    CommandProcessingResult deleteDatatableEntry(String datatable, Long 
appTableId, Long datatableId, JsonCommand command);
 
     String getTableName(String Url);
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
index fb8184c32..a5ccb9331 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java
@@ -20,9 +20,10 @@ package 
org.apache.fineract.infrastructure.dataqueries.service;
 
 import static java.util.Arrays.asList;
 import static 
org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer.DATATABLE_NAME_REGEX_PATTERN;
-import static 
org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.BIT;
+import static 
org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.BIGINT;
 import static 
org.apache.fineract.infrastructure.core.service.database.JdbcJavaType.DATETIME;
 import static 
org.apache.fineract.infrastructure.core.service.database.SqlOperator.EQ;
+import static 
org.apache.fineract.infrastructure.core.service.database.SqlOperator.IN;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_AFTER;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_CODE;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_INDEXED;
@@ -42,8 +43,13 @@ import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiCon
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_DROPCOLUMNS;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_MULTIROW;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_PARAM_SUBTYPE;
+import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.CREATEDAT_FIELD_NAME;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_FIELD_ID;
 import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.TABLE_REGISTERED_TABLE;
+import static 
org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.UPDATEDAT_FIELD_NAME;
+import static 
org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_DATETIME_FORMAT;
+import static 
org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_DATE_FORMAT;
+import static 
org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_LOCALE;
 
 import com.google.common.base.Splitter;
 import com.google.gson.JsonArray;
@@ -54,18 +60,15 @@ import jakarta.persistence.PersistenceException;
 import jakarta.validation.constraints.NotNull;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
-import java.sql.SQLException;
-import java.sql.Statement;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Stream;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -77,11 +80,12 @@ import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
 import 
org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
-import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
 import 
org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
 import org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
@@ -115,8 +119,6 @@ import org.springframework.data.domain.Sort;
 import org.springframework.data.support.PageableExecutionUtils;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
-import org.springframework.jdbc.support.GeneratedKeyHolder;
-import org.springframework.jdbc.support.KeyHolder;
 import org.springframework.jdbc.support.rowset.SqlRowSet;
 import org.springframework.orm.jpa.JpaSystemException;
 import org.springframework.transaction.annotation.Transactional;
@@ -329,10 +331,6 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         return true;
     }
 
-    private void logAsErrorUnexpectedDataIntegrityException(final Exception 
dve) {
-        log.error("Error occurred.", dve);
-    }
-
     @Transactional
     @Override
     public void registerDatatable(final String dataTableName, final String 
entityName, final String entitySubType) {
@@ -390,9 +388,9 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             this.jdbcTemplate.update(permissionsSql);
 
             // add the registered table to the config if it is a ppi
-            if (this.isSurveyCategory(category)) {
+            if (category.equals(DataTableApiConstant.CATEGORY_PPI)) {
                 this.namedParameterJdbcTemplate
-                        .update("insert into c_configuration (name, value, 
enabled ) values( :dataTableName , '0',false)", paramMap);
+                        .update("insert into c_configuration (name, value, 
enabled ) values( :dataTableName, '0', false)", paramMap);
             }
 
         } catch (final JpaSystemException | DataIntegrityViolationException 
dve) {
@@ -443,28 +441,7 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
 
     private Integer getCategory(final JsonCommand command) {
         Integer category = 
command.integerValueOfParameterNamedDefaultToNullIfZero(DataTableApiConstant.categoryParamName);
-        if (category == null) {
-            category = DataTableApiConstant.CATEGORY_DEFAULT;
-        }
-        return category;
-    }
-
-    private boolean isSurveyCategory(final Integer category) {
-        return category.equals(DataTableApiConstant.CATEGORY_PPI);
-    }
-
-    private JsonElement addColumn(final String name, final JdbcJavaType 
dataType, final boolean isMandatory, final Integer length,
-            final boolean isUnique, final boolean isIndexed) {
-        JsonObject column = new JsonObject();
-        column.addProperty(API_FIELD_NAME, name);
-        column.addProperty(API_FIELD_TYPE, 
dataType.formatSql(databaseTypeResolver.databaseType()));
-        if (dataType.isStringType()) {
-            column.addProperty(API_FIELD_LENGTH, length);
-        }
-        column.addProperty(API_FIELD_MANDATORY, (isMandatory ? "true" : 
"false"));
-        column.addProperty(API_FIELD_UNIQUE, (isUnique ? "true" : "false"));
-        column.addProperty(API_FIELD_INDEXED, (isIndexed ? "true" : "false"));
-        return column;
+        return category == null ? DataTableApiConstant.CATEGORY_DEFAULT : 
category;
     }
 
     @Override
@@ -506,136 +483,6 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         this.jdbcTemplate.batchUpdate(sqlArray); // NOSONAR
     }
 
-    @Transactional
-    @Override
-    public CommandProcessingResult createNewDatatableEntry(final String 
dataTableName, final Long appTableId, final JsonCommand command) {
-        return createNewDatatableEntry(dataTableName, appTableId, 
command.json());
-    }
-
-    @Transactional
-    @Override
-    public CommandProcessingResult createNewDatatableEntry(final String 
dataTableName, final Long appTableId, final String json) {
-        try {
-            final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
-            CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
-
-            final List<ResultsetColumnHeaderData> columnHeaders = 
this.genericDataService.fillResultsetColumnHeaders(dataTableName);
-
-            final boolean multiRow = isMultirowDatatable(columnHeaders);
-
-            final Type typeOfMap = new TypeToken<Map<String, String>>() 
{}.getType();
-            final Map<String, String> dataParams = 
this.fromJsonHelper.extractDataMap(typeOfMap, json);
-
-            final String sql = getAddSql(columnHeaders, dataTableName, 
getFKField(entityTable), appTableId, dataParams);
-
-            if (!multiRow) {
-                this.jdbcTemplate.update(sql);
-                commandProcessingResult = 
CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, 
appTableId);
-            } else {
-                final Long resourceId = addMultirowRecord(sql);
-                commandProcessingResult = 
CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, 
resourceId);
-            }
-
-            return commandProcessingResult; //
-
-        } catch (final SQLException e) {
-            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
-                    "Unknown data integrity issue with resource.", e);
-        } catch (final DataAccessException dve) {
-            final Throwable cause = dve.getCause();
-            final Throwable realCause = dve.getMostSpecificCause();
-            if (realCause.getMessage().contains("Duplicate entry") || 
cause.getMessage().contains("Duplicate entry")) {
-                throw new PlatformDataIntegrityException(
-                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
-                                + "` and application table with identifier `" 
+ appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
-            } else if (realCause.getMessage().contains("doesn't have a default 
value")
-                    || cause.getMessage().contains("doesn't have a default 
value")) {
-                throw new PlatformDataIntegrityException(
-                        
"error.msg.datatable.no.value.provided.for.required.fields", "No values 
provided for the datatable `"
-                                + dataTableName + "` and application table 
with identifier `" + appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
-            }
-
-            logAsErrorUnexpectedDataIntegrityException(dve);
-            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
-                    "Unknown data integrity issue with resource.", dve);
-        } catch (final PersistenceException e) {
-            final Throwable cause = e.getCause();
-            if (cause.getMessage().contains("Duplicate entry")) {
-                throw new PlatformDataIntegrityException(
-                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
-                                + "` and application table with identifier `" 
+ appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
e);
-            } else if (cause.getMessage().contains("doesn't have a default 
value")) {
-                throw new PlatformDataIntegrityException(
-                        
"error.msg.datatable.no.value.provided.for.required.fields", "No values 
provided for the datatable `"
-                                + dataTableName + "` and application table 
with identifier `" + appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
e);
-            }
-
-            logAsErrorUnexpectedDataIntegrityException(e);
-            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
-                    "Unknown data integrity issue with resource.", e);
-        }
-    }
-
-    @Override
-    public CommandProcessingResult createPPIEntry(final String dataTableName, 
final Long appTableId, final JsonCommand command) {
-        try {
-            final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
-            final CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
-
-            final List<ResultsetColumnHeaderData> columnHeaders = 
this.genericDataService.fillResultsetColumnHeaders(dataTableName);
-
-            final Type typeOfMap = new TypeToken<Map<String, String>>() 
{}.getType();
-            final Map<String, String> dataParams = 
this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
-
-            final String sql = getAddSqlWithScore(columnHeaders, 
dataTableName, getFKField(entityTable), appTableId, dataParams);
-
-            this.jdbcTemplate.update(sql);
-
-            return commandProcessingResult; //
-
-        } catch (final DataAccessException dve) {
-            final Throwable cause = dve.getCause();
-            final Throwable realCause = dve.getMostSpecificCause();
-            if (realCause.getMessage().contains("Duplicate entry") || 
cause.getMessage().contains("Duplicate entry")) {
-                throw new PlatformDataIntegrityException(
-                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
-                                + "` and application table with identifier `" 
+ appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
-            }
-
-            logAsErrorUnexpectedDataIntegrityException(dve);
-            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
-                    "Unknown data integrity issue with resource.", dve);
-        } catch (final PersistenceException dve) {
-            final Throwable cause = dve.getCause();
-            if (cause.getMessage().contains("Duplicate entry")) {
-                throw new PlatformDataIntegrityException(
-                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
-                                + "` and application table with identifier `" 
+ appTableId + "`.",
-                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
-            }
-
-            logAsErrorUnexpectedDataIntegrityException(dve);
-            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
-                    "Unknown data integrity issue with resource.", dve);
-        }
-    }
-
-    private String datatableColumnNameToCodeValueName(final String columnName, 
final String code) {
-        return code + "_cd_" + columnName;
-    }
-
-    private void throwExceptionIfValidationWarningsExist(final 
List<ApiParameterError> dataValidationErrors) {
-        if (!dataValidationErrors.isEmpty()) {
-            throw new 
PlatformApiDataValidationException("validation.msg.validation.errors.exist", 
"Validation errors exist.",
-                    dataValidationErrors);
-        }
-    }
-
     private void parseDatatableColumnObjectForCreate(final JsonObject column, 
StringBuilder sqlBuilder,
             final StringBuilder constrainBuilder, final String 
dataTableNameAlias, final Map<String, Long> codeMappings,
             final boolean isConstraintApproach) {
@@ -722,8 +569,8 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             sqlBuilder.append(sqlGenerator.escape(fkColumnName)).append(" 
BIGINT NOT NULL, ");
 
             // Add Created At and Updated At
-            columns.add(addColumn(DataTableApiConstant.CREATEDAT_FIELD_NAME, 
DATETIME, false, null, false, false));
-            columns.add(addColumn(DataTableApiConstant.UPDATEDAT_FIELD_NAME, 
DATETIME, false, null, false, false));
+            columns.add(addColumn(CREATEDAT_FIELD_NAME, DATETIME, false, null, 
false, false));
+            columns.add(addColumn(UPDATEDAT_FIELD_NAME, DATETIME, false, null, 
false, false));
 
             final Map<String, Long> codeMappings = new HashMap<>();
             final StringBuilder constrainBuilder = new StringBuilder();
@@ -786,13 +633,26 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             } else if (realCause.getMessage().contains("Row") && 
realCause.getMessage().contains("large")) {
                 
baseDataValidator.reset().parameter("row").failWithCode("size.too.large");
             }
-
-            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+            baseDataValidator.throwValidationErrors();
         }
 
         return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withResourceIdAsString(datatableName).build();
     }
 
+    private JsonElement addColumn(final String name, final JdbcJavaType 
dataType, final boolean isMandatory, final Integer length,
+            final boolean isUnique, final boolean isIndexed) {
+        JsonObject column = new JsonObject();
+        column.addProperty(API_FIELD_NAME, name);
+        column.addProperty(API_FIELD_TYPE, 
dataType.formatSql(databaseTypeResolver.databaseType()));
+        if (dataType.isStringType()) {
+            column.addProperty(API_FIELD_LENGTH, length);
+        }
+        column.addProperty(API_FIELD_MANDATORY, Boolean.toString(isMandatory));
+        column.addProperty(API_FIELD_UNIQUE, Boolean.toString(isUnique));
+        column.addProperty(API_FIELD_INDEXED, Boolean.toString(isIndexed));
+        return column;
+    }
+
     private void createFkIndex(String datatableName, String fkColumnName) {
         String indexName = 
datatableKeywordGenerator.generateIndexName(datatableName, fkColumnName);
         createIndex(indexName, datatableName, fkColumnName);
@@ -814,320 +674,74 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         }
     }
 
-    private long addMultirowRecord(String sql) throws SQLException {
-        KeyHolder keyHolder = new GeneratedKeyHolder();
-        int insertsCount = this.jdbcTemplate.update(c -> 
c.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS), keyHolder);
-        if (insertsCount == 1) {
-            Number assignedKey;
-            if (keyHolder.getKeys().size() > 1) {
-                assignedKey = (Long) keyHolder.getKeys().get(TABLE_FIELD_ID);
-            } else {
-                assignedKey = keyHolder.getKey();
-            }
-            if (assignedKey == null) {
-                throw new SQLException("Row id getting error.");
-            }
-            return assignedKey.longValue();
-        }
-        throw new SQLException("Expected one inserted row.");
-    }
+    @Transactional
+    @Override
+    public void updateDatatable(final String datatableName, final JsonCommand 
command) {
+        try {
+            this.context.authenticatedUser();
+            this.fromApiJsonDeserializer.validateForUpdate(command.json());
 
-    private void parseDatatableColumnForUpdate(final JsonObject column,
-            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition, final String datatableName, StringBuilder 
renameBuilder,
-            StringBuilder changeBuilder, final StringBuilder constrainBuilder, 
final Map<String, Long> codeMappings,
-            final List<String> removeMappings, final boolean 
isConstraintApproach) {
-        String oldName = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
-        if (!mapColumnNameDefinition.containsKey(oldName)) {
-            throw new 
PlatformDataIntegrityException("error.msg.datatable.column.missing.update.parse",
-                    "Column " + oldName + " does not exist.", oldName);
-        }
-        final String lengthStr = column.has(API_FIELD_LENGTH) ? 
column.get(API_FIELD_LENGTH).getAsString() : null;
-        Long length = StringUtils.isNotBlank(lengthStr) ? 
Long.parseLong(lengthStr) : null;
-        String newName = column.has(API_FIELD_NEWNAME) ? 
column.get(API_FIELD_NEWNAME).getAsString() : null;
-        final Boolean newMandatory = column.has(API_FIELD_MANDATORY) ? 
column.get(API_FIELD_MANDATORY).getAsBoolean() : null;
-        final String after = column.has(API_FIELD_AFTER) ? 
column.get(API_FIELD_AFTER).getAsString() : null;
-        final String code = column.has(API_FIELD_CODE) ? 
column.get(API_FIELD_CODE).getAsString() : null;
-        final String newCode = column.has(API_FIELD_NEWCODE) ? 
column.get(API_FIELD_NEWCODE).getAsString() : null;
-        final String dataTableNameAlias = 
datatableName.toLowerCase().replaceAll("\\s", "_");
-        if (isConstraintApproach) {
-            if (StringUtils.isBlank(newName)) {
-                newName = oldName;
+            final JsonElement element = 
this.fromJsonHelper.parse(command.json());
+            final JsonArray changeColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_CHANGECOLUMNS, element);
+            final JsonArray addColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_ADDCOLUMNS, element);
+            final JsonArray dropColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_DROPCOLUMNS, element);
+            final String entityName = 
this.fromJsonHelper.extractStringNamed(API_PARAM_APPTABLE_NAME, element);
+            final String entitySubType = 
this.fromJsonHelper.extractStringNamed(API_PARAM_SUBTYPE, element);
+
+            validateDatatableName(datatableName);
+            int rowCount = getDatatableRowCount(datatableName);
+            final List<ResultsetColumnHeaderData> columnHeaderData = 
this.genericDataService.fillResultsetColumnHeaders(datatableName);
+            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition = SearchUtil.mapHeadersToName(columnHeaderData);
+
+            final boolean isConstraintApproach = 
this.configurationDomainService.isConstraintApproachEnabledForDatatables();
+
+            if (!StringUtils.isBlank(entitySubType)) {
+                jdbcTemplate.update("update x_registered_table SET 
entity_subtype=? WHERE registered_table_name = ?", // NOSONAR
+                        new Object[] { entitySubType, datatableName });
             }
-            String fkName = "fk_" + dataTableNameAlias + "_" + oldName;
-            String newFkName = "fk_" + dataTableNameAlias + "_" + newName;
-            if (!StringUtils.equalsIgnoreCase(code, newCode) || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
-                if (StringUtils.equalsIgnoreCase(code, newCode)) {
-                    final int codeId = getCodeIdForColumn(dataTableNameAlias, 
oldName);
-                    if (codeId > 0) {
-                        removeMappings.add(dataTableNameAlias + "_" + oldName);
-                        constrainBuilder.append(", DROP CONSTRAINT 
").append(sqlGenerator.escape(fkName)).append(" ");
-                        codeMappings.put(dataTableNameAlias + "_" + newName, 
(long) codeId);
-                        constrainBuilder.append(", ADD CONSTRAINT 
").append(sqlGenerator.escape(newFkName)).append(" ")
-                                .append("FOREIGN KEY 
(").append(sqlGenerator.escape(newName)).append(") ").append("REFERENCES ")
-                                
.append(sqlGenerator.escape(CODE_VALUES_TABLE)).append(" 
(").append(TABLE_FIELD_ID).append(")");
-                    }
 
-                } else {
-                    if (code != null) {
-                        removeMappings.add(dataTableNameAlias + "_" + oldName);
-                        if (newCode == null || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
-                            constrainBuilder.append(", DROP CONSTRAINT 
").append(sqlGenerator.escape(fkName)).append(" ");
-                        }
-                    }
-                    if (newCode != null) {
-                        codeMappings.put(dataTableNameAlias + "_" + newName, 
this.codeReadPlatformService.retriveCode(newCode).getId());
-                        if (code == null || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
-                            constrainBuilder.append(", ADD CONSTRAINT  
").append(sqlGenerator.escape(newFkName)).append(" ")
-                                    .append("FOREIGN KEY 
(").append(sqlGenerator.escape(newName)).append(") ").append("REFERENCES ")
-                                    
.append(sqlGenerator.escape(CODE_VALUES_TABLE)).append(" 
(").append(TABLE_FIELD_ID).append(")");
-                        }
+            if (!StringUtils.isBlank(entityName)) {
+                EntityTables entityTable = resolveEntity(entityName);
+                EntityTables oldEntityTable = 
queryForApplicationEntity(datatableName);
+                if (entityTable != oldEntityTable) {
+                    final String oldFKName = getFKField(oldEntityTable);
+                    final String newFKName = getFKField(entityTable);
+                    final String oldConstraintName = 
datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + oldFKName;
+                    final String newConstraintName = 
datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + newFKName;
+                    StringBuilder sqlBuilder = new StringBuilder();
+
+                    String fullOldFk = "fk_" + oldFKName;
+                    String fullOldConstraint = "fk_" + oldConstraintName;
+                    String fullNewFk = "fk_" + newFKName;
+                    String fullNewConstraint = "fk_" + newConstraintName;
+                    if (mapColumnNameDefinition.containsKey(TABLE_FIELD_ID)) {
+                        sqlBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" DROP KEY ")
+                                
.append(sqlGenerator.escape(fullOldFk)).append(",").append("DROP FOREIGN KEY ")
+                                
.append(sqlGenerator.escape(fullOldConstraint)).append(",").append("CHANGE 
COLUMN ")
+                                
.append(sqlGenerator.escape(oldFKName)).append(" 
").append(sqlGenerator.escape(newFKName))
+                                .append(" BIGINT NOT NULL,").append("ADD KEY 
").append(sqlGenerator.escape(fullNewFk)).append(" (")
+                                
.append(sqlGenerator.escape(newFKName)).append("),").append("ADD CONSTRAINT ")
+                                
.append(sqlGenerator.escape(fullNewConstraint)).append(" FOREIGN KEY (")
+                                
.append(sqlGenerator.escape(newFKName)).append(") ").append("REFERENCES ")
+                                
.append(sqlGenerator.escape(entityTable.getApptableName())).append(" 
(").append(TABLE_FIELD_ID).append(")");
+                    } else {
+                        sqlBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" DROP FOREIGN KEY ")
+                                
.append(sqlGenerator.escape(fullOldConstraint)).append(",").append("CHANGE 
COLUMN ")
+                                
.append(sqlGenerator.escape(oldFKName)).append(" 
").append(sqlGenerator.escape(newFKName))
+                                .append(" BIGINT NOT NULL,").append("ADD 
CONSTRAINT ").append(sqlGenerator.escape(fullNewConstraint))
+                                .append(" FOREIGN KEY 
(").append(sqlGenerator.escape(newFKName)).append(") ").append("REFERENCES ")
+                                
.append(sqlGenerator.escape(entityTable.getApptableName())).append(" 
(").append(TABLE_FIELD_ID).append(")");
                     }
+
+                    this.jdbcTemplate.execute(sqlBuilder.toString());
+
+                    deregisterDatatable(datatableName);
+                    registerDatatable(datatableName, entityName, 
entitySubType);
                 }
             }
-        } else {
-            if (StringUtils.isNotBlank(code)) {
-                oldName = datatableColumnNameToCodeValueName(oldName, code);
-                if (StringUtils.isNotBlank(newCode)) {
-                    newName = datatableColumnNameToCodeValueName(newName, 
newCode);
-                } else {
-                    newName = datatableColumnNameToCodeValueName(newName, 
code);
-                }
-            }
-        }
-        DatabaseType dialect = databaseTypeResolver.databaseType();
-        ResultsetColumnHeaderData columnHeader = 
mapColumnNameDefinition.get(oldName);
-        final JdbcJavaType type = columnHeader.getColumnType();
-        boolean nameChanged = !StringUtils.isBlank(newName) && 
!newName.equals(oldName);
-        boolean lengthChanged = length != null && 
!length.equals(columnHeader.getColumnLength()) && type.hasPrecision(dialect);
-        boolean nullityChanged = newMandatory != null && newMandatory != 
columnHeader.isMandatory();
-        boolean afterChanged = after != null && databaseTypeResolver.isMySQL();
-        if (nameChanged || lengthChanged || nullityChanged || afterChanged) {
-            Integer precision = length == null ? null : length.intValue();
-            Integer scale = null;
-            if (type.isDecimalType()) {
-                precision = 19;
-                scale = 6;
-            }
-            String colName = StringUtils.isBlank(newName) ? oldName : newName;
-            boolean mandatory = newMandatory == null ? 
columnHeader.isMandatory() : newMandatory;
-            if (databaseTypeResolver.isMySQL()) {
-                String modifySql = nameChanged ? ("CHANGE " + 
sqlGenerator.escape(oldName) + " " + sqlGenerator.escape(colName))
-                        : (" MODIFY " + sqlGenerator.escape(colName));
-                changeBuilder.append(", ").append(modifySql).append(" 
").append(type.formatSql(dialect, precision, scale))
-                        .append(mandatory ? " NOT NULL" : " DEFAULT NULL");
-                if (after != null) {
-                    changeBuilder.append(" AFTER 
").append(sqlGenerator.escape(after));
-                }
-            } else {
-                if (nameChanged) {
-                    renameBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" RENAME COLUMN ")
-                            .append(sqlGenerator.escape(oldName)).append(" TO 
").append(sqlGenerator.escape(newName)).append("; ");
-                }
-                if (lengthChanged) {
-                    changeBuilder.append(", ALTER 
").append(sqlGenerator.escape(colName)).append(" type ")
-                            .append(type.formatSql(dialect, precision, scale));
-                }
-                if (nullityChanged) {
-                    changeBuilder.append(", ALTER 
").append(sqlGenerator.escape(colName))
-                            .append(mandatory ? " set not null" : " drop not 
null");
-                }
-            }
-        }
-    }
-
-    private int getCodeIdForColumn(final String dataTableNameAlias, final 
String name) {
-        final StringBuilder checkColumnCodeMapping = new StringBuilder();
-        checkColumnCodeMapping.append("select ccm.code_id from 
x_table_column_code_mappings ccm where ccm.column_alias_name='")
-                
.append(dataTableNameAlias).append("_").append(name).append("'");
-        Integer codeId = 0;
-        try {
-            codeId = 
this.jdbcTemplate.queryForObject(checkColumnCodeMapping.toString(), 
Integer.class);
-        } catch (final EmptyResultDataAccessException e) {
-            log.warn("Error occurred.", e);
-        }
-        return ObjectUtils.defaultIfNull(codeId, 0);
-    }
-
-    private void parseDatatableColumnForAdd(final JsonObject column, 
StringBuilder sqlBuilder, final String dataTableNameAlias,
-            final StringBuilder constrainBuilder, final Map<String, Long> 
codeMappings, final boolean isConstraintApproach) {
-
-        String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
-        final String type = column.has(API_FIELD_TYPE) ? 
column.get(API_FIELD_TYPE).getAsString().toLowerCase() : null;
-        final Integer length = column.has(API_FIELD_LENGTH) ? 
column.get(API_FIELD_LENGTH).getAsInt() : null;
-        final boolean mandatory = column.has(API_FIELD_MANDATORY) && 
column.get(API_FIELD_MANDATORY).getAsBoolean();
-        final boolean unique = column.has(API_FIELD_UNIQUE) && 
column.get(API_FIELD_UNIQUE).getAsBoolean();
-        final String after = column.has(API_FIELD_AFTER) ? 
column.get(API_FIELD_AFTER).getAsString() : null;
-        final String code = column.has(API_FIELD_CODE) ? 
column.get(API_FIELD_CODE).getAsString() : null;
-
-        if (StringUtils.isNotBlank(code)) {
-            if (isConstraintApproach) {
-                String fkName = "fk_" + dataTableNameAlias + "_" + name;
-                codeMappings.put(dataTableNameAlias + "_" + name, 
this.codeReadPlatformService.retriveCode(code).getId());
-                constrainBuilder.append(",ADD CONSTRAINT  
").append(sqlGenerator.escape(fkName)).append(" ").append("FOREIGN KEY (")
-                        .append(sqlGenerator.escape(name)).append(") 
").append("REFERENCES ").append(sqlGenerator.escape(CODE_VALUES_TABLE))
-                        .append(" (").append(TABLE_FIELD_ID).append(")");
-            } else {
-                name = datatableColumnNameToCodeValueName(name, code);
-            }
-        }
-        sqlBuilder.append(", ADD ").append(sqlGenerator.escape(name)).append(" 
").append(mapApiTypeToDbType(type, length));
-
-        if (unique) {
-            String uniqueKeyName = 
datatableKeywordGenerator.generateUniqueKeyName(dataTableNameAlias, name);
-            constrainBuilder.append(",ADD CONSTRAINT  
").append(sqlGenerator.escape(uniqueKeyName)).append(" ").append("UNIQUE (")
-                    .append(sqlGenerator.escape(name)).append(")");
-        }
-
-        if (mandatory) {
-            sqlBuilder.append(" NOT NULL");
-        } else {
-            sqlBuilder.append(" DEFAULT NULL");
-        }
-
-        if (after != null) {
-            sqlBuilder.append(" AFTER ").append(sqlGenerator.escape(after));
-        }
-    }
-
-    private void parseDatatableColumnForDrop(final JsonObject column, 
StringBuilder sqlBuilder, final String datatableName,
-            final StringBuilder constrainBuilder, final List<String> 
codeMappings) {
-        final String datatableAlias = 
datatableName.toLowerCase().replaceAll("\\s", "_");
-        final String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
-        if (name == null) {
-            throw new 
GeneralPlatformDomainRuleException("error.msg.missing.datatable.column.name",
-                    "Datatable column name to drop is missing.");
-        }
-        sqlBuilder.append(", DROP COLUMN 
").append(sqlGenerator.escape(name)).append(" ");
-
-        String fkName = "fk_" + datatableAlias + "_" + name;
-        String schemaSql = databaseTypeResolver.isMySQL() ? "i.TABLE_SCHEMA = 
SCHEMA()"
-                : "i.table_catalog = current_catalog AND i.table_schema = 
current_schema";
-        String findFKSql = "SELECT count(*) FROM 
information_schema.TABLE_CONSTRAINTS i" + " WHERE i.CONSTRAINT_TYPE = 'FOREIGN 
KEY' AND "
-                + schemaSql + " AND i.TABLE_NAME = '" + datatableName + "' AND 
i.CONSTRAINT_NAME = '" + fkName + "' ";
-
-        final int count = this.jdbcTemplate.queryForObject(findFKSql, 
Integer.class);
-        if (count > 0) {
-            codeMappings.add(datatableAlias + "_" + name);
-            constrainBuilder.append(", DROP FOREIGN KEY 
").append(sqlGenerator.escape(fkName)).append(" ");
-        }
-    }
-
-    private void registerColumnCodeMapping(final Map<String, Long> 
codeMappings) {
-        if (codeMappings != null && !codeMappings.isEmpty()) {
-            final String[] addSqlList = new String[codeMappings.size()];
-            int i = 0;
-            for (final Map.Entry<String, Long> mapEntry : 
codeMappings.entrySet()) {
-                addSqlList[i++] = "insert into x_table_column_code_mappings 
(column_alias_name, code_id) values ('" + mapEntry.getKey()
-                        + "'," + mapEntry.getValue() + ");";
-            }
-
-            this.jdbcTemplate.batchUpdate(addSqlList);
-        }
-    }
-
-    private void deleteColumnCodeMapping(final List<String> columnNames) {
-        if (columnNames != null && !columnNames.isEmpty()) {
-            final String[] deleteSqlList = new String[columnNames.size()];
-            int i = 0;
-            for (final String columnName : columnNames) {
-                deleteSqlList[i++] = "DELETE FROM x_table_column_code_mappings 
WHERE  column_alias_name='" + columnName + "';";
-            }
-
-            this.jdbcTemplate.batchUpdate(deleteSqlList);
-        }
-    }
-
-    /**
-     * Update data table, set column value to empty string where current value 
is NULL. Run update SQL only if the
-     * "mandatory" property is set to true
-     *
-     * @param datatableName
-     *            Name of data table
-     * @param column
-     *            JSON encoded array of column properties
-     * @see <a 
href="https://mifosforge.jira.com/browse/MIFOSX-1145";>MIFOSX-1145</a>
-     **/
-    private void removeNullValuesFromStringColumn(final String datatableName, 
final JsonObject column,
-            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition) {
-        final boolean mandatory = column.has(API_FIELD_MANDATORY) && 
column.get(API_FIELD_MANDATORY).getAsBoolean();
-        final String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : "";
-        final JdbcJavaType type = mapColumnNameDefinition.containsKey(name) ? 
mapColumnNameDefinition.get(name).getColumnType() : null;
-
-        if (type != null && mandatory && type.isStringType()) {
-            String sql = "UPDATE " + sqlGenerator.escape(datatableName) + " 
SET " + sqlGenerator.escape(name) + " = '' WHERE "
-                    + sqlGenerator.escape(name) + " IS NULL";
-            this.jdbcTemplate.update(sql);
-        }
-    }
-
-    @Transactional
-    @Override
-    public void updateDatatable(final String datatableName, final JsonCommand 
command) {
-        try {
-            this.context.authenticatedUser();
-            this.fromApiJsonDeserializer.validateForUpdate(command.json());
-
-            final JsonElement element = 
this.fromJsonHelper.parse(command.json());
-            final JsonArray changeColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_CHANGECOLUMNS, element);
-            final JsonArray addColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_ADDCOLUMNS, element);
-            final JsonArray dropColumns = 
this.fromJsonHelper.extractJsonArrayNamed(API_PARAM_DROPCOLUMNS, element);
-            final String entityName = 
this.fromJsonHelper.extractStringNamed(API_PARAM_APPTABLE_NAME, element);
-            final String entitySubType = 
this.fromJsonHelper.extractStringNamed(API_PARAM_SUBTYPE, element);
-
-            validateDatatableName(datatableName);
-            int rowCount = getRowCount(datatableName);
-            final List<ResultsetColumnHeaderData> columnHeaderData = 
this.genericDataService.fillResultsetColumnHeaders(datatableName);
-            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition = SearchUtil.mapHeadersToName(columnHeaderData);
-
-            final boolean isConstraintApproach = 
this.configurationDomainService.isConstraintApproachEnabledForDatatables();
-
-            if (!StringUtils.isBlank(entitySubType)) {
-                jdbcTemplate.update("update x_registered_table SET 
entity_subtype=? WHERE registered_table_name = ?", // NOSONAR
-                        new Object[] { entitySubType, datatableName });
-            }
-
-            if (!StringUtils.isBlank(entityName)) {
-                EntityTables entityTable = resolveEntity(entityName);
-                EntityTables oldEntityTable = 
queryForApplicationEntity(datatableName);
-                if (entityTable != oldEntityTable) {
-                    final String oldFKName = getFKField(oldEntityTable);
-                    final String newFKName = getFKField(entityTable);
-                    final String oldConstraintName = 
datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + oldFKName;
-                    final String newConstraintName = 
datatableName.toLowerCase().replaceAll("\\s", "_") + "_" + newFKName;
-                    StringBuilder sqlBuilder = new StringBuilder();
-
-                    String fullOldFk = "fk_" + oldFKName;
-                    String fullOldConstraint = "fk_" + oldConstraintName;
-                    String fullNewFk = "fk_" + newFKName;
-                    String fullNewConstraint = "fk_" + newConstraintName;
-                    if (mapColumnNameDefinition.containsKey(TABLE_FIELD_ID)) {
-                        sqlBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" DROP KEY ")
-                                
.append(sqlGenerator.escape(fullOldFk)).append(",").append("DROP FOREIGN KEY ")
-                                
.append(sqlGenerator.escape(fullOldConstraint)).append(",").append("CHANGE 
COLUMN ")
-                                
.append(sqlGenerator.escape(oldFKName)).append(" 
").append(sqlGenerator.escape(newFKName))
-                                .append(" BIGINT NOT NULL,").append("ADD KEY 
").append(sqlGenerator.escape(fullNewFk)).append(" (")
-                                
.append(sqlGenerator.escape(newFKName)).append("),").append("ADD CONSTRAINT ")
-                                
.append(sqlGenerator.escape(fullNewConstraint)).append(" FOREIGN KEY (")
-                                
.append(sqlGenerator.escape(newFKName)).append(") ").append("REFERENCES ")
-                                
.append(sqlGenerator.escape(entityTable.getApptableName())).append(" 
(").append(TABLE_FIELD_ID).append(")");
-                    } else {
-                        sqlBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" DROP FOREIGN KEY ")
-                                
.append(sqlGenerator.escape(fullOldConstraint)).append(",").append("CHANGE 
COLUMN ")
-                                
.append(sqlGenerator.escape(oldFKName)).append(" 
").append(sqlGenerator.escape(newFKName))
-                                .append(" BIGINT NOT NULL,").append("ADD 
CONSTRAINT ").append(sqlGenerator.escape(fullNewConstraint))
-                                .append(" FOREIGN KEY 
(").append(sqlGenerator.escape(newFKName)).append(") ").append("REFERENCES ")
-                                
.append(sqlGenerator.escape(entityTable.getApptableName())).append(" 
(").append(TABLE_FIELD_ID).append(")");
-                    }
-
-                    this.jdbcTemplate.execute(sqlBuilder.toString());
-
-                    deregisterDatatable(datatableName);
-                    registerDatatable(datatableName, entityName, 
entitySubType);
-                }
-            }
-
-            if (changeColumns == null && addColumns == null && dropColumns == 
null) {
-                return;
+
+            if (changeColumns == null && addColumns == null && dropColumns == 
null) {
+                return;
             }
 
             if (dropColumns != null) {
@@ -1240,8 +854,7 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             } else if 
(realCause.getMessage().toLowerCase().contains("duplicate column")) {
                 
baseDataValidator.reset().parameter(API_FIELD_NAME).failWithCode("column.already.exists");
             }
-
-            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+            baseDataValidator.throwValidationErrors();
         } catch (final PersistenceException ee) {
             Throwable realCause = ExceptionUtils.getRootCause(ee.getCause());
             final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
@@ -1256,8 +869,235 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             } else if (realCause.getMessage().contains("Row") && 
realCause.getMessage().contains("large")) {
                 
baseDataValidator.reset().parameter("row").failWithCode("size.too.large");
             }
+            baseDataValidator.throwValidationErrors();
+        }
+    }
 
-            throwExceptionIfValidationWarningsExist(dataValidationErrors);
+    private void parseDatatableColumnForAdd(final JsonObject column, 
StringBuilder sqlBuilder, final String dataTableNameAlias,
+            final StringBuilder constrainBuilder, final Map<String, Long> 
codeMappings, final boolean isConstraintApproach) {
+
+        String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
+        final String type = column.has(API_FIELD_TYPE) ? 
column.get(API_FIELD_TYPE).getAsString().toLowerCase() : null;
+        final Integer length = column.has(API_FIELD_LENGTH) ? 
column.get(API_FIELD_LENGTH).getAsInt() : null;
+        final boolean mandatory = column.has(API_FIELD_MANDATORY) && 
column.get(API_FIELD_MANDATORY).getAsBoolean();
+        final boolean unique = column.has(API_FIELD_UNIQUE) && 
column.get(API_FIELD_UNIQUE).getAsBoolean();
+        final String after = column.has(API_FIELD_AFTER) ? 
column.get(API_FIELD_AFTER).getAsString() : null;
+        final String code = column.has(API_FIELD_CODE) ? 
column.get(API_FIELD_CODE).getAsString() : null;
+
+        if (StringUtils.isNotBlank(code)) {
+            if (isConstraintApproach) {
+                String fkName = "fk_" + dataTableNameAlias + "_" + name;
+                codeMappings.put(dataTableNameAlias + "_" + name, 
this.codeReadPlatformService.retriveCode(code).getId());
+                constrainBuilder.append(",ADD CONSTRAINT  
").append(sqlGenerator.escape(fkName)).append(" ").append("FOREIGN KEY (")
+                        .append(sqlGenerator.escape(name)).append(") 
").append("REFERENCES ").append(sqlGenerator.escape(CODE_VALUES_TABLE))
+                        .append(" (").append(TABLE_FIELD_ID).append(")");
+            } else {
+                name = datatableColumnNameToCodeValueName(name, code);
+            }
+        }
+        sqlBuilder.append(", ADD ").append(sqlGenerator.escape(name)).append(" 
").append(mapApiTypeToDbType(type, length));
+
+        if (unique) {
+            String uniqueKeyName = 
datatableKeywordGenerator.generateUniqueKeyName(dataTableNameAlias, name);
+            constrainBuilder.append(",ADD CONSTRAINT  
").append(sqlGenerator.escape(uniqueKeyName)).append(" ").append("UNIQUE (")
+                    .append(sqlGenerator.escape(name)).append(")");
+        }
+
+        if (mandatory) {
+            sqlBuilder.append(" NOT NULL");
+        } else {
+            sqlBuilder.append(" DEFAULT NULL");
+        }
+
+        if (after != null) {
+            sqlBuilder.append(" AFTER ").append(sqlGenerator.escape(after));
+        }
+    }
+
+    private void parseDatatableColumnForUpdate(final JsonObject column,
+            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition, final String datatableName, StringBuilder 
renameBuilder,
+            StringBuilder changeBuilder, final StringBuilder constrainBuilder, 
final Map<String, Long> codeMappings,
+            final List<String> removeMappings, final boolean 
isConstraintApproach) {
+        String oldName = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
+        if (!mapColumnNameDefinition.containsKey(oldName)) {
+            throw new 
PlatformDataIntegrityException("error.msg.datatable.column.missing.update.parse",
+                    "Column " + oldName + " does not exist.", oldName);
+        }
+        final String lengthStr = column.has(API_FIELD_LENGTH) ? 
column.get(API_FIELD_LENGTH).getAsString() : null;
+        Long length = StringUtils.isNotBlank(lengthStr) ? 
Long.parseLong(lengthStr) : null;
+        String newName = column.has(API_FIELD_NEWNAME) ? 
column.get(API_FIELD_NEWNAME).getAsString() : null;
+        final Boolean newMandatory = column.has(API_FIELD_MANDATORY) ? 
column.get(API_FIELD_MANDATORY).getAsBoolean() : null;
+        final String after = column.has(API_FIELD_AFTER) ? 
column.get(API_FIELD_AFTER).getAsString() : null;
+        final String code = column.has(API_FIELD_CODE) ? 
column.get(API_FIELD_CODE).getAsString() : null;
+        final String newCode = column.has(API_FIELD_NEWCODE) ? 
column.get(API_FIELD_NEWCODE).getAsString() : null;
+        final String dataTableNameAlias = 
datatableName.toLowerCase().replaceAll("\\s", "_");
+        if (isConstraintApproach) {
+            if (StringUtils.isBlank(newName)) {
+                newName = oldName;
+            }
+            String fkName = "fk_" + dataTableNameAlias + "_" + oldName;
+            String newFkName = "fk_" + dataTableNameAlias + "_" + newName;
+            if (!StringUtils.equalsIgnoreCase(code, newCode) || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
+                if (StringUtils.equalsIgnoreCase(code, newCode)) {
+                    final int codeId = getCodeIdForColumn(dataTableNameAlias, 
oldName);
+                    if (codeId > 0) {
+                        removeMappings.add(dataTableNameAlias + "_" + oldName);
+                        constrainBuilder.append(", DROP CONSTRAINT 
").append(sqlGenerator.escape(fkName)).append(" ");
+                        codeMappings.put(dataTableNameAlias + "_" + newName, 
(long) codeId);
+                        constrainBuilder.append(", ADD CONSTRAINT 
").append(sqlGenerator.escape(newFkName)).append(" ")
+                                .append("FOREIGN KEY 
(").append(sqlGenerator.escape(newName)).append(") ").append("REFERENCES ")
+                                
.append(sqlGenerator.escape(CODE_VALUES_TABLE)).append(" 
(").append(TABLE_FIELD_ID).append(")");
+                    }
+
+                } else {
+                    if (code != null) {
+                        removeMappings.add(dataTableNameAlias + "_" + oldName);
+                        if (newCode == null || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
+                            constrainBuilder.append(", DROP CONSTRAINT 
").append(sqlGenerator.escape(fkName)).append(" ");
+                        }
+                    }
+                    if (newCode != null) {
+                        codeMappings.put(dataTableNameAlias + "_" + newName, 
this.codeReadPlatformService.retriveCode(newCode).getId());
+                        if (code == null || 
!StringUtils.equalsIgnoreCase(oldName, newName)) {
+                            constrainBuilder.append(", ADD CONSTRAINT  
").append(sqlGenerator.escape(newFkName)).append(" ")
+                                    .append("FOREIGN KEY 
(").append(sqlGenerator.escape(newName)).append(") ").append("REFERENCES ")
+                                    
.append(sqlGenerator.escape(CODE_VALUES_TABLE)).append(" 
(").append(TABLE_FIELD_ID).append(")");
+                        }
+                    }
+                }
+            }
+        } else {
+            if (StringUtils.isNotBlank(code)) {
+                oldName = datatableColumnNameToCodeValueName(oldName, code);
+                if (StringUtils.isNotBlank(newCode)) {
+                    newName = datatableColumnNameToCodeValueName(newName, 
newCode);
+                } else {
+                    newName = datatableColumnNameToCodeValueName(newName, 
code);
+                }
+            }
+        }
+        DatabaseType dialect = databaseTypeResolver.databaseType();
+        ResultsetColumnHeaderData columnHeader = 
mapColumnNameDefinition.get(oldName);
+        final JdbcJavaType type = columnHeader.getColumnType();
+        boolean nameChanged = !StringUtils.isBlank(newName) && 
!newName.equals(oldName);
+        boolean lengthChanged = length != null && 
!length.equals(columnHeader.getColumnLength()) && type.hasPrecision(dialect);
+        boolean nullityChanged = newMandatory != null && newMandatory != 
columnHeader.isMandatory();
+        boolean afterChanged = after != null && databaseTypeResolver.isMySQL();
+        if (nameChanged || lengthChanged || nullityChanged || afterChanged) {
+            Integer precision = length == null ? null : length.intValue();
+            Integer scale = null;
+            if (type.isDecimalType()) {
+                precision = 19;
+                scale = 6;
+            }
+            String colName = StringUtils.isBlank(newName) ? oldName : newName;
+            boolean mandatory = newMandatory == null ? 
columnHeader.isMandatory() : newMandatory;
+            if (databaseTypeResolver.isMySQL()) {
+                String modifySql = nameChanged ? ("CHANGE " + 
sqlGenerator.escape(oldName) + " " + sqlGenerator.escape(colName))
+                        : (" MODIFY " + sqlGenerator.escape(colName));
+                changeBuilder.append(", ").append(modifySql).append(" 
").append(type.formatSql(dialect, precision, scale))
+                        .append(mandatory ? " NOT NULL" : " DEFAULT NULL");
+                if (after != null) {
+                    changeBuilder.append(" AFTER 
").append(sqlGenerator.escape(after));
+                }
+            } else {
+                if (nameChanged) {
+                    renameBuilder.append("ALTER TABLE 
").append(sqlGenerator.escape(datatableName)).append(" RENAME COLUMN ")
+                            .append(sqlGenerator.escape(oldName)).append(" TO 
").append(sqlGenerator.escape(newName)).append("; ");
+                }
+                if (lengthChanged) {
+                    changeBuilder.append(", ALTER 
").append(sqlGenerator.escape(colName)).append(" type ")
+                            .append(type.formatSql(dialect, precision, scale));
+                }
+                if (nullityChanged) {
+                    changeBuilder.append(", ALTER 
").append(sqlGenerator.escape(colName))
+                            .append(mandatory ? " set not null" : " drop not 
null");
+                }
+            }
+        }
+    }
+
+    private void parseDatatableColumnForDrop(final JsonObject column, 
StringBuilder sqlBuilder, final String datatableName,
+            final StringBuilder constrainBuilder, final List<String> 
codeMappings) {
+        final String datatableAlias = 
datatableName.toLowerCase().replaceAll("\\s", "_");
+        final String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : null;
+        if (name == null) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.missing.datatable.column.name",
+                    "Datatable column name to drop is missing.");
+        }
+        sqlBuilder.append(", DROP COLUMN 
").append(sqlGenerator.escape(name)).append(" ");
+
+        String fkName = "fk_" + datatableAlias + "_" + name;
+        String schemaSql = databaseTypeResolver.isMySQL() ? "i.TABLE_SCHEMA = 
SCHEMA()"
+                : "i.table_catalog = current_catalog AND i.table_schema = 
current_schema";
+        String findFKSql = "SELECT count(*) FROM 
information_schema.TABLE_CONSTRAINTS i" + " WHERE i.CONSTRAINT_TYPE = 'FOREIGN 
KEY' AND "
+                + schemaSql + " AND i.TABLE_NAME = '" + datatableName + "' AND 
i.CONSTRAINT_NAME = '" + fkName + "' ";
+
+        final Integer count = this.jdbcTemplate.queryForObject(findFKSql, 
Integer.class);
+        if (count != null && count > 0) {
+            codeMappings.add(datatableAlias + "_" + name);
+            constrainBuilder.append(", DROP FOREIGN KEY 
").append(sqlGenerator.escape(fkName)).append(" ");
+        }
+    }
+
+    private void registerColumnCodeMapping(final Map<String, Long> 
codeMappings) {
+        if (codeMappings != null && !codeMappings.isEmpty()) {
+            final String[] addSqlList = new String[codeMappings.size()];
+            int i = 0;
+            for (final Map.Entry<String, Long> mapEntry : 
codeMappings.entrySet()) {
+                addSqlList[i++] = "insert into x_table_column_code_mappings 
(column_alias_name, code_id) values ('" + mapEntry.getKey()
+                        + "'," + mapEntry.getValue() + ");";
+            }
+
+            this.jdbcTemplate.batchUpdate(addSqlList);
+        }
+    }
+
+    private void deleteColumnCodeMapping(final List<String> columnNames) {
+        if (columnNames != null && !columnNames.isEmpty()) {
+            final String[] deleteSqlList = new String[columnNames.size()];
+            int i = 0;
+            for (final String columnName : columnNames) {
+                deleteSqlList[i++] = "DELETE FROM x_table_column_code_mappings 
WHERE  column_alias_name='" + columnName + "';";
+            }
+
+            this.jdbcTemplate.batchUpdate(deleteSqlList);
+        }
+    }
+
+    private int getCodeIdForColumn(final String dataTableNameAlias, final 
String name) {
+        final StringBuilder checkColumnCodeMapping = new StringBuilder();
+        checkColumnCodeMapping.append("select ccm.code_id from 
x_table_column_code_mappings ccm where ccm.column_alias_name='")
+                
.append(dataTableNameAlias).append("_").append(name).append("'");
+        Integer codeId = 0;
+        try {
+            codeId = 
this.jdbcTemplate.queryForObject(checkColumnCodeMapping.toString(), 
Integer.class);
+        } catch (final EmptyResultDataAccessException e) {
+            log.warn("Error occurred.", e);
+        }
+        return ObjectUtils.defaultIfNull(codeId, 0);
+    }
+
+    /**
+     * Update data table, set column value to empty string where current value 
is NULL. Run update SQL only if the
+     * "mandatory" property is set to true
+     *
+     * @param datatableName
+     *            Name of data table
+     * @param column
+     *            JSON encoded array of column properties
+     * @see <a 
href="https://mifosforge.jira.com/browse/MIFOSX-1145";>MIFOSX-1145</a>
+     **/
+    private void removeNullValuesFromStringColumn(final String datatableName, 
final JsonObject column,
+            final Map<String, ResultsetColumnHeaderData> 
mapColumnNameDefinition) {
+        final boolean mandatory = column.has(API_FIELD_MANDATORY) && 
column.get(API_FIELD_MANDATORY).getAsBoolean();
+        final String name = column.has(API_FIELD_NAME) ? 
column.get(API_FIELD_NAME).getAsString() : "";
+        final JdbcJavaType type = mapColumnNameDefinition.containsKey(name) ? 
mapColumnNameDefinition.get(name).getColumnType() : null;
+
+        if (type != null && mandatory && type.isStringType()) {
+            String sql = "UPDATE " + sqlGenerator.escape(datatableName) + " 
SET " + sqlGenerator.escape(name) + " = '' WHERE "
+                    + sqlGenerator.escape(name) + " IS NULL";
+            this.jdbcTemplate.update(sql);
         }
     }
 
@@ -1422,32 +1262,142 @@ public class ReadWriteNonCoreDataServiceImpl 
implements ReadWriteNonCoreDataServ
             } else {
                 sqlArray = new String[1];
             }
-            final String sql = "DROP TABLE " + 
sqlGenerator.escape(datatableName);
-            sqlArray[0] = sql;
-            this.jdbcTemplate.batchUpdate(sqlArray);
-        } catch (final JpaSystemException | DataIntegrityViolationException e) 
{
-            final Throwable realCause = e.getCause();
-            final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
-            final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource("datatable");
-            if (realCause.getMessage().contains("Unknown table")) {
-                
baseDataValidator.reset().parameter(API_PARAM_DATATABLE_NAME).failWithCode("does.not.exist");
+            final String sql = "DROP TABLE " + 
sqlGenerator.escape(datatableName);
+            sqlArray[0] = sql;
+            this.jdbcTemplate.batchUpdate(sqlArray);
+        } catch (final JpaSystemException | DataIntegrityViolationException e) 
{
+            final Throwable realCause = e.getCause();
+            final List<ApiParameterError> dataValidationErrors = new 
ArrayList<>();
+            final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors).resource("datatable");
+            if (realCause.getMessage().contains("Unknown table")) {
+                
baseDataValidator.reset().parameter(API_PARAM_DATATABLE_NAME).failWithCode("does.not.exist");
+            }
+            baseDataValidator.throwValidationErrors();
+        }
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createNewDatatableEntry(final String 
dataTableName, final Long appTableId, final JsonCommand command) {
+        return createNewDatatableEntry(dataTableName, appTableId, 
command.json(), false);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createNewDatatableEntry(final String 
dataTableName, final Long appTableId, final String json) {
+        return createNewDatatableEntry(dataTableName, appTableId, json, false);
+    }
+
+    @Transactional
+    @Override
+    public CommandProcessingResult createPPIEntry(final String dataTableName, 
final Long appTableId, final JsonCommand command) {
+        return createNewDatatableEntry(dataTableName, appTableId, 
command.json(), true);
+    }
+
+    private CommandProcessingResult createNewDatatableEntry(final String 
dataTableName, final Long appTableId, final String json,
+            boolean addScore) {
+        final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
+        CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
+
+        List<ResultsetColumnHeaderData> columnHeaders = 
genericDataService.fillResultsetColumnHeaders(dataTableName);
+        Map<String, ResultsetColumnHeaderData> headersByName = 
SearchUtil.mapHeadersToName(columnHeaders);
+
+        final Type typeOfMap = new TypeToken<Map<String, String>>() 
{}.getType();
+        final Map<String, String> dataParams = 
fromJsonHelper.extractDataMap(typeOfMap, json);
+
+        final String dateFormat = dataParams.get(API_PARAM_DATE_FORMAT);
+        // fall back to dateFormat to keep backward compatibility
+        final String dateTimeFormat = 
dataParams.getOrDefault(API_PARAM_DATETIME_FORMAT, dateFormat);
+        final String localeString = dataParams.get(API_PARAM_LOCALE);
+        Locale locale = localeString == null ? null : 
JsonParserHelper.localeFromString(localeString);
+
+        ArrayList<String> insertColumns = new ArrayList<>(
+                List.of(entityTable.getForeignKeyColumnNameOnDatatable(), 
CREATEDAT_FIELD_NAME, UPDATEDAT_FIELD_NAME));
+        LocalDateTime auditDateTime = DateUtils.getAuditLocalDateTime();
+        ArrayList<Object> params = new ArrayList<>(List.of(appTableId, 
auditDateTime, auditDateTime));
+        for (String key : dataParams.keySet()) {
+            if (isTechnicalParam(key)) {
+                continue;
+            }
+            ResultsetColumnHeaderData columnHeader = 
SearchUtil.validateToJdbcColumn(key, headersByName, false);
+            if (!isUserInsertable(entityTable, columnHeader)) {
+                continue;
+            }
+            insertColumns.add(columnHeader.getColumnName());
+            params.add(SearchUtil.parseJdbcColumnValue(columnHeader, 
dataParams.get(key), dateFormat, dateTimeFormat, locale, false,
+                    sqlGenerator));
+        }
+        final String sql = sqlGenerator.buildInsert(dataTableName, 
insertColumns);
+        if (addScore) {
+            List<Object> scoreIds = params.stream().filter(e -> e != null && 
!String.valueOf(e).isBlank()).toList();
+            int scoreValue;
+            if (scoreIds.isEmpty()) {
+                scoreValue = 0;
+            } else {
+                StringBuilder scoreSql = new StringBuilder("SELECT 
SUM(code_score) FROM m_code_value WHERE m_code_value.");
+                ArrayList<Object> scoreParams = new ArrayList<>();
+                SearchUtil.buildCondition("id", BIGINT, IN, scoreIds, 
scoreSql, scoreParams, null, sqlGenerator);
+                Integer score = 
jdbcTemplate.queryForObject(scoreSql.toString(), Integer.class, 
scoreParams.toArray(Object[]::new));
+                scoreValue = score == null ? 0 : score;
+            }
+            insertColumns.add("score");
+            params.add(scoreValue);
+        }
+        try {
+            int updated = jdbcTemplate.update(sql, 
params.toArray(Object[]::new));
+            if (updated != 1) {
+                throw new 
PlatformDataIntegrityException("error.msg.invalid.insert", "Expected one 
inserted row.");
+            }
+
+            Long resourceId = appTableId;
+            if (isMultirowDatatable(columnHeaders)) {
+                resourceId = 
jdbcTemplate.queryForObject(DatabaseSpecificSQLGenerator.SELECT_CLAUSE.formatted(sqlGenerator.lastInsertId()),
+                        Long.class);
+            }
+            return 
CommandProcessingResult.fromCommandProcessingResult(commandProcessingResult, 
resourceId);
+        } catch (final DataAccessException dve) {
+            final Throwable cause = dve.getCause();
+            final Throwable realCause = dve.getMostSpecificCause();
+            if (realCause.getMessage().contains("Duplicate entry") || 
cause.getMessage().contains("Duplicate entry")) {
+                throw new PlatformDataIntegrityException(
+                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
+                                + "` and application table with identifier `" 
+ appTableId + "`.",
+                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
+            } else if (realCause.getMessage().contains("doesn't have a default 
value")
+                    || cause.getMessage().contains("doesn't have a default 
value")) {
+                throw new PlatformDataIntegrityException(
+                        
"error.msg.datatable.no.value.provided.for.required.fields", "No values 
provided for the datatable `"
+                                + dataTableName + "` and application table 
with identifier `" + appTableId + "`.",
+                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
dve);
             }
 
-            throwExceptionIfValidationWarningsExist(dataValidationErrors);
-        }
-    }
+            logAsErrorUnexpectedDataIntegrityException(dve);
+            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.", dve);
+        } catch (final PersistenceException e) {
+            final Throwable cause = e.getCause();
+            if (cause.getMessage().contains("Duplicate entry")) {
+                throw new PlatformDataIntegrityException(
+                        "error.msg.datatable.entry.duplicate", "An entry 
already exists for datatable `" + dataTableName
+                                + "` and application table with identifier `" 
+ appTableId + "`.",
+                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
e);
+            } else if (cause.getMessage().contains("doesn't have a default 
value")) {
+                throw new PlatformDataIntegrityException(
+                        
"error.msg.datatable.no.value.provided.for.required.fields", "No values 
provided for the datatable `"
+                                + dataTableName + "` and application table 
with identifier `" + appTableId + "`.",
+                        API_PARAM_DATATABLE_NAME, dataTableName, appTableId, 
e);
+            }
 
-    private void assertDataTableEmpty(final String datatableName) {
-        final int rowCount = getRowCount(datatableName);
-        if (rowCount != 0) {
-            throw new 
GeneralPlatformDomainRuleException("error.msg.non.empty.datatable.cannot.be.deleted",
-                    "Non-empty datatable cannot be deleted.");
+            logAsErrorUnexpectedDataIntegrityException(e);
+            throw new 
PlatformDataIntegrityException("error.msg.unknown.data.integrity.issue",
+                    "Unknown data integrity issue with resource.", e);
         }
     }
 
-    private int getRowCount(final String datatableName) {
-        final String sql = "select count(*) from " + 
sqlGenerator.escape(datatableName);
-        return this.jdbcTemplate.queryForObject(sql, Integer.class); // NOSONAR
+    private static boolean isUserInsertable(@NotNull EntityTables entityTable, 
@NotNull ResultsetColumnHeaderData columnHeader) {
+        String columnName = columnHeader.getColumnName();
+        return !columnHeader.getIsColumnPrimaryKey() && 
!CREATEDAT_FIELD_NAME.equals(columnName) && 
!UPDATEDAT_FIELD_NAME.equals(columnName)
+                && 
!entityTable.getForeignKeyColumnNameOnDatatable().equals(columnName);
     }
 
     @Transactional
@@ -1464,78 +1414,108 @@ public class ReadWriteNonCoreDataServiceImpl 
implements ReadWriteNonCoreDataServ
         return updateDatatableEntry(dataTableName, appTableId, datatableId, 
command);
     }
 
+    @SuppressWarnings({ "WhitespaceAround" })
     private CommandProcessingResult updateDatatableEntry(final String 
dataTableName, final Long appTableId, final Long datatableId,
             final JsonCommand command) {
         final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
-        final CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
+        CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
 
-        final GenericResultsetData grs = 
retrieveDataTableGenericResultSetForUpdate(entityTable, dataTableName, 
appTableId, datatableId);
-
-        if (grs.hasNoEntries()) {
+        final GenericResultsetData existingRows = 
retrieveDataTableGenericResultSet(entityTable, dataTableName, appTableId, null,
+                datatableId);
+        if (existingRows.hasNoEntries()) {
             throw new DatatableNotFoundException(dataTableName, appTableId);
         }
+        if (existingRows.hasMoreThanOneEntry()) {
+            throw new 
PlatformDataIntegrityException("error.msg.attempting.multiple.update",
+                    "Application table: " + dataTableName + " Foreign key id: 
" + appTableId);
+        }
 
-        if (grs.hasMoreThanOneEntry()) {
+        List<ResultsetColumnHeaderData> columnHeaders = 
existingRows.getColumnHeaders();
+        if (isMultirowDatatable(columnHeaders) && datatableId == null) {
             throw new 
PlatformDataIntegrityException("error.msg.attempting.multiple.update",
                     "Application table: " + dataTableName + " Foreign key id: 
" + appTableId);
         }
+        Map<String, ResultsetColumnHeaderData> headersByName = 
SearchUtil.mapHeadersToName(columnHeaders);
+        final List<Object> existingValues = 
existingRows.getData().get(0).getRow();
+        HashMap<ResultsetColumnHeaderData, Object> valuesByHeader = 
columnHeaders.stream().collect(HashMap::new,
+                (map, e) -> map.put(e, existingValues.get(map.size())), (map, 
map2) -> {});
 
         final Type typeOfMap = new TypeToken<Map<String, String>>() 
{}.getType();
-        final Map<String, String> dataParams = 
this.fromJsonHelper.extractDataMap(typeOfMap, command.json());
-
-        String pkName = TABLE_FIELD_ID; // 1:M datatable
-        if (datatableId == null) {
-            pkName = getFKField(entityTable);
-        } // 1:1 datatable
-
-        final Map<String, Object> changes = getAffectedAndChangedColumns(grs, 
dataParams, pkName);
-
-        if (!changes.isEmpty()) {
-            Long pkValue = appTableId;
-            if (datatableId != null) {
-                pkValue = datatableId;
+        final Map<String, String> dataParams = 
fromJsonHelper.extractDataMap(typeOfMap, command.json());
+
+        final String dateFormat = dataParams.get(API_PARAM_DATE_FORMAT);
+        // fall back to dateFormat to keep backward compatibility
+        final String dateTimeFormat = 
dataParams.getOrDefault(API_PARAM_DATETIME_FORMAT, dateFormat);
+        final String localeString = dataParams.get(API_PARAM_LOCALE);
+        Locale locale = localeString == null ? null : 
JsonParserHelper.localeFromString(localeString);
+
+        DatabaseType dialect = sqlGenerator.getDialect();
+        List<String> updateColumns = new 
ArrayList<>(List.of(UPDATEDAT_FIELD_NAME));
+        List<Object> params = new 
ArrayList<>(List.of(DateUtils.getAuditLocalDateTime()));
+        final HashMap<String, Object> changes = new HashMap<>();
+        for (String key : dataParams.keySet()) {
+            if (isTechnicalParam(key)) {
+                continue;
             }
-            final String sql = getUpdateSql(grs.getColumnHeaders(), 
dataTableName, pkName, pkValue, changes);
-            log.debug("Update sql: {}", sql);
-            if (StringUtils.isNotBlank(sql)) {
-                this.jdbcTemplate.update(sql);
-            } else {
-                log.debug("No Changes");
+            ResultsetColumnHeaderData columnHeader = 
SearchUtil.validateToJdbcColumn(key, headersByName, false);
+            if (!isUserUpdatable(entityTable, columnHeader)) {
+                continue;
+            }
+            String columnName = columnHeader.getColumnName();
+            Object existingValue = valuesByHeader.get(columnHeader);
+            Object columnValue = SearchUtil.parseColumnValue(columnHeader, 
dataParams.get(key), dateFormat, dateTimeFormat, locale, false,
+                    sqlGenerator);
+            if ((columnHeader.getColumnType().isDecimalType() && 
MathUtil.isEqualTo((BigDecimal) existingValue, (BigDecimal) columnValue))
+                    || (existingValue == null ? columnValue == null : 
existingValue.equals(columnValue))) {
+                log.debug("Ignore change on update {}:{}", dataTableName, 
columnName);
+                continue;
             }
+            updateColumns.add(columnName);
+            params.add(columnHeader.getColumnType().toJdbcValue(dialect, 
columnValue, false));
+            changes.put(columnName, columnValue);
+        }
+        Long primaryKey = datatableId == null ? appTableId : datatableId;
+        if (!updateColumns.isEmpty()) {
+            ResultsetColumnHeaderData pkColumn = 
SearchUtil.getFiltered(columnHeaders, 
ResultsetColumnHeaderData::getIsColumnPrimaryKey);
+            params.add(primaryKey);
+            final String sql = sqlGenerator.buildUpdate(dataTableName, 
updateColumns) + " WHERE " + pkColumn.getColumnName() + " = ?";
+            int updated = jdbcTemplate.update(sql, 
params.toArray(Object[]::new));
+            if (updated != 1) {
+                throw new 
PlatformDataIntegrityException("error.msg.invalid.update", "Expected one 
updated row.");
+            }
+        } else {
+            log.debug("No change on update {}", dataTableName);
         }
-
         return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()) //
-                .withEntityId(datatableId != null ? command.subentityId() : 
command.entityId()) //
+                .withEntityId(primaryKey) //
                 .withOfficeId(commandProcessingResult.getOfficeId()) //
                 .withGroupId(commandProcessingResult.getGroupId()) //
                 .withClientId(commandProcessingResult.getClientId()) //
                 .withSavingsId(commandProcessingResult.getSavingsId()) //
                 .withLoanId(commandProcessingResult.getLoanId()) //
+                .withTransactionId(commandProcessingResult.getTransactionId()) 
//
                 .with(changes).build();
     }
 
+    private static boolean isUserUpdatable(@NotNull EntityTables entityTable, 
@NotNull ResultsetColumnHeaderData columnHeader) {
+        return isUserInsertable(entityTable, columnHeader);
+    }
+
     @Transactional
     @Override
-    public CommandProcessingResult deleteDatatableEntries(final String 
dataTableName, final Long appTableId) {
-        validateDatatableName(dataTableName);
-        if (isDatatableAttachedToEntityDatatableCheck(dataTableName)) {
-            throw new DatatableEntryRequiredException(dataTableName, 
appTableId);
-        }
-        final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
-        final CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
-        final String deleteOneToOneEntrySql = 
getDeleteEntriesSql(dataTableName, getFKField(entityTable), appTableId);
-
-        final int rowsDeleted = 
this.jdbcTemplate.update(deleteOneToOneEntrySql);
-        if (rowsDeleted < 1) {
-            throw new DatatableNotFoundException(dataTableName, appTableId);
-        }
-
-        return commandProcessingResult;
+    public CommandProcessingResult deleteDatatableEntries(final String 
dataTableName, final Long appTableId, JsonCommand command) {
+        return deleteDatatableEntries(dataTableName, appTableId, null, 
command);
     }
 
     @Transactional
     @Override
-    public CommandProcessingResult deleteDatatableEntry(final String 
dataTableName, final Long appTableId, final Long datatableId) {
+    public CommandProcessingResult deleteDatatableEntry(final String 
dataTableName, final Long appTableId, final Long datatableId,
+            JsonCommand command) {
+        return deleteDatatableEntries(dataTableName, appTableId, datatableId, 
command);
+    }
+
+    private CommandProcessingResult deleteDatatableEntries(final String 
dataTableName, final Long appTableId, final Long datatableId,
+            JsonCommand command) {
         validateDatatableName(dataTableName);
         if (isDatatableAttachedToEntityDatatableCheck(dataTableName)) {
             throw new DatatableEntryRequiredException(dataTableName, 
appTableId);
@@ -1543,10 +1523,29 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
         final CommandProcessingResult commandProcessingResult = 
checkMainResourceExistsWithinScope(entityTable, appTableId);
 
-        final String sql = getDeleteEntrySql(dataTableName, datatableId);
+        String whereColumn;
+        Long whereValue;
+        if (datatableId == null) {
+            whereColumn = getFKField(entityTable);
+            whereValue = appTableId;
+        } else {
+            whereColumn = TABLE_FIELD_ID;
+            whereValue = datatableId;
+        }
+        String sql = "DELETE FROM " + sqlGenerator.escape(dataTableName) + " 
WHERE " + sqlGenerator.escape(whereColumn) + " = "
+                + whereValue;
 
         this.jdbcTemplate.update(sql);
-        return commandProcessingResult;
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(whereValue) //
+                .withOfficeId(commandProcessingResult.getOfficeId()) //
+                .withGroupId(commandProcessingResult.getGroupId()) //
+                .withClientId(commandProcessingResult.getClientId()) //
+                .withSavingsId(commandProcessingResult.getSavingsId()) //
+                .withLoanId(commandProcessingResult.getLoanId()) //
+                .withTransactionId(commandProcessingResult.getTransactionId()) 
//
+                .build();
     }
 
     @Override
@@ -1555,7 +1554,11 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
             final Long id) {
         final EntityTables entityTable = 
queryForApplicationEntity(dataTableName);
         checkMainResourceExistsWithinScope(entityTable, appTableId);
+        return retrieveDataTableGenericResultSet(entityTable, dataTableName, 
appTableId, order, id);
+    }
 
+    private GenericResultsetData retrieveDataTableGenericResultSet(final 
EntityTables entityTable, final String dataTableName,
+            final Long appTableId, final String order, final Long id) {
         final List<ResultsetColumnHeaderData> columnHeaders = 
this.genericDataService.fillResultsetColumnHeaders(dataTableName);
         final boolean multiRow = isMultirowDatatable(columnHeaders);
 
@@ -1563,38 +1566,15 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         SQLInjectionValidator.validateSQLInput(whereClause);
         String sql = "select * from " + sqlGenerator.escape(dataTableName) + " 
where " + whereClause;
 
-        // id only used for reading a specific entry that belongs to 
appTableId (in a
-        // one to many datatable)
+        // id only used for reading a specific entry that belongs to 
appTableId (in a one to many datatable)
         if (multiRow && id != null) {
             sql = sql + " and " + TABLE_FIELD_ID + " = " + id;
         }
-
         if (StringUtils.isNotBlank(order)) {
             this.columnValidator.validateSqlInjection(sql, order);
             sql = sql + " order by " + order;
         }
 
-        final List<ResultsetRowData> result = 
genericDataService.fillResultsetRowData(sql, columnHeaders);
-
-        return new GenericResultsetData(columnHeaders, result);
-    }
-
-    private GenericResultsetData 
retrieveDataTableGenericResultSetForUpdate(final EntityTables entityTable, 
final String dataTableName,
-            final Long appTableId, final Long id) {
-        final List<ResultsetColumnHeaderData> columnHeaders = 
this.genericDataService.fillResultsetColumnHeaders(dataTableName);
-
-        final boolean multiRow = isMultirowDatatable(columnHeaders);
-
-        String whereClause = getFKField(entityTable) + " = " + appTableId;
-        SQLInjectionValidator.validateSQLInput(whereClause);
-        String sql = "select * from " + sqlGenerator.escape(dataTableName) + " 
where " + whereClause;
-
-        // id only used for reading a specific entry that belongs to 
appTableId (in a
-        // one to many datatable)
-        if (multiRow && id != null) {
-            sql = sql + " and " + TABLE_FIELD_ID + " = " + id;
-        }
-
         final List<ResultsetRowData> result = 
genericDataService.fillResultsetRowData(sql, columnHeaders);
         return new GenericResultsetData(columnHeaders, result);
     }
@@ -1607,13 +1587,13 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         if (!rs.next()) {
             throw new DatatableNotFoundException(entityTable, appTableId);
         }
-
-        final Long officeId = getLongSqlRowSet(rs, "officeId");
-        final Long groupId = getLongSqlRowSet(rs, "groupId");
-        final Long clientId = getLongSqlRowSet(rs, "clientId");
-        final Long savingsId = getLongSqlRowSet(rs, "savingsId");
-        final Long LoanId = getLongSqlRowSet(rs, "loanId");
-        final Long entityId = getLongSqlRowSet(rs, "entityId");
+        final Long officeId = rs.getLong("officeId");
+        final Long groupId = rs.getLong("groupId");
+        final Long clientId = rs.getLong("clientId");
+        final Long savingsId = rs.getLong("savingsId");
+        final Long loanId = rs.getLong("loanId");
+        final Long transactionId = rs.getLong("transactionId");
+        final Long entityId = rs.getLong("entityId");
 
         if (rs.next()) {
             throw new DatatableSystemErrorException("System Error: More than 
one row returned from data scoping query");
@@ -1624,59 +1604,48 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
                 .withGroupId(groupId) //
                 .withClientId(clientId) //
                 .withSavingsId(savingsId) //
-                .withLoanId(LoanId).withEntityId(entityId)//
+                
.withLoanId(loanId).withTransactionId(String.valueOf(transactionId)).withEntityId(entityId)//
                 .build();
     }
 
-    private Long getLongSqlRowSet(final SqlRowSet rs, final String column) {
-        Long val = rs.getLong(column);
-        if (val == 0) {
-            val = null;
-        }
-        return val;
-    }
-
     private String dataScopedSQL(@NotNull EntityTables entityTable, final Long 
appTableId) {
-        /*
-         * unfortunately have to, one way or another, be able to restrict data 
to the users office hierarchy. Here, a
-         * few key tables are done. But if additional fields are needed on 
other tables the same pattern applies
-         */
-
+        // unfortunately have to, one way or another, be able to restrict data 
to the users office hierarchy. Here, a
+        // few key tables are done. But if additional fields are needed on 
other tables the same pattern applies
         final AppUser currentUser = this.context.authenticatedUser();
         String officeHierarchy = currentUser.getOffice().getHierarchy();
-        /*
-         * m_loan and m_savings_account are connected to an m_office thru 
either an m_client or an m_group If both it
-         * means it relates to an m_client that is in a group (still an 
m_client account)
-         */
+        // m_loan and m_savings_account are connected to an m_office through 
either an m_client or an m_group If both it
+        // means it relates to an m_client that is in a group (still an 
m_client account)
         return switch (entityTable) {
-            case LOAN -> "select distinct x.* from ("
-                    + " (select o.id as officeId, l.group_id as groupId, 
l.client_id as clientId, null as savingsId, l.id as loanId, null as entityId 
from m_loan l "
+            case LOAN -> "select distinct x.* from ( "
+                    + "(select o.id as officeId, l.group_id as groupId, 
l.client_id as clientId, null as savingsId, l.id as loanId, null as 
transactionId, null as entityId from m_loan l "
                     + getClientOfficeJoinCondition(officeHierarchy, "l") + " 
where l.id = " + appTableId + ")" + " union all "
-                    + " (select o.id as officeId, l.group_id as groupId, 
l.client_id as clientId, null as savingsId, l.id as loanId, null as entityId 
from m_loan l "
+                    + "(select o.id as officeId, l.group_id as groupId, 
l.client_id as clientId, null as savingsId, l.id as loanId, null as 
transactionId, null as entityId from m_loan l "
                     + getGroupOfficeJoinCondition(officeHierarchy, "l") + " 
where l.id = " + appTableId + ")" + " ) as x";
-            case SAVINGS -> "select distinct x.* from ("
-                    + " (select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, null as entityId 
from m_savings_account s "
-                    + getClientOfficeJoinCondition(officeHierarchy, "s") + " 
where s.id = " + appTableId + ")" + " union all "
-                    + " (select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, null as entityId 
from m_savings_account s "
-                    + getGroupOfficeJoinCondition(officeHierarchy, "s") + " 
where s.id = " + appTableId + ")" + " ) as x";
-            case SAVINGS_TRANSACTION -> "select distinct x.* from ("
-                    + " (select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, t.id as entityId 
from m_savings_account_transaction t"
-                    + " join m_savings_account s on t.savings_account_id = 
s.id " + getClientOfficeJoinCondition(officeHierarchy, "s")
-                    + " where t.id = " + appTableId + ")" + " union all "
-                    + " (select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, t.id as entityId 
from m_savings_account_transaction t "
-                    + " join m_savings_account s on t.savings_account_id = 
s.id " + getGroupOfficeJoinCondition(officeHierarchy, "s")
-                    + " where t.id = " + appTableId + ")" + " ) as x";
+            case SAVINGS -> "select distinct x.* from ( "
+                    + "(select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, null as 
transactionId, null as entityId "
+                    + "from m_savings_account s " + 
getClientOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + 
appTableId + ")"
+                    + " union all "
+                    + "(select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, null as 
transactionId, null as entityId "
+                    + "from m_savings_account s " + 
getGroupOfficeJoinCondition(officeHierarchy, "s") + " where s.id = " + 
appTableId + ")"
+                    + " ) as x";
+            case SAVINGS_TRANSACTION -> "select distinct x.* from ( "
+                    + "(select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, t.id as 
transactionId, null as entityId "
+                    + "from m_savings_account_transaction t join 
m_savings_account s on t.savings_account_id = s.id "
+                    + getClientOfficeJoinCondition(officeHierarchy, "s") + " 
where t.id = " + appTableId + ")" + " union all "
+                    + "(select o.id as officeId, s.group_id as groupId, 
s.client_id as clientId, s.id as savingsId, null as loanId, t.id as 
transactionId, null as entityId "
+                    + "from m_savings_account_transaction t join 
m_savings_account s on t.savings_account_id = s.id "
+                    + getGroupOfficeJoinCondition(officeHierarchy, "s") + " 
where t.id = " + appTableId + ")" + " ) as x";
             case CLIENT ->
-                "select o.id as officeId, null as groupId, c.id as clientId, 
null as savingsId, null as loanId, null as entityId from m_client c "
+                "select o.id as officeId, null as groupId, c.id as clientId, 
null as savingsId, null as loanId, null as transactionId, null as entityId from 
m_client c "
                         + getOfficeJoinCondition(officeHierarchy, "c") + " 
where c.id = " + appTableId;
             case GROUP, CENTER ->
-                "select o.id as officeId, g.id as groupId, null as clientId, 
null as savingsId, null as loanId, null as entityId from m_group g "
+                "select o.id as officeId, g.id as groupId, null as clientId, 
null as savingsId, null as loanId, null as transactionId, null as entityId from 
m_group g "
                         + getOfficeJoinCondition(officeHierarchy, "g") + " 
where g.id = " + appTableId;
             case OFFICE ->
-                "select o.id as officeId, null as groupId, null as clientId, 
null as savingsId, null as loanId, null as entityId from m_office o "
-                        + " where o.hierarchy like '" + officeHierarchy + "%'" 
+ " and o.id = " + appTableId;
+                "select o.id as officeId, null as groupId, null as clientId, 
null as savingsId, null as loanId, null as transactionId, null as entityId from 
m_office o "
+                        + "where o.hierarchy like '" + officeHierarchy + "%'" 
+ " and o.id = " + appTableId;
             case LOAN_PRODUCT, SAVINGS_PRODUCT, SHARE_PRODUCT ->
-                "select null as officeId, null as groupId, null as clientId, 
null as savingsId, null as loanId, p.id as entityId from "
+                "select null as officeId, null as groupId, null as clientId, 
null as savingsId, null as loanId, null as transactionId, p.id as entityId from 
"
                         + entityTable.getName() + " as p WHERE p.id = " + 
appTableId;
             default -> throw new 
PlatformDataIntegrityException("error.msg.invalid.dataScopeCriteria",
                     "Application Table: " + entityTable.getName() + " not 
catered for in data Scoping");
@@ -1710,281 +1679,6 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         return resolveEntity(applicationTableName);
     }
 
-    private String getFKField(EntityTables entityTable) {
-        return entityTable.getForeignKeyColumnNameOnDatatable();
-    }
-
-    private String getAddSql(final List<ResultsetColumnHeaderData> 
columnHeaders, final String datatable, final String fkName,
-            final Long appTableId, final Map<String, String> queryParams) {
-        final Map<String, Object> affectedColumns = 
getAffectedColumns(columnHeaders, queryParams, fkName);
-
-        String pValueWrite = "";
-        String addSql = "";
-        final String singleQuote = "'";
-
-        String insertColumns = "";
-        String selectColumns = "";
-        String columnName = "";
-        String pValue = null;
-        for (final ResultsetColumnHeaderData pColumnHeader : columnHeaders) {
-            final String key = pColumnHeader.getColumnName();
-            if (affectedColumns.containsKey(key)) {
-                pValue = String.valueOf(affectedColumns.get(key));
-                if (StringUtils.isEmpty(pValue) || 
"null".equalsIgnoreCase(pValue)) {
-                    pValueWrite = "null";
-                } else {
-                    if (pColumnHeader.getColumnType() == BIT) {
-                        if (databaseTypeResolver.isMySQL()) {
-                            pValueWrite = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "1", "0", "null");
-                        } else if (databaseTypeResolver.isPostgreSQL()) {
-                            pValueWrite = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "B'1'", "B'0'", 
"null");
-                        } else {
-                            throw new IllegalStateException("Current database 
is not supported");
-                        }
-                    } else {
-                        pValueWrite = singleQuote + 
this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote)
-                                + singleQuote;
-                    }
-                }
-                columnName = sqlGenerator.escape(key);
-                insertColumns += ", " + columnName;
-                selectColumns += "," + pValueWrite + " as " + columnName;
-            } else {
-                if 
(key.equalsIgnoreCase(DataTableApiConstant.CREATEDAT_FIELD_NAME)
-                        || 
key.equalsIgnoreCase(DataTableApiConstant.UPDATEDAT_FIELD_NAME)) {
-                    columnName = sqlGenerator.escape(key);
-                    insertColumns += ", " + columnName;
-                    selectColumns += "," + 
sqlGenerator.currentTenantDateTime() + " as " + columnName;
-                }
-            }
-        }
-        addSql = "insert into " + sqlGenerator.escape(datatable) + " (" + 
sqlGenerator.escape(fkName) + " " + insertColumns + ")"
-                + " select " + appTableId + " as id" + selectColumns;
-
-        log.debug("{}", addSql);
-        return addSql;
-    }
-
-    /**
-     * This method is used special for ppi cases Where the score need to be 
computed
-     *
-     * @param columnHeaders
-     * @param datatable
-     * @param fkName
-     * @param appTableId
-     * @param queryParams
-     * @return
-     */
-    public String getAddSqlWithScore(final List<ResultsetColumnHeaderData> 
columnHeaders, final String datatable, final String fkName,
-            final Long appTableId, final Map<String, String> queryParams) {
-        final Map<String, Object> affectedColumns = 
getAffectedColumns(columnHeaders, queryParams, fkName);
-
-        String pValueWrite = "";
-        String scoresId = " ";
-        final String singleQuote = "'";
-
-        String insertColumns = "";
-        String selectColumns = "";
-        String columnName = "";
-        String pValue = null;
-        for (final String key : affectedColumns.keySet()) {
-            pValue = String.valueOf(affectedColumns.get(key));
-
-            if (StringUtils.isEmpty(pValue) || 
"null".equalsIgnoreCase(pValue)) {
-                pValueWrite = "null";
-            } else {
-                pValueWrite = singleQuote + 
this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote) 
+ singleQuote;
-
-                scoresId += pValueWrite + " ,";
-
-            }
-            columnName = sqlGenerator.escape(key);
-            insertColumns += ", " + columnName;
-            selectColumns += "," + pValueWrite + " as " + columnName;
-        }
-
-        scoresId = scoresId.replaceAll(" ,$", "");
-
-        String vaddSql = "insert into " + sqlGenerator.escape(datatable) + " 
(" + sqlGenerator.escape(fkName) + " " + insertColumns
-                + ", score )" + " select " + appTableId + " as id" + 
selectColumns
-                + " , ( SELECT SUM( code_score ) FROM m_code_value WHERE 
m_code_value.id IN (" + scoresId + " ) ) as score";
-
-        log.debug("{}", vaddSql);
-        return vaddSql;
-    }
-
-    private String getUpdateSql(List<ResultsetColumnHeaderData> columnHeaders, 
final String datatable, final String keyFieldName,
-            final Long keyFieldValue, final Map<String, Object> 
changedColumns) {
-        // just updating fields that have changed since pre-update read - 
though
-        // its possible these values are different from the page the user was
-        // looking at and even different from the current db values (if some
-        // other update got in quick) - would need a version field for
-        // completeness but its okay to take this risk with additional fields
-        // data
-
-        if (changedColumns.size() == 0) {
-            return null;
-        }
-
-        String pValue = null;
-        String pValueWrite = "";
-        final String singleQuote = "'";
-        boolean firstColumn = true;
-        String sql = "update " + sqlGenerator.escape(datatable) + " ";
-        for (final ResultsetColumnHeaderData pColumnHeader : columnHeaders) {
-            final String key = pColumnHeader.getColumnName();
-            if (changedColumns.containsKey(key)) {
-                if (firstColumn) {
-                    sql += " set ";
-                    firstColumn = false;
-                } else {
-                    sql += ", ";
-                }
-
-                pValue = String.valueOf(changedColumns.get(key));
-                if (StringUtils.isEmpty(pValue) || 
"null".equalsIgnoreCase(pValue)) {
-                    pValueWrite = "null";
-                } else {
-                    if (pColumnHeader.getColumnType() == BIT) {
-                        if (databaseTypeResolver.isMySQL()) {
-                            pValueWrite = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "1", "0", "null");
-                        } else if (databaseTypeResolver.isPostgreSQL()) {
-                            pValueWrite = 
BooleanUtils.toString(BooleanUtils.toBooleanObject(pValue), "B'1'", "B'0'", 
"null");
-                        } else {
-                            throw new IllegalStateException("Current database 
is not supported");
-                        }
-                    } else {
-                        pValueWrite = singleQuote + 
this.genericDataService.replace(pValue, singleQuote, singleQuote + singleQuote)
-                                + singleQuote;
-                    }
-                }
-                sql += sqlGenerator.escape(key) + " = " + pValueWrite;
-            } else {
-                if 
(key.equalsIgnoreCase(DataTableApiConstant.UPDATEDAT_FIELD_NAME)) {
-                    if (firstColumn) {
-                        sql += " set ";
-                        firstColumn = false;
-                    } else {
-                        sql += ", ";
-                    }
-                    sql += sqlGenerator.escape(key) + " = " + 
sqlGenerator.currentTenantDateTime();
-                }
-            }
-        }
-
-        sql += " where " + keyFieldName + " = " + keyFieldValue;
-        return sql;
-    }
-
-    private Map<String, Object> getAffectedAndChangedColumns(final 
GenericResultsetData grs, final Map<String, String> queryParams,
-            final String fkName) {
-        final Map<String, Object> affectedColumns = 
getAffectedColumns(grs.getColumnHeaders(), queryParams, fkName);
-        final Map<String, Object> affectedAndChangedColumns = new HashMap<>();
-
-        for (final String key : affectedColumns.keySet()) {
-            final Object columnValue = affectedColumns.get(key);
-            if (columnChanged(key, columnValue, grs)) {
-                affectedAndChangedColumns.put(key, columnValue);
-            }
-        }
-        return affectedAndChangedColumns;
-    }
-
-    private boolean columnChanged(final String key, final Object value, final 
GenericResultsetData grs) {
-        final List<Object> columnValues = grs.getData().get(0).getRow();
-
-        for (int i = 0; i < grs.getColumnHeaders().size(); i++) {
-            if (key.equals(grs.getColumnHeaders().get(i).getColumnName())) {
-                return notTheSame(columnValues.get(i), value);
-            }
-        }
-        throw new 
PlatformDataIntegrityException("error.msg.invalid.columnName", "Parameter 
Column Name: " + key + " not found");
-    }
-
-    public Map<String, Object> getAffectedColumns(final 
List<ResultsetColumnHeaderData> columnHeaders,
-            final Map<String, String> queryParams, final String keyFieldName) {
-
-        final String dateFormat = queryParams.get("dateFormat");
-        Locale clientApplicationLocale = null;
-        final String localeQueryParam = queryParams.get("locale");
-        if (!StringUtils.isBlank(localeQueryParam)) {
-            clientApplicationLocale = new Locale(queryParams.get("locale"));
-        }
-
-        final String underscore = "_";
-        final String space = " ";
-        String pValue = null;
-        Object validatedValue = null;
-        String queryParamColumnUnderscored;
-        String columnHeaderUnderscored;
-        boolean notFound;
-
-        final Map<String, Object> affectedColumns = new HashMap<>();
-        final Set<String> keys = queryParams.keySet();
-        for (final String key : keys) {
-            // ignores id and foreign key fields
-            // also ignores locale and dateformat fields that are used for
-            // validating numeric and date data
-            if (!(key.equalsIgnoreCase(TABLE_FIELD_ID) || 
key.equalsIgnoreCase(keyFieldName) || key.equals("locale")
-                    || key.equals("dateFormat") || 
key.equals(DataTableApiConstant.CREATEDAT_FIELD_NAME)
-                    || key.equals(DataTableApiConstant.UPDATEDAT_FIELD_NAME))) 
{
-                notFound = true;
-                // matches incoming fields with and without underscores (spaces
-                // and underscores considered the same)
-                queryParamColumnUnderscored = 
this.genericDataService.replace(key, space, underscore);
-                for (final ResultsetColumnHeaderData columnHeader : 
columnHeaders) {
-                    if (notFound) {
-                        columnHeaderUnderscored = 
this.genericDataService.replace(columnHeader.getColumnName(), space, 
underscore);
-                        if 
(queryParamColumnUnderscored.equalsIgnoreCase(columnHeaderUnderscored)) {
-                            pValue = queryParams.get(key);
-                            validatedValue = 
SearchUtil.parseColumnValue(columnHeader, pValue, dateFormat, dateFormat,
-                                    clientApplicationLocale, true, 
sqlGenerator);
-                            affectedColumns.put(columnHeader.getColumnName(), 
validatedValue);
-                            notFound = false;
-                        }
-                    }
-
-                }
-                if (notFound) {
-                    throw new 
PlatformDataIntegrityException("error.msg.column.not.found", "Column: " + key + 
" Not Found");
-                }
-            }
-        }
-        return affectedColumns;
-    }
-
-    private String getDeleteEntriesSql(final String datatable, final String 
FKField, final Long appTableId) {
-        return "delete from " + sqlGenerator.escape(datatable) + " where " + 
sqlGenerator.escape(FKField) + " = " + appTableId;
-    }
-
-    private String getDeleteEntrySql(final String datatable, final Long 
datatableId) {
-        return "delete from " + sqlGenerator.escape(datatable) + " where " + 
TABLE_FIELD_ID + " = " + datatableId;
-    }
-
-    private boolean isMultirowDatatable(final List<ResultsetColumnHeaderData> 
columnHeaders) {
-        boolean multiRow = false;
-        for (ResultsetColumnHeaderData column : columnHeaders) {
-            if (column.isNamed(TABLE_FIELD_ID)) {
-                multiRow = true;
-                break;
-            }
-        }
-        return multiRow;
-    }
-
-    private boolean notTheSame(final Object currValue, final Object pValue) {
-        if (currValue == null && pValue == null) {
-            return false;
-        } else if (currValue == null || pValue == null) {
-            return true;
-        }
-        // Equals would fail if the scale is not the same
-        if (currValue instanceof BigDecimal && pValue instanceof BigDecimal) {
-            return !MathUtil.isEqualTo((BigDecimal) currValue, (BigDecimal) 
pValue);
-        }
-        return !currValue.equals(pValue);
-    }
-
     @Override
     public Long countDatatableEntries(final String datatableName, final Long 
appTableId, String foreignKeyColumn) {
         final String sqlString = "SELECT COUNT(" + 
sqlGenerator.escape(foreignKeyColumn) + ") FROM " + 
sqlGenerator.escape(datatableName)
@@ -1992,6 +1686,8 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         return this.jdbcTemplate.queryForObject(sqlString, Long.class); // 
NOSONAR
     }
 
+    // --- Validation ---
+
     public boolean isDatatableAttachedToEntityDatatableCheck(final String 
datatableName) {
         String sql = "SELECT COUNT(edc.x_registered_table_name) FROM 
x_registered_table xrt"
                 + " JOIN m_entity_datatable_check edc ON 
edc.x_registered_table_name = xrt.registered_table_name"
@@ -2000,25 +1696,6 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
         return count != null && count > 0;
     }
 
-    // --- DbUtils ---
-
-    @NotNull
-    private String mapApiTypeToDbType(String apiType, Integer length) {
-        if (StringUtils.isEmpty(apiType)) {
-            return "";
-        }
-        JdbcJavaType jdbcType = 
DatatableCommandFromApiJsonDeserializer.mapApiTypeToJdbcType(apiType);
-        DatabaseType dialect = databaseTypeResolver.databaseType();
-        if (jdbcType.isDecimalType()) {
-            return jdbcType.formatSql(dialect, 19, 6); // TODO: parameter 
length is not used
-        } else if (apiType.equalsIgnoreCase(API_FIELD_TYPE_DROPDOWN)) {
-            return jdbcType.formatSql(dialect, 11); // TODO: parameter length 
is not used
-        }
-        return jdbcType.formatSql(dialect, length);
-    }
-
-    // --- Validation ---
-
     private EntityTables resolveEntity(final String entityName) {
         EntityTables entityTable = EntityTables.fromEntityName(entityName);
         if (entityTable == null) {
@@ -2060,4 +1737,57 @@ public class ReadWriteNonCoreDataServiceImpl implements 
ReadWriteNonCoreDataServ
                     datatableName);
         }
     }
+
+    private void assertDataTableEmpty(final String datatableName) {
+        final int rowCount = getDatatableRowCount(datatableName);
+        if (rowCount != 0) {
+            throw new 
GeneralPlatformDomainRuleException("error.msg.non.empty.datatable.cannot.be.deleted",
+                    "Non-empty datatable cannot be deleted.");
+        }
+    }
+
+    // --- DbUtils ---
+
+    @NotNull
+    private String mapApiTypeToDbType(String apiType, Integer length) {
+        if (StringUtils.isEmpty(apiType)) {
+            return "";
+        }
+        JdbcJavaType jdbcType = 
DatatableCommandFromApiJsonDeserializer.mapApiTypeToJdbcType(apiType);
+        DatabaseType dialect = databaseTypeResolver.databaseType();
+        if (jdbcType.isDecimalType()) {
+            return jdbcType.formatSql(dialect, 19, 6); // TODO: parameter 
length is not used
+        } else if (apiType.equalsIgnoreCase(API_FIELD_TYPE_DROPDOWN)) {
+            return jdbcType.formatSql(dialect, 11); // TODO: parameter length 
is not used
+        }
+        return jdbcType.formatSql(dialect, length);
+    }
+
+    private int getDatatableRowCount(final String datatableName) {
+        final String sql = "select count(*) from " + 
sqlGenerator.escape(datatableName);
+        Integer count = this.jdbcTemplate.queryForObject(sql, Integer.class);
+        return count == null ? 0 : count;
+    }
+
+    // --- Utils ---
+
+    private String getFKField(EntityTables entityTable) {
+        return entityTable.getForeignKeyColumnNameOnDatatable();
+    }
+
+    private static boolean isTechnicalParam(String param) {
+        return API_PARAM_DATE_FORMAT.equals(param) || 
API_PARAM_DATETIME_FORMAT.equals(param) || API_PARAM_LOCALE.equals(param);
+    }
+
+    private boolean isMultirowDatatable(final List<ResultsetColumnHeaderData> 
columnHeaders) {
+        return SearchUtil.findFiltered(columnHeaders, e -> 
e.isNamed(TABLE_FIELD_ID)) != null;
+    }
+
+    private String datatableColumnNameToCodeValueName(final String columnName, 
final String code) {
+        return code + "_cd_" + columnName;
+    }
+
+    private void logAsErrorUnexpectedDataIntegrityException(final Exception 
dve) {
+        log.error("Error occurred.", dve);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
index b9fe1ec85..910f720e7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/api/SurveyApiResource.java
@@ -174,7 +174,7 @@ public class SurveyApiResource {
             @PathParam("fulfilledId") final Long fulfilledId) {
 
         final CommandWrapper commandRequest = new CommandWrapperBuilder() //
-                .deleteDatatable(surveyName, clientId, fulfilledId) //
+                .deleteDatatableEntry(surveyName, clientId, fulfilledId) //
                 .build();
 
         final CommandProcessingResult result = 
this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
index dbe099447..ed6a8106c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/SearchConstants.java
@@ -31,6 +31,9 @@ public final class SearchConstants {
     public static final String API_PARAM_VALUES = "values";
     public static final String API_PARAM_TABLE = "table";
     public static final String API_PARAM_QUERY = "query";
+    public static final String API_PARAM_DATE_FORMAT = "dateFormat";
+    public static final String API_PARAM_DATETIME_FORMAT = "dateTimeFormat";
+    public static final String API_PARAM_LOCALE = "locale";
 
     private SearchConstants() {}
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java
index 880613f0d..d8dcbba8e 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/search/service/SearchUtil.java
@@ -86,8 +86,7 @@ public final class SearchUtil {
             @NotNull List<String> resultColumns, @NotNull List<JsonObject> 
results) {
         JsonObject json = new JsonObject();
         for (int i = 0; i < selectColumns.size(); i++) {
-            String sCol = selectColumns.get(i).replaceAll("\"", "");
-            Object rowValue = rowSet.getObject(sCol);
+            Object rowValue = rowSet.getObject(selectColumns.get(i));
             if (rowValue != null) {
                 String rCol = resultColumns.get(i);
                 if (rowValue instanceof Character) {
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionDatatableIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionDatatableIntegrationTest.java
index bec3c9a75..0f4f666d5 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionDatatableIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccountTransactionDatatableIntegrationTest.java
@@ -178,7 +178,8 @@ public class 
SavingsAccountTransactionDatatableIntegrationTest {
 
         assertNotNull(response);
 
-        String datatableId = response.getResourceIdentifier();
+        String createdName = response.getResourceIdentifier();
+        assertEquals(datatableName, createdName);
 
         // add entries
         final HashMap<String, Object> datatableEntryMap = new HashMap<>();
@@ -190,13 +191,14 @@ public class 
SavingsAccountTransactionDatatableIntegrationTest {
 
         final boolean genericResultSet = true;
 
-        HashMap<String, Object> datatableEntryResponseFirst = 
this.datatableHelper.createDatatableEntry(datatableId, transactionId,
+        HashMap<String, Object> datatableEntryResponseFirst = 
this.datatableHelper.createDatatableEntry(datatableName, transactionId,
                 genericResultSet, datatabelEntryRequestJsonString);
 
-        assertNotNull(datatableEntryResponseFirst.get("resourceId"));
+        Integer datatableId = (Integer) 
datatableEntryResponseFirst.get("resourceId");
+        assertNotNull(datatableId);
 
         // Read the Datatable entry generated with genericResultSet
-        HashMap<String, Object> items = 
this.datatableHelper.readDatatableEntry(datatableId, transactionId, 
genericResultSet, null, "");
+        HashMap<String, Object> items = 
this.datatableHelper.readDatatableEntry(datatableName, transactionId, 
genericResultSet, null, "");
         assertNotNull(items);
         assertEquals(1, ((List) items.get("data")).size());
 
@@ -206,13 +208,14 @@ public class 
SavingsAccountTransactionDatatableIntegrationTest {
         datatableEntryMap.put("dateFormat", "yyyy-MM-dd");
         datatabelEntryRequestJsonString = new Gson().toJson(datatableEntryMap);
         HashMap<String, Object> updatedDatatableEntryResponse = 
this.datatableHelper.updateDatatableEntry(datatableName, transactionId,
-                false, datatabelEntryRequestJsonString);
+                datatableId, false, datatabelEntryRequestJsonString);
 
-        assertEquals(transactionId, 
updatedDatatableEntryResponse.get("resourceId"));
+        assertEquals(transactionId, Integer.valueOf((String) 
updatedDatatableEntryResponse.get("transactionId")));
+        assertEquals(datatableId, 
updatedDatatableEntryResponse.get("resourceId"));
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(datatableName, transactionId, 
"resourceId");
-        assertEquals(transactionId, appTableId, "ERROR IN DELETING THE 
DATATABLE ENTRIES");
+        String deletedTransactionId = (String) 
this.datatableHelper.deleteDatatableEntries(datatableName, transactionId, 
"transactionId");
+        assertEquals(transactionId, Integer.valueOf(deletedTransactionId), 
"ERROR IN DELETING THE DATATABLE ENTRIES");
 
         // deleting the datatable
         String deletedDataTableName = 
this.datatableHelper.deleteDatatable(datatableName);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java
index 03c7661ed..507c28bc8 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/EntityDatatableChecksIntegrationTest.java
@@ -155,7 +155,7 @@ public class EntityDatatableChecksIntegrationTest {
         assertNotNull(entityDatatableCheckId, "ERROR IN DELETING THE ENTITY 
DATATABLE CHECK");
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(registeredTableName, clientID, 
"clientId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(registeredTableName, clientID, 
"clientId");
         assertEquals(clientID, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
@@ -215,7 +215,7 @@ public class EntityDatatableChecksIntegrationTest {
         assertNotNull(entityDatatableCheckId, "ERROR IN DELETING THE ENTITY 
DATATABLE CHECK");
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(registeredTableName, groupId, 
"groupId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(registeredTableName, groupId, 
"groupId");
         assertEquals(groupId, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
@@ -290,7 +290,7 @@ public class EntityDatatableChecksIntegrationTest {
         assertNotNull(entityDatatableCheckId, "ERROR IN DELETING THE ENTITY 
DATATABLE CHECK");
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(registeredTableName, savingsId, 
"savingsId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(registeredTableName, savingsId, 
"savingsId");
         assertEquals(savingsId, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
@@ -372,7 +372,7 @@ public class EntityDatatableChecksIntegrationTest {
         assertNotNull(entityDatatableCheckId, "ERROR IN DELETING THE ENTITY 
DATATABLE CHECK");
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(registeredTableName, loanID, 
"loanId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(registeredTableName, loanID, 
"loanId");
         assertEquals(loanID, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java
index f40e11a96..313494a7a 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/DatatableHelper.java
@@ -155,7 +155,7 @@ public class DatatableHelper extends IntegrationTest {
                 DATATABLE_URL + "/" + datatableName + "?" + 
Utils.TENANT_IDENTIFIER, "resourceIdentifier");
     }
 
-    public Integer deleteDatatableEntries(final String datatableName, final 
Integer apptableId, String jsonAttributeToGetBack) {
+    public Object deleteDatatableEntries(final String datatableName, final 
Integer apptableId, String jsonAttributeToGetBack) {
         final String deleteEntryUrl = DATATABLE_URL + "/" + datatableName + 
"/" + apptableId + "?genericResultSet=true" + "&"
                 + Utils.TENANT_IDENTIFIER;
         return Utils.performServerDelete(this.requestSpec, this.responseSpec, 
deleteEntryUrl, jsonAttributeToGetBack);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableAdvancedQueryTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableAdvancedQueryTest.java
index e47b1de62..87a6f98ea 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableAdvancedQueryTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableAdvancedQueryTest.java
@@ -359,8 +359,8 @@ public class DatatableAdvancedQueryTest {
 
     private void deleteDatatable(String datatable, Integer... apptableIds) {
         for (Integer apptableId : apptableIds) {
-            Integer deletedId = 
this.datatableHelper.deleteDatatableEntries(datatable, apptableId, 
"resourceId");
-            assertEquals(apptableId, deletedId, "ERROR IN DELETING THE 
DATATABLE ENTRY");
+            String deletedId = (String) 
this.datatableHelper.deleteDatatableEntries(datatable, apptableId, 
"transactionId");
+            assertEquals(apptableId, Integer.valueOf(deletedId), "ERROR IN 
DELETING THE DATATABLE ENTRY");
         }
         String deletedDatatable = 
this.datatableHelper.deleteDatatable(datatable);
         assertEquals(datatable, deletedDatatable, "ERROR IN DELETING THE 
DATATABLE");
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java
index a66697575..8dd74135e 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java
@@ -333,7 +333,7 @@ public class DatatableIntegrationTest extends 
IntegrationTest {
         }
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(datatableName, clientID, 
"clientId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(datatableName, clientID, 
"clientId");
         assertEquals(clientID, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
@@ -434,7 +434,7 @@ public class DatatableIntegrationTest extends 
IntegrationTest {
         LOG.info("query result : {}", queryResult);
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(datatableName, clientID, 
"clientId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(datatableName, clientID, 
"clientId");
         assertEquals(clientID, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable
@@ -662,7 +662,7 @@ public class DatatableIntegrationTest extends 
IntegrationTest {
         assertEquals(randomString, records.get(2));
         assertEquals(textValue, records.get(5));
 
-        Integer resourceId = 
this.datatableHelper.deleteDatatableEntries(datatableName, clientId, 
"resourceId");
+        Integer resourceId = (Integer) 
this.datatableHelper.deleteDatatableEntries(datatableName, clientId, 
"resourceId");
         assertEquals(clientId, resourceId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // Update - update, delete DataTable columns
@@ -915,7 +915,7 @@ public class DatatableIntegrationTest extends 
IntegrationTest {
         assertEquals(datatableEntryMap.get("itsAText"), 
datatableEntryResponseNoGenericResult.get(0).get("itsAText"));
 
         // deleting datatable entries
-        Integer appTableId = 
this.datatableHelper.deleteDatatableEntries(datatableName, loanID, "loanId");
+        Integer appTableId = (Integer) 
this.datatableHelper.deleteDatatableEntries(datatableName, loanID, "loanId");
         assertEquals(loanID, appTableId, "ERROR IN DELETING THE DATATABLE 
ENTRIES");
 
         // deleting the datatable


Reply via email to