This is an automated email from the ASF dual-hosted git repository. dixitdeepak pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit 3eaef2fdbf3cbd6c1c7ef61e81a4789df1597fc9 Author: Deepak Dixit <[email protected]> AuthorDate: Tue Jun 30 20:17:08 2026 +0530 Fixed: ComplexAliasField default value not quoted for non-numeric fields in SQL COALESCE (OFBIZ-5199) The defaultValue in ComplexAliasField.makeAliasColName() was inserted raw into COALESCE SQL, causing syntax errors for date-time and string field types. Numeric literals (0) worked fine but timestamp/string values were unquoted. --- .../apache/ofbiz/entity/model/ModelViewEntity.java | 13 ++- .../model/ModelViewEntityComplexAliasTests.java | 93 ++++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/model/ModelViewEntity.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/model/ModelViewEntity.java index 5543310d85..20b69aa3f7 100644 --- a/framework/entity/src/main/java/org/apache/ofbiz/entity/model/ModelViewEntity.java +++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/model/ModelViewEntity.java @@ -59,6 +59,9 @@ public class ModelViewEntity extends ModelEntity { private static final Map<String, String> FUNCTION_PREFIX_MAP = new HashMap<>(); private static final Set<String> NUMERIC_FUNCTION_SET = new HashSet<>(); // names of functions that return a numeric type + // OFBiz field types that map to numeric Java types — default values for these do not need SQL quoting + private static final Set<String> NUMERIC_FIELD_TYPES = new HashSet<>(Arrays.asList( + "numeric", "integer", "floating-point", "fixed-point", "currency-amount", "currency-precise")); static { FUNCTION_PREFIX_MAP.put("min", "MIN("); FUNCTION_PREFIX_MAP.put("max", "MAX("); @@ -1251,7 +1254,15 @@ public class ModelViewEntity extends ModelEntity { String colName = entityAlias + "." + SqlJdbcUtil.filterColName(modelField.getColName()); if (UtilValidate.isNotEmpty(defaultValue)) { - colName = "COALESCE(" + colName + "," + defaultValue + ")"; + String sqlDefault = defaultValue; + // Quote plain string values for non-numeric fields. + // Skip quoting when the value is already a SQL literal (starts with ') + // or a SQL expression/function call (contains '('), e.g. TO_TIMESTAMP(...). + boolean alreadySqlExpression = defaultValue.startsWith("'") || defaultValue.contains("("); + if (!NUMERIC_FIELD_TYPES.contains(modelField.getType()) && !alreadySqlExpression) { + sqlDefault = "'" + defaultValue + "'"; + } + colName = "COALESCE(" + colName + "," + sqlDefault + ")"; } if (UtilValidate.isNotEmpty(function)) { diff --git a/framework/entity/src/test/java/org/apache/ofbiz/entity/model/ModelViewEntityComplexAliasTests.java b/framework/entity/src/test/java/org/apache/ofbiz/entity/model/ModelViewEntityComplexAliasTests.java index 20e71c9d95..7b80b85b2f 100644 --- a/framework/entity/src/test/java/org/apache/ofbiz/entity/model/ModelViewEntityComplexAliasTests.java +++ b/framework/entity/src/test/java/org/apache/ofbiz/entity/model/ModelViewEntityComplexAliasTests.java @@ -25,6 +25,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -32,6 +33,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * Tests for the Tier-2 complex-alias conversion binding: @@ -44,6 +46,12 @@ public final class ModelViewEntityComplexAliasTests { @Mock private ModelViewEntity mockViewEntity; + @Mock + private ModelReader mockModelReader; + + @Mock + private ModelEntity mockModelEntity; + private AutoCloseable mocks; @BeforeEach @@ -167,4 +175,89 @@ public final class ModelViewEntityComplexAliasTests { verify(mockViewEntity, never()).getOrCreateModelConversion(anyString()); } + + // ── ComplexAliasField.makeAliasColName() — default-value quoting ──────── + + @Test + void makeAliasColNameDateTimeDefaultValueIsWrappedInSingleQuotes() { + ModelField dateTimeField = ModelField.create(null, "estimatedDeliveryDate", "date-time", false); + when(mockViewEntity.getAliasedEntity("OI", mockModelReader)).thenReturn(mockModelEntity); + when(mockViewEntity.getAliasedField(mockModelEntity, "estimatedDeliveryDate", mockModelReader)).thenReturn(dateTimeField); + + ModelViewEntity.ComplexAliasField caf = + new ModelViewEntity.ComplexAliasField("OI", "estimatedDeliveryDate", "2026-06-30 12:34:56.789", "min"); + + StringBuilder colNameBuffer = new StringBuilder(); + StringBuilder fieldTypeBuffer = new StringBuilder(); + caf.makeAliasColName(colNameBuffer, fieldTypeBuffer, mockViewEntity, mockModelReader); + + assertEquals("MIN(COALESCE(OI.ESTIMATED_DELIVERY_DATE,'2026-06-30 12:34:56.789'))", colNameBuffer.toString()); + assertEquals("date-time", fieldTypeBuffer.toString()); + } + + @Test + void makeAliasColNameNumericDefaultValueIsNotQuoted() { + ModelField numericField = ModelField.create(null, "quantity", "numeric", false); + when(mockViewEntity.getAliasedEntity("OI", mockModelReader)).thenReturn(mockModelEntity); + when(mockViewEntity.getAliasedField(mockModelEntity, "quantity", mockModelReader)).thenReturn(numericField); + + ModelViewEntity.ComplexAliasField caf = + new ModelViewEntity.ComplexAliasField("OI", "quantity", "0", null); + + StringBuilder colNameBuffer = new StringBuilder(); + StringBuilder fieldTypeBuffer = new StringBuilder(); + caf.makeAliasColName(colNameBuffer, fieldTypeBuffer, mockViewEntity, mockModelReader); + + assertEquals("COALESCE(OI.QUANTITY,0)", colNameBuffer.toString()); + } + + @Test + void makeAliasColNameAlreadyQuotedDefaultValueIsNotDoubleQuoted() { + ModelField dateTimeField = ModelField.create(null, "estimatedDeliveryDate", "date-time", false); + when(mockViewEntity.getAliasedEntity("OI", mockModelReader)).thenReturn(mockModelEntity); + when(mockViewEntity.getAliasedField(mockModelEntity, "estimatedDeliveryDate", mockModelReader)).thenReturn(dateTimeField); + + ModelViewEntity.ComplexAliasField caf = + new ModelViewEntity.ComplexAliasField("OI", "estimatedDeliveryDate", "'2026-06-30 12:34:56.789'", null); + + StringBuilder colNameBuffer = new StringBuilder(); + StringBuilder fieldTypeBuffer = new StringBuilder(); + caf.makeAliasColName(colNameBuffer, fieldTypeBuffer, mockViewEntity, mockModelReader); + + assertEquals("COALESCE(OI.ESTIMATED_DELIVERY_DATE,'2026-06-30 12:34:56.789')", colNameBuffer.toString()); + } + + @Test + void makeAliasColNameNoDefaultValueProducesPlainColumnName() { + ModelField dateTimeField = ModelField.create(null, "estimatedDeliveryDate", "date-time", false); + when(mockViewEntity.getAliasedEntity("OI", mockModelReader)).thenReturn(mockModelEntity); + when(mockViewEntity.getAliasedField(mockModelEntity, "estimatedDeliveryDate", mockModelReader)).thenReturn(dateTimeField); + + ModelViewEntity.ComplexAliasField caf = + new ModelViewEntity.ComplexAliasField("OI", "estimatedDeliveryDate", null, "min"); + + StringBuilder colNameBuffer = new StringBuilder(); + StringBuilder fieldTypeBuffer = new StringBuilder(); + caf.makeAliasColName(colNameBuffer, fieldTypeBuffer, mockViewEntity, mockModelReader); + + assertEquals("MIN(OI.ESTIMATED_DELIVERY_DATE)", colNameBuffer.toString()); + } + + @Test + void makeAliasColNameSqlFunctionDefaultValueIsNotQuoted() { + // Oracle requires TO_TIMESTAMP(...) — a function expression must pass through unmodified + ModelField dateTimeField = ModelField.create(null, "estimatedDeliveryDate", "date-time", false); + when(mockViewEntity.getAliasedEntity("OI", mockModelReader)).thenReturn(mockModelEntity); + when(mockViewEntity.getAliasedField(mockModelEntity, "estimatedDeliveryDate", mockModelReader)).thenReturn(dateTimeField); + + ModelViewEntity.ComplexAliasField caf = new ModelViewEntity.ComplexAliasField( + "OI", "estimatedDeliveryDate", "TO_TIMESTAMP('2026-06-30','YYYY-MM-DD')", null); + + StringBuilder colNameBuffer = new StringBuilder(); + StringBuilder fieldTypeBuffer = new StringBuilder(); + caf.makeAliasColName(colNameBuffer, fieldTypeBuffer, mockViewEntity, mockModelReader); + + assertEquals("COALESCE(OI.ESTIMATED_DELIVERY_DATE,TO_TIMESTAMP('2026-06-30','YYYY-MM-DD'))", + colNameBuffer.toString()); + } }

