diff --git a/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/PostgresqlSchemaMetadata.java b/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/PostgresqlSchemaMetadata.java index 8649edd6..98d6dd63 100644 --- a/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/PostgresqlSchemaMetadata.java +++ b/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/PostgresqlSchemaMetadata.java @@ -28,6 +28,12 @@ public PostgresqlSchemaMetadata(String name) { .add(FeatureUtils.r2dbcIsAlive(), () -> PostgresqlR2DBCExceptionTranslation.of(this)) ); + ValueByTimeFunctionFragmentBuilder last = new ValueByTimeFunctionFragmentBuilder("last", "最后一值"); + ValueByTimeFunctionFragmentBuilder first = new ValueByTimeFunctionFragmentBuilder("first", "第一个值"); + + addFeature(last); + addFeature(first); + addFeature((ValueCodecFactory) column -> { if(column.getType() instanceof ValueCodec){ return Optional.of( diff --git a/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilder.java b/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilder.java new file mode 100644 index 00000000..400c5612 --- /dev/null +++ b/hsweb-easy-orm-rdb/src/main/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilder.java @@ -0,0 +1,79 @@ +package org.hswebframework.ezorm.rdb.supports.postgres; + +import com.google.common.collect.Lists; +import lombok.Getter; +import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.BatchSqlFragments; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.EmptySqlFragments; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.function.FunctionFragmentBuilder; + +import java.sql.JDBCType; +import java.sql.SQLType; +import java.util.List; +import java.util.Map; + +@Getter +public class ValueByTimeFunctionFragmentBuilder implements FunctionFragmentBuilder { + + //必须是 TIMESTAMP(时间戳)或 INTEGER(整数)类型 字段 + public static final String TIME_COLUMN = "time"; + + private final String function; + + private final String name; + + private final SqlFragments FUNCTION; + + private static final List timeTypes = Lists.newArrayList(JDBCType.TIMESTAMP, JDBCType.DATE, JDBCType.TIME, + JDBCType.TIME_WITH_TIMEZONE, JDBCType.TIMESTAMP_WITH_TIMEZONE); + + public ValueByTimeFunctionFragmentBuilder(String function, String alias, String name) { + this.function = function; + this.name = name; + FUNCTION = SqlFragments.single(alias + "("); + } + + public ValueByTimeFunctionFragmentBuilder(String function, String name) { + this.function = function; + this.name = name; + FUNCTION = SqlFragments.single(function + "("); + } + + + @Override + public SqlFragments create(String columnFullName, RDBColumnMetadata metadata, Map opts) { + String table = metadata.getOwner().getName(); + String fullTimeColumn = null; + if (opts != null && opts.containsKey(TIME_COLUMN)) { + String timeColumn = (String) opts.get(TIME_COLUMN); + fullTimeColumn = metadata.getDialect().buildColumnFullName(table, timeColumn); + } else { + //优先获取时间类型字段、其次获取数字类型字段 + RDBColumnMetadata numberBack = null; + for (RDBColumnMetadata column : metadata.getOwner().getColumns()) { + if (timeTypes.contains(column.getSqlType())) { + fullTimeColumn = column.getFullName(); + break; + } + if (numberBack == null && column.getJavaType() != null && Number.class.isAssignableFrom(column.getJavaType())) { + numberBack = column; + } + } + if (fullTimeColumn == null && numberBack != null) { + fullTimeColumn = numberBack.getFullName(); + } + } + if (fullTimeColumn == null) { + throw new IllegalArgumentException("No time columns"); + } + if (columnFullName == null) { + return EmptySqlFragments.INSTANCE; + } + return new BatchSqlFragments(2, 0) + .add(FUNCTION) + .addSql(columnFullName, ",", fullTimeColumn, ")"); + } + + +} diff --git a/hsweb-easy-orm-rdb/src/test/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilderTest.java b/hsweb-easy-orm-rdb/src/test/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilderTest.java new file mode 100644 index 00000000..ef377a83 --- /dev/null +++ b/hsweb-easy-orm-rdb/src/test/java/org/hswebframework/ezorm/rdb/supports/postgres/ValueByTimeFunctionFragmentBuilderTest.java @@ -0,0 +1,110 @@ +package org.hswebframework.ezorm.rdb.supports.postgres; + +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.RDBSchemaMetadata; +import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata; +import org.hswebframework.ezorm.rdb.metadata.dialect.Dialect; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.EmptySqlFragments; +import org.hswebframework.ezorm.rdb.operator.builder.fragments.SqlFragments; +import org.junit.Assert; +import org.junit.Test; + +import java.sql.JDBCType; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; + +public class ValueByTimeFunctionFragmentBuilderTest { + + @Test + public void shouldPreferDetectedTimeColumn() { + RDBTableMetadata table = createTable("metrics"); + RDBColumnMetadata valueColumn = addColumn(table, "value", JDBCType.BIGINT, Long.class); + RDBColumnMetadata timeColumn = addColumn(table, "event_time", JDBCType.TIMESTAMP, Date.class); + + ValueByTimeFunctionFragmentBuilder builder = new ValueByTimeFunctionFragmentBuilder("first", "第一个值"); + SqlFragments fragments = builder.create(valueColumn.getFullName(), valueColumn, Collections.emptyMap()); + + Assert.assertEquals( + Arrays.asList( + "first(", + valueColumn.getFullName(), + ",", + timeColumn.getFullName(), + ")" + ), + fragments.getSql() + ); + } + + @Test + public void shouldFallbackToNumberColumnWhenNoTimeColumn() { + RDBTableMetadata table = createTable("metrics"); + RDBColumnMetadata nameColumn = addColumn(table, "name", JDBCType.VARCHAR, String.class); + RDBColumnMetadata numberColumn = addColumn(table, "sequence", JDBCType.BIGINT, Long.class); + + ValueByTimeFunctionFragmentBuilder builder = new ValueByTimeFunctionFragmentBuilder("last", "最后一值"); + SqlFragments fragments = builder.create(nameColumn.getFullName(), nameColumn, Collections.emptyMap()); + + Assert.assertEquals( + Arrays.asList( + "last(", + nameColumn.getFullName(), + ",", + numberColumn.getFullName(), + ")" + ), + fragments.getSql() + ); + } + + @Test + public void shouldReturnEmptyFragmentsWhenColumnFullNameIsNull() { + RDBTableMetadata table = createTable("metrics"); + RDBColumnMetadata valueColumn = addColumn(table, "value", JDBCType.DOUBLE, Double.class); + addColumn(table, "event_time", JDBCType.TIMESTAMP, Date.class); + + ValueByTimeFunctionFragmentBuilder builder = new ValueByTimeFunctionFragmentBuilder("last", "最后一值"); + + Assert.assertSame(EmptySqlFragments.INSTANCE, builder.create(null, valueColumn, Collections.emptyMap())); + } + + @Test + public void shouldThrowWhenNoTimeOrNumberColumnExists() { + RDBTableMetadata table = createTable("metrics"); + RDBColumnMetadata textColumn = addColumn(table, "name", JDBCType.VARCHAR, String.class); + + IllegalArgumentException error = Assert.assertThrows( + IllegalArgumentException.class, + () -> new ValueByTimeFunctionFragmentBuilder("last", "最后一值") + .create(textColumn.getFullName(), textColumn, Collections.emptyMap()) + ); + + Assert.assertEquals("No time columns", error.getMessage()); + } + + private RDBTableMetadata createTable(String name) { + RDBSchemaMetadata schema = createSchema(); + RDBTableMetadata table = schema.newTable(name); + schema.addTable(table); + return table; + } + + private RDBSchemaMetadata createSchema() { + RDBDatabaseMetadata database = new RDBDatabaseMetadata(Dialect.POSTGRES); + RDBSchemaMetadata schema = new PostgresqlSchemaMetadata("public"); + database.addSchema(schema); + database.setCurrentSchema(schema); + return schema; + } + + private RDBColumnMetadata addColumn(RDBTableMetadata table, String name, JDBCType jdbcType, Class javaType) { + RDBColumnMetadata column = table.newColumn(); + column.setName(name); + column.setType(JdbcDataType.of(jdbcType, javaType)); + table.addColumn(column); + return column; + } +}