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

arnold 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 f797e1f638 FINERACT-1398: Standardize charset and collation defaults
f797e1f638 is described below

commit f797e1f6385b2c5146d16ee2fc03acb4d3d64061
Author: airajena <[email protected]>
AuthorDate: Mon Mar 2 23:21:28 2026 +0530

    FINERACT-1398: Standardize charset and collation defaults
---
 fineract-provider/build.gradle                     |  4 +-
 .../service/DatatableWriteServiceImpl.java         |  2 +-
 .../tenant-store/changelog-tenant-store.xml        |  1 +
 ...11_standardize_character_set_and_collation.xml} | 12 ++---
 .../db/changelog/tenant/changelog-tenant.xml       |  1 +
 ...18_standardize_character_set_and_collation.xml} | 12 ++---
 .../service/DatatableWriteServiceImplTest.java     | 53 ++++++++++++++++++++++
 7 files changed, 64 insertions(+), 21 deletions(-)

diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle
index 34a414a9c9..f79a834a84 100644
--- a/fineract-provider/build.gradle
+++ b/fineract-provider/build.gradle
@@ -185,7 +185,7 @@ tasks.register('createDB') {
     description = "Creates the MariaDB Database. Needs database name to be 
passed (like: -PdbName=someDBname)"
     doLast {
         def sql = Sql.newInstance('jdbc:mariadb://localhost:3306/', mysqlUser, 
mysqlPassword, 'org.mariadb.jdbc.Driver')
-        sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4")
+        sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci")
     }
 }
 
@@ -217,7 +217,7 @@ tasks.register('createMySQLDB') {
     description = "Creates the MySQL Database. Needs database name to be 
passed (like: -PdbName=someDBname)"
     doLast {
         def sql = Sql.newInstance('jdbc:mysql://localhost:3306/', mysqlUser, 
mysqlPassword, 'com.mysql.cj.jdbc.Driver')
-        sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4")
+        sql.execute('CREATE DATABASE ' + "`$dbName` CHARACTER SET utf8mb4 
COLLATE utf8mb4_unicode_ci")
     }
 }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java
index 5c07474ea5..3f81b76674 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImpl.java
@@ -277,7 +277,7 @@ public class DatatableWriteServiceImpl implements 
DatatableWriteService {
             sqlBuilder.append(constrainBuilder);
             sqlBuilder.append(")");
             if (databaseTypeResolver.isMySQL()) {
-                sqlBuilder.append(" ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;");
+                sqlBuilder.append(" ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4 
COLLATE=UTF8MB4_UNICODE_CI;");
             }
             log.debug("SQL:: {}", sqlBuilder);
 
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
index de38521dae..7d5a94e783 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
@@ -31,4 +31,5 @@
      <include file="parts/0008_encrypt_existing_ro_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
      <include file="parts/0009_set_and_encrypt_ro_if_not_exists.xml" 
relativeToChangelogFile="true"/>
      <include file="parts/0010_set_datetime_precision.xml" 
relativeToChangelogFile="true"/>
+     <include file="parts/0011_standardize_character_set_and_collation.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml
similarity index 56%
copy from 
fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
copy to 
fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml
index de38521dae..7825c41606 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0011_standardize_character_set_and_collation.xml
@@ -22,13 +22,7 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
                    
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-     <include file="parts/0003_reset_postgresql_sequences.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0004_readonly_database_connection.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0005_jdbc_connection_string.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0006_drop_retry_parameter_columns.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0007_encrypt_existing_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0007_x_extend_tenant_ro_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0008_encrypt_existing_ro_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0009_set_and_encrypt_ro_if_not_exists.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0010_set_datetime_precision.xml" 
relativeToChangelogFile="true"/>
+    <changeSet author="fineract" id="1" context="tenant_store_db">
+        <sql dbms="mysql,mariadb">ALTER DATABASE CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci;</sql>
+    </changeSet>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index 38143a7df6..7ad3149322 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -236,4 +236,5 @@
     <include 
file="parts/0215_transaction_summary_reports_add_buydown_fee_types.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0216_add_unique_constraint_sms_campaign_name.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0217_force_withdrawal_configs.xml" 
relativeToChangelogFile="true" />
+    <include file="parts/0218_standardize_character_set_and_collation.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml
similarity index 56%
copy from 
fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
copy to 
fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml
index de38521dae..9b995bc863 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0218_standardize_character_set_and_collation.xml
@@ -22,13 +22,7 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
                    
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
-     <include file="parts/0003_reset_postgresql_sequences.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0004_readonly_database_connection.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0005_jdbc_connection_string.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0006_drop_retry_parameter_columns.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0007_encrypt_existing_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0007_x_extend_tenant_ro_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0008_encrypt_existing_ro_tenant_passwords.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0009_set_and_encrypt_ro_if_not_exists.xml" 
relativeToChangelogFile="true"/>
-     <include file="parts/0010_set_datetime_precision.xml" 
relativeToChangelogFile="true"/>
+    <changeSet author="fineract" id="1">
+        <sql dbms="mysql,mariadb">ALTER DATABASE CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci;</sql>
+    </changeSet>
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java
index 44be562808..a1cd828842 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/dataqueries/service/DatatableWriteServiceImplTest.java
@@ -24,21 +24,27 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import 
org.apache.fineract.infrastructure.codes.service.CodeReadPlatformService;
 import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import 
org.apache.fineract.infrastructure.core.serialization.DatatableCommandFromApiJsonDeserializer;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
+import org.apache.fineract.infrastructure.core.service.database.DatabaseType;
 import 
org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver;
 import org.apache.fineract.infrastructure.dataqueries.data.DataTableValidator;
+import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import 
org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.search.service.SearchUtil;
@@ -245,4 +251,51 @@ class DatatableWriteServiceImplTest {
         assertTrue(sql.contains("INSERT INTO x_table_column_code_mappings"), 
"SQL should insert into code mappings table");
         assertTrue(sql.contains("VALUES (?, ?)"), "SQL should use ? 
placeholders");
     }
+
+    @Test
+    void testCreateDatatableUsesUtf8mb4UnicodeCiForMySql() {
+        final JsonElement payload = JsonParser.parseString("""
+                {
+                  "datatableName": "dt_charset_test",
+                  "apptableName": "m_client",
+                  "entitySubType": "PERSON",
+                  "multiRow": false,
+                  "columns": [
+                    {
+                      "name": "itsAString",
+                      "type": "String",
+                      "mandatory": true,
+                      "length": 10
+                    }
+                  ]
+                }
+                """);
+
+        final JsonCommand command = mock(JsonCommand.class);
+        when(command.json()).thenReturn(payload.toString());
+        when(command.commandId()).thenReturn(1L);
+
+        when(databaseTypeResolver.isMySQL()).thenReturn(true);
+        
when(databaseTypeResolver.databaseType()).thenReturn(DatabaseType.MYSQL);
+        
when(configurationDomainService.isConstraintApproachEnabledForDatatables()).thenReturn(false);
+        when(sqlGenerator.currentSchema()).thenReturn("database()");
+        when(sqlGenerator.escape(anyString())).thenAnswer(invocation -> "`" + 
invocation.getArgument(0) + "`");
+        
when(datatableUtil.resolveEntity("m_client")).thenReturn(EntityTables.CLIENT);
+        
when(datatableUtil.getFKField(EntityTables.CLIENT)).thenReturn("client_id");
+        when(fromJsonHelper.parse(anyString())).thenReturn(payload);
+        when(fromJsonHelper.extractJsonArrayNamed(eq("columns"), eq(payload)))
+                
.thenReturn(payload.getAsJsonObject().getAsJsonArray("columns"));
+        when(fromJsonHelper.extractStringNamed(eq("datatableName"), 
eq(payload))).thenReturn("dt_charset_test");
+        when(fromJsonHelper.extractStringNamed(eq("entitySubType"), 
eq(payload))).thenReturn("PERSON");
+        when(fromJsonHelper.extractStringNamed(eq("apptableName"), 
eq(payload))).thenReturn("m_client");
+        when(fromJsonHelper.extractBooleanNamed(eq("multiRow"), 
eq(payload))).thenReturn(false);
+        when(jdbcTemplate.queryForObject(anyString(), eq(String.class), 
eq("dt_charset_test"))).thenReturn("true");
+
+        underTest.createDatatable(command);
+
+        final ArgumentCaptor<String> sqlCaptor = 
ArgumentCaptor.forClass(String.class);
+        verify(jdbcTemplate).execute(sqlCaptor.capture());
+        assertTrue(sqlCaptor.getValue().contains("ENGINE=InnoDB DEFAULT 
CHARSET=UTF8MB4 COLLATE=UTF8MB4_UNICODE_CI;"),
+                "MySQL table creation must include utf8mb4 charset and 
utf8mb4_unicode_ci collation");
+    }
 }

Reply via email to