diff --git a/hsweb-commons/hsweb-commons-crud/pom.xml b/hsweb-commons/hsweb-commons-crud/pom.xml
index 8b53d6d44..3efcbec25 100644
--- a/hsweb-commons/hsweb-commons-crud/pom.xml
+++ b/hsweb-commons/hsweb-commons-crud/pom.xml
@@ -111,6 +111,12 @@
test
+
+ org.postgresql
+ r2dbc-postgresql
+ test
+
+
com.h2database
h2
@@ -166,4 +172,4 @@
-
\ No newline at end of file
+
diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/query/QueryAnalyzerImpl.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/query/QueryAnalyzerImpl.java
index 9d2ad6dd4..4c85cb18f 100644
--- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/query/QueryAnalyzerImpl.java
+++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/query/QueryAnalyzerImpl.java
@@ -78,7 +78,16 @@ public Select select() {
@Override
public Optional findColumn(String name) {
- return Optional.ofNullable(getColumnMappings().get(name));
+ if (name == null) {
+ return Optional.empty();
+ }
+ Map mappings = getColumnMappings();
+ Column column = mappings.get(name);
+ if (column == null) {
+ // 精确别名优先,仅在未命中时兼容实体属性名到数据库列名的转换。
+ column = mappings.get(QueryHelperUtils.toSnake(name));
+ }
+ return Optional.ofNullable(column);
}
@Override
@@ -177,7 +186,7 @@ private Column getColumnOrSelectColumn(String name) {
return column;
}
- return getColumnMappings().get(name);
+ return findColumn(name).orElse(null);
}
@SneakyThrows
@@ -652,7 +661,7 @@ public SqlFragments createTermFragments(QueryAnalyzerImpl impl, Term term) {
Table table = impl.select.table;
String column = term.getColumn();
- Column col = impl.getColumnMappings().get(column);
+ Column col = impl.findColumn(column).orElse(null);
//
// if (col == null) {
// if (column.contains(".")) {
@@ -697,9 +706,12 @@ public SqlFragments createTermFragments(QueryAnalyzerImpl impl, Term term) {
String colName = col.metadata != null ? col.metadata.getRealName() : col.name;
- String fullName = col.metadata != null
- ? col.getMetadata().getFullName(table.alias)
- : table.alias + "." + dialect.quote(colName, false);
+ // CTE和子查询会复用真实表列元数据,其内部可能已缓存原表全名,
+ // 条件列必须以当前查询分析出的表别名为准。
+ String fullName = dialect.buildColumnFullName(
+ table.alias,
+ colName,
+ col.metadata != null && !col.metadata.realNameDetected());
return metadata
.findFeature(createFeatureId(term.getTermType()))
diff --git a/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/query/QueryAnalyzerImplTest.java b/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/query/QueryAnalyzerImplTest.java
index 8a9f30999..590598620 100644
--- a/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/query/QueryAnalyzerImplTest.java
+++ b/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/query/QueryAnalyzerImplTest.java
@@ -1,12 +1,14 @@
package org.hswebframework.web.crud.query;
+import org.hswebframework.ezorm.core.param.Sort;
+import org.hswebframework.ezorm.core.param.Term;
import org.hswebframework.ezorm.rdb.executor.SqlRequest;
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
-import org.hswebframework.ezorm.core.param.Sort;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.crud.TestApplication;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -15,6 +17,7 @@
import reactor.test.StepVerifier;
import java.util.ArrayList;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@@ -24,6 +27,17 @@ public class QueryAnalyzerImplTest {
@Autowired
private DatabaseOperator database;
+ @Before
+ public void clearTestData() {
+ database
+ .sql()
+ .reactive()
+ .update("delete from s_test")
+ .then()
+ .as(StepVerifier::create)
+ .verifyComplete();
+ }
+
/**
* 执行SQL并验证是否有错误
*/
@@ -762,6 +776,15 @@ public void testNestedUnion() {
@Test
public void testMultipleCTE() {
+ database
+ .getMetadata()
+ .getCurrentSchema()
+ .getTableOrView("s_test", false)
+ .orElseThrow()
+ .getColumn("name")
+ .orElseThrow()
+ .getFullName();
+
QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
database,
"WITH " +
@@ -779,6 +802,7 @@ public void testMultipleCTE() {
assertNotNull(request.getSql(), "SQL should be generated");
assertNotNull(request.getParameters(), "Parameters should be set");
assertTrue(request.getSql().contains("), cte2 AS"), "multiple CTEs should be separated by comma");
+ assertTrue(request.getSql().contains("cte1.\"NAME\""), "filter column should use current CTE alias");
executeAndVerify(request);
}
@@ -1128,4 +1152,69 @@ public void testCustomSortFunctionFallbackToColumn() {
executeAndVerify(request);
}
+
+ @Test
+ public void testCamelCaseFilterColumnFallback() {
+ QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
+ database,
+ "select * from (select t.name as alarm_time from s_test t) a");
+
+ SqlRequest request = analyzer.refactor(
+ QueryParamEntity
+ .newQuery()
+ .and("alarmTime", "eq", "test")
+ .getParam());
+
+ assertTrue(analyzer.findColumn("alarmTime").isPresent());
+ assertTrue(request.getSql().contains("alarm_time"));
+ assertArrayEquals(new Object[]{"test"}, request.getParameters());
+ executeAndVerify(request);
+ }
+
+ @Test
+ public void testQualifiedCamelCaseNestedFilterColumnFallback() {
+ QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
+ database,
+ "select * from (select t.name as alarm_time from s_test t) a");
+ Term nested = new Term()
+ .nest()
+ .and("a.alarmTime", "eq", "test")
+ .clone();
+ QueryParamEntity param = QueryParamEntity.of();
+ param.setTerms(List.of(nested));
+
+ SqlRequest request = analyzer.refactor(param);
+
+ assertTrue(request.getSql().contains("alarm_time"));
+ assertArrayEquals(new Object[]{"test"}, request.getParameters());
+ executeAndVerify(request);
+ }
+
+ @Test
+ public void testExactFilterAliasTakesPriorityOverSnakeCaseFallback() {
+ QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
+ database,
+ "select * from (select t.name as alarm_time, t.age as alarmTime from s_test t) a");
+
+ QueryAnalyzer.Column column = analyzer.findColumn("alarmTime").orElseThrow();
+
+ assertEquals("alarmTime", column.getAlias());
+ }
+
+ @Test
+ public void testUnknownCamelCaseFilterColumnIsRejected() {
+ QueryAnalyzerImpl analyzer = new QueryAnalyzerImpl(
+ database,
+ "select * from (select t.name as alarm_time from s_test t) a");
+ QueryParamEntity param = QueryParamEntity
+ .newQuery()
+ .and("unknownAlarmTime", "eq", "test")
+ .getParam();
+
+ IllegalArgumentException error = assertThrows(
+ IllegalArgumentException.class,
+ () -> analyzer.refactor(param));
+
+ assertEquals("undefined column [unknownAlarmTime]", error.getMessage());
+ }
}