From aafd957b7f9a6d149c4b8e3144c3095995a33277 Mon Sep 17 00:00:00 2001 From: zhou-hao Date: Thu, 16 Jul 2026 12:38:32 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(crud):=20=E7=BB=9F=E4=B8=80=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E5=88=86=E6=9E=90=E5=99=A8=E5=88=97=E5=90=8D=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/crud/query/QueryAnalyzerImpl.java | 15 +++- .../web/crud/query/QueryAnalyzerImplTest.java | 69 ++++++++++++++++++- 2 files changed, 80 insertions(+), 4 deletions(-) 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..f24292c15 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(".")) { 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..b69bb78c5 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,9 +1,10 @@ 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; @@ -15,6 +16,7 @@ import reactor.test.StepVerifier; import java.util.ArrayList; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -1128,4 +1130,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()); + } } From 1a56dd002afcfbb53eefb100193b8a011a775f87 Mon Sep 17 00:00:00 2001 From: zhou-hao Date: Thu, 16 Jul 2026 14:05:16 +0800 Subject: [PATCH 2/3] =?UTF-8?q?test(crud):=20=E8=A1=A5=E9=BD=90=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E6=B5=8B=E8=AF=95=E8=BF=90=E8=A1=8C=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hsweb-commons/hsweb-commons-crud/pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 + From f91d2f48d20aa28c4b71e275c643bf07f1b99715 Mon Sep 17 00:00:00 2001 From: zhou-hao Date: Thu, 16 Jul 2026 14:06:02 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(crud):=20=E4=BF=AE=E5=A4=8D=20CTE=20?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6=E5=88=97=E5=88=AB=E5=90=8D=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/crud/query/QueryAnalyzerImpl.java | 9 +++++--- .../web/crud/query/QueryAnalyzerImplTest.java | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) 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 f24292c15..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 @@ -706,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 b69bb78c5..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 @@ -8,6 +8,7 @@ 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; @@ -26,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并验证是否有错误 */ @@ -764,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 " + @@ -781,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); }