Skip to content

Commit a9d5307

Browse files
rhopmangalovics
authored andcommitted
FINERACT-2411: Allow empty columns to be deleted from datatables
1 parent e5b6268 commit a9d5307

File tree

2 files changed

+102
-2
lines changed

2 files changed

+102
-2
lines changed

fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,16 @@ public void updateDatatable(final String datatableName, final JsonCommand comman
394394
}
395395

396396
if (dropColumns != null) {
397+
// Check if any of the columns to be dropped have non-NULL values
397398
if (rowCount > 0) {
398-
throw new GeneralPlatformDomainRuleException("error.msg.non.empty.datatable.column.cannot.be.deleted",
399-
"Non-empty datatable columns can not be deleted.");
399+
for (final JsonElement column : dropColumns) {
400+
JsonObject columnAsJson = column.getAsJsonObject();
401+
final String columnName = columnAsJson.has(API_FIELD_NAME) ? columnAsJson.get(API_FIELD_NAME).getAsString() : null;
402+
if (columnName != null && hasNonNullValues(datatableName, columnName)) {
403+
throw new GeneralPlatformDomainRuleException("error.msg.non.empty.datatable.column.cannot.be.deleted",
404+
"Non-empty datatable columns can not be deleted. Column '" + columnName + "' has non-null values.");
405+
}
406+
}
400407
}
401408
StringBuilder sqlBuilder = new StringBuilder(ALTER_TABLE + sqlGenerator.escape(datatableName));
402409
final StringBuilder constrainBuilder = new StringBuilder();
@@ -1392,6 +1399,13 @@ private int getDatatableRowCount(final String datatableName) {
13921399
return count == null ? 0 : count;
13931400
}
13941401

1402+
private boolean hasNonNullValues(final String datatableName, final String columnName) {
1403+
final String sql = "select count(*) from " + sqlGenerator.escape(datatableName) + " where " + sqlGenerator.escape(columnName)
1404+
+ " IS NOT NULL";
1405+
Integer count = this.jdbcTemplate.queryForObject(sql, Integer.class); // NOSONAR
1406+
return count != null && count > 0;
1407+
}
1408+
13951409
private static boolean isTechnicalParam(String param) {
13961410
return API_PARAM_DATE_FORMAT.equals(param) || API_PARAM_DATETIME_FORMAT.equals(param) || API_PARAM_LOCALE.equals(param);
13971411
}

integration-tests/src/test/java/org/apache/fineract/integrationtests/datatable/DatatableIntegrationTest.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,4 +965,90 @@ private Integer createLoanProductWithPeriodicAccrualAccountingEnabled() {
965965
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
966966
}
967967

968+
@Test
969+
public void testDropNullColumnWithData() {
970+
// Create datatable for client entity
971+
final HashMap<String, Object> columnMap = new HashMap<>();
972+
final List<HashMap<String, Object>> datatableColumnsList = new ArrayList<>();
973+
columnMap.put("datatableName", Utils.uniqueRandomStringGenerator(CLIENT_APP_TABLE_NAME + "_", 5));
974+
columnMap.put("apptableName", CLIENT_APP_TABLE_NAME);
975+
columnMap.put("entitySubType", CLIENT_PERSON_SUBTYPE_NAME);
976+
columnMap.put("multiRow", false);
977+
978+
// Add columns: one that will have data and one that will be NULL
979+
addDatatableColumn(datatableColumnsList, "columnWithData", "String", false, 50, null);
980+
addDatatableColumn(datatableColumnsList, "columnWithNull", "String", false, 50, null);
981+
columnMap.put("columns", datatableColumnsList);
982+
983+
String datatableRequestJsonString = new Gson().toJson(columnMap);
984+
LOG.info("Creating datatable: {}", datatableRequestJsonString);
985+
986+
HashMap<String, Object> datatableResponse = this.datatableHelper.createDatatable(datatableRequestJsonString, "");
987+
String datatableName = (String) datatableResponse.get("resourceIdentifier");
988+
assertNotNull(datatableName);
989+
DatatableHelper.verifyDatatableCreatedOnServer(this.requestSpec, this.responseSpec, datatableName);
990+
991+
// Create a client
992+
final Integer clientId = ClientHelper.createClientAsPerson(requestSpec, responseSpec);
993+
994+
// Create a datatable entry with data in one column and NULL in the other
995+
final HashMap<String, Object> datatableEntryMap = new HashMap<>();
996+
datatableEntryMap.put("columnWithData", "TestValue");
997+
// columnWithNull is intentionally not set, so it will be NULL
998+
datatableEntryMap.put("locale", "en");
999+
1000+
String datatableEntryRequestJsonString = new Gson().toJson(datatableEntryMap);
1001+
LOG.info("Creating datatable entry: {}", datatableEntryRequestJsonString);
1002+
1003+
final boolean genericResultSet = true;
1004+
HashMap<String, Object> datatableEntryResponse = this.datatableHelper.createDatatableEntry(datatableName, clientId,
1005+
genericResultSet, datatableEntryRequestJsonString);
1006+
assertNotNull(datatableEntryResponse.get("resourceId"), "ERROR IN CREATING THE ENTITY DATATABLE RECORD");
1007+
assertEquals(clientId, datatableEntryResponse.get("resourceId"));
1008+
1009+
// Verify column count before drop
1010+
GetDataTablesResponse dataTableBeforeDrop = datatableHelper.getDataTableDetails(datatableName);
1011+
List<ResultsetColumnHeaderData> columnHeadersBeforeDrop = dataTableBeforeDrop.getColumnHeaderData();
1012+
// Should have 5 columns before drop: client_id, columnWithData, columnWithNull, created_at, updated_at
1013+
// Note: Datatables automatically add audit columns (created_at, updated_at)
1014+
assertEquals(5, columnHeadersBeforeDrop.size(), "Should have 5 columns before dropping columnWithNull");
1015+
1016+
// Now try to drop the NULL column - this should succeed with the fix
1017+
HashMap<String, Object> updateMap = new HashMap<>();
1018+
updateMap.put("apptableName", CLIENT_APP_TABLE_NAME);
1019+
updateMap.put("entitySubType", CLIENT_PERSON_SUBTYPE_NAME);
1020+
List<Map<String, Object>> dropColumnsList = Collections.singletonList(Collections.singletonMap("name", "columnWithNull"));
1021+
updateMap.put("dropColumns", dropColumnsList);
1022+
1023+
String updateRequestJsonString = new Gson().toJson(updateMap);
1024+
LOG.info("Dropping NULL column: {}", updateRequestJsonString);
1025+
1026+
PutDataTablesResponse updateResponse = this.datatableHelper.updateDatatable(datatableName, updateRequestJsonString);
1027+
assertNotNull(updateResponse);
1028+
assertEquals(datatableName, updateResponse.getResourceIdentifier());
1029+
1030+
// Verify the column was dropped
1031+
GetDataTablesResponse dataTable = datatableHelper.getDataTableDetails(datatableName);
1032+
List<ResultsetColumnHeaderData> columnHeaders = dataTable.getColumnHeaderData();
1033+
// Should have 4 columns after drop: client_id, columnWithData, created_at, updated_at (columnWithNull should be
1034+
// dropped)
1035+
assertEquals(4, columnHeaders.size(), "Should have 4 columns after dropping columnWithNull");
1036+
boolean hasColumnWithData = false;
1037+
boolean hasColumnWithNull = false;
1038+
for (ResultsetColumnHeaderData header : columnHeaders) {
1039+
if ("columnWithData".equals(header.getColumnName())) {
1040+
hasColumnWithData = true;
1041+
}
1042+
if ("columnWithNull".equals(header.getColumnName())) {
1043+
hasColumnWithNull = true;
1044+
}
1045+
}
1046+
assertTrue(hasColumnWithData, "columnWithData should still exist");
1047+
assertFalse(hasColumnWithNull, "columnWithNull should have been dropped");
1048+
1049+
// Clean up
1050+
this.datatableHelper.deleteDatatableEntries(datatableName, clientId, "clientId");
1051+
this.datatableHelper.deleteDatatable(datatableName);
1052+
}
1053+
9681054
}

0 commit comments

Comments
 (0)