Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ List<Map<String,Object>> dataList= operator.dml().query()
# 使用

建议配合[hsweb4](https://github.com/hs-web/hsweb-framework/tree/4.0.x)使用.

## 聚合计数

内置 `count` 函数默认生成 `count(列)`。查询方可通过 `SelectColumn.option("countRows", true)` 显式请求行数计数;表元数据确认目标列非空时生成 `count(*)`,此时 `countRows` 优先于 `distinct`、`arg` 等选项,其他情况沿用原有生成逻辑。调用方需确保关联查询不会将目标列扩展为 NULL。自定义 `count` 函数仍按其自身实现处理该选项;旧版本不识别 `countRows` 时继续按原有选项生成 SQL。实际性能取决于索引和执行计划。
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.hswebframework.ezorm.core.FeatureId;
import org.hswebframework.ezorm.core.param.TermType;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.function.CountFunctionFragmentBuilder;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.function.SimpleFunctionFragmentBuilder;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.query.QuerySqlBuilder;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.query.QuerySqlFragmentBuilder;
Expand Down Expand Up @@ -51,7 +52,7 @@ public interface RDBFeatures {
/*===============函数==============*/

/*==聚合函数==*/
SimpleFunctionFragmentBuilder count = new SimpleFunctionFragmentBuilder("count", "计数");
SimpleFunctionFragmentBuilder count = new CountFunctionFragmentBuilder();
SimpleFunctionFragmentBuilder sum = new SimpleFunctionFragmentBuilder("sum", "求和");
SimpleFunctionFragmentBuilder avg = new SimpleFunctionFragmentBuilder("avg", "平均值");
SimpleFunctionFragmentBuilder max = new SimpleFunctionFragmentBuilder("max", "最大值");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.hswebframework.ezorm.rdb.operator.builder.fragments.function;

import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments;

import java.util.Map;

/**
* 内置 count 函数。调用方明确启用 countRows,且查询不会使非空列因关联变为 NULL 时,
* 可以将列计数生成为行数计数,并优先于 distinct、arg 等选项;其他情况沿用普通 count 的行为。
*/
public class CountFunctionFragmentBuilder extends SimpleFunctionFragmentBuilder {

public static final String COUNT_ROWS = "countRows";

public CountFunctionFragmentBuilder() {
super("count", "计数");
}

@Override
public SqlFragments create(String columnFullName, RDBColumnMetadata metadata, Map<String, Object> opts) {
if (columnFullName != null
&& metadata != null
&& metadata.isNotNull()
&& opts != null
&& Boolean.TRUE.equals(opts.get(COUNT_ROWS))) {
return SqlFragments.single("count(*)");
}
return super.create(columnFullName, metadata, opts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package org.hswebframework.ezorm.rdb.operator.builder.fragments.function;

import org.hswebframework.ezorm.rdb.metadata.JdbcDataType;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBDatabaseMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBFeatures;
import org.hswebframework.ezorm.rdb.metadata.RDBSchemaMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata;
import org.hswebframework.ezorm.rdb.metadata.dialect.Dialect;
import org.hswebframework.ezorm.rdb.operator.DefaultDatabaseOperator;
import org.hswebframework.ezorm.rdb.operator.dml.query.SelectColumn;
import org.hswebframework.ezorm.rdb.supports.postgres.PostgresqlSchemaMetadata;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.sql.JDBCType;
import java.util.Collections;

public class CountFunctionFragmentBuilderTest {

private RDBDatabaseMetadata database;
private PostgresqlSchemaMetadata schema;
private RDBTableMetadata table;

@Before
public void init() {
database = new RDBDatabaseMetadata(Dialect.POSTGRES);
schema = new PostgresqlSchemaMetadata("public");
database.addSchema(schema);
database.setCurrentSchema(schema);

table = schema.newTable("metrics");
addColumn(table, "id", true);
addColumn(table, "value", false);
schema.addTable(table);
}

@Test
public void shouldOnlyRewriteRequestedNonNullCount() {
String normalSql = querySql(count("id", "total"));
Assert.assertTrue(normalSql, normalSql.contains("count( " + table.getColumnNow("id").getFullName() + " ) as \"total\""));
Assert.assertFalse(normalSql, normalSql.contains("count(*)"));

SelectColumn countRows = count("id", "total");
countRows.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
String rowsSql = querySql(countRows);
Assert.assertTrue(rowsSql, rowsSql.contains("count(*) as \"total\""));
}

@Test
public void shouldKeepNullableColumnCount() {
SelectColumn countRows = count("value", "total");
countRows.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);

String sql = querySql(countRows);
Assert.assertTrue(sql, sql.contains("count( " + table.getColumnNow("value").getFullName() + " ) as \"total\""));
Assert.assertFalse(sql, sql.contains("count(*)"));
}

@Test
public void shouldRequireEnabledOptionAndResolvedColumn() {
SelectColumn disabled = count("id", "total");
disabled.option(CountFunctionFragmentBuilder.COUNT_ROWS, false);
String disabledSql = querySql(disabled);
Assert.assertTrue(disabledSql, disabledSql.contains("count( " + table.getColumnNow("id").getFullName() + " )"));

Assert.assertTrue(RDBFeatures.count
.create(null,
table.getColumnNow("id"),
Collections.singletonMap(CountFunctionFragmentBuilder.COUNT_ROWS, true))
.isEmpty());
}

@Test
public void shouldPreferCountRowsOverDistinctAndArg() {
SelectColumn distinct = count("id", "total");
distinct.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
distinct.option("distinct", true);
String distinctSql = querySql(distinct);
Assert.assertTrue(distinctSql, distinctSql.contains("count(*) as \"total\""));

SelectColumn arg = count("id", "total");
arg.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
arg.option("arg", 1);
String argSql = querySql(arg);
Assert.assertTrue(argSql, argSql.contains("count(*) as \"total\""));
}

@Test
public void shouldKeepDistinctAndArgWithoutCountRows() {
SelectColumn distinct = count("id", "total");
distinct.option("distinct", true);
String distinctSql = querySql(distinct);
Assert.assertTrue(distinctSql, distinctSql.contains("count( distinct " + table.getColumnNow("id").getFullName() + " )"));

SelectColumn arg = count("id", "total");
arg.option("arg", 1);
String argSql = querySql(arg);
Assert.assertTrue(argSql, argSql.contains("count( 1 ) as \"total\""));
}

@Test
public void shouldUseOriginalOptionsForNullableColumn() {
SelectColumn distinct = count("value", "total");
distinct.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
distinct.option("distinct", true);
String distinctSql = querySql(distinct);
Assert.assertTrue(distinctSql, distinctSql.contains("count( distinct " + table.getColumnNow("value").getFullName() + " )"));

SelectColumn arg = count("value", "total");
arg.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
arg.option("arg", 1);
String argSql = querySql(arg);
Assert.assertTrue(argSql, argSql.contains("count( 1 ) as \"total\""));
}

@Test
public void shouldRespectCustomCountFunction() {
schema.addFeature(new SimpleFunctionFragmentBuilder("count", "schema_count", "自定义计数"));
SelectColumn countRows = count("id", "total");
countRows.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
String schemaSql = querySql(countRows);
Assert.assertTrue(schemaSql, schemaSql.contains("schema_count( " + table.getColumnNow("id").getFullName() + " )"));

table.addFeature(new SimpleFunctionFragmentBuilder("count", "table_count", "自定义计数"));
String tableSql = querySql(countRows);
Assert.assertTrue(tableSql, tableSql.contains("table_count( " + table.getColumnNow("id").getFullName() + " )"));
}

@Test
public void shouldKeepLegacyBuilderBehaviorForUnknownOption() {
schema.addFeature(new SimpleFunctionFragmentBuilder("count", "计数"));
SelectColumn countRows = count("id", "total");
countRows.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);

String sql = querySql(countRows);
Assert.assertTrue(sql, sql.contains("count( " + table.getColumnNow("id").getFullName() + " )"));
Assert.assertFalse(sql, sql.contains("count(*)"));
}

@Test
public void shouldKeepOptInBehaviorForOtherDialects() {
RDBDatabaseMetadata h2Database = new RDBDatabaseMetadata(Dialect.H2);
RDBSchemaMetadata h2Schema = new RDBSchemaMetadata("PUBLIC");
h2Database.addSchema(h2Schema);
h2Database.setCurrentSchema(h2Schema);
RDBTableMetadata h2Table = h2Schema.newTable("metrics");
addColumn(h2Table, "id", true);
h2Schema.addTable(h2Table);

String normalSql = DefaultDatabaseOperator.of(h2Database)
.dml()
.query("metrics")
.select(count("id", "total"))
.getSql()
.getSql();
Assert.assertTrue(normalSql, normalSql.contains("count( " + h2Table.getColumnNow("id").getFullName() + " )"));

SelectColumn countRows = count("id", "total");
countRows.option(CountFunctionFragmentBuilder.COUNT_ROWS, true);
String rowsSql = DefaultDatabaseOperator.of(h2Database)
.dml()
.query("metrics")
.select(countRows)
.getSql()
.getSql();
Assert.assertTrue(rowsSql, rowsSql.contains("count(*)"));
}

private String querySql(SelectColumn column) {
return DefaultDatabaseOperator.of(database)
.dml()
.query("metrics")
.select(column)
.getSql()
.getSql();
}

private SelectColumn count(String property, String alias) {
SelectColumn column = SelectColumn.of(property, alias);
column.setFunction("count");
return column;
}

private void addColumn(RDBTableMetadata target, String name, boolean notNull) {
RDBColumnMetadata column = target.newColumn();
column.setName(name);
column.setType(JdbcDataType.of(JDBCType.VARCHAR, String.class));
column.setNotNull(notNull);
target.addColumn(column);
}
}
Loading