io.deephaven.engine.table.impl.select.ReinterpretedColumn Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of deephaven-engine-table Show documentation
Show all versions of deephaven-engine-table Show documentation
Engine Table: Implementation and closely-coupled utilities
/**
* Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
*/
package io.deephaven.engine.table.impl.select;
import io.deephaven.base.verify.Assert;
import io.deephaven.base.verify.Require;
import io.deephaven.engine.table.ColumnDefinition;
import io.deephaven.engine.table.Table;
import io.deephaven.engine.table.impl.MatchPair;
import io.deephaven.api.util.NameValidator;
import io.deephaven.engine.table.impl.NoSuchColumnException;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.WritableColumnSource;
import io.deephaven.engine.rowset.TrackingRowSet;
import io.deephaven.engine.table.impl.sources.ConvertibleTimeSource;
import io.deephaven.engine.table.impl.sources.LocalDateWrapperSource;
import io.deephaven.engine.table.impl.sources.LocalTimeWrapperSource;
import io.deephaven.engine.table.impl.sources.LongAsInstantColumnSource;
import io.deephaven.engine.table.impl.sources.LongAsLocalDateColumnSource;
import io.deephaven.engine.table.impl.sources.LongAsLocalTimeColumnSource;
import io.deephaven.engine.table.impl.sources.LongAsZonedDateTimeColumnSource;
import io.deephaven.engine.table.impl.sources.ReinterpretUtils;
import io.deephaven.engine.table.impl.util.TableTimeConversions;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Allows {@link ColumnSource} reinterpretation via view-type ({@link Table#view} and {@link Table#updateView})
* {@link Table} operations.
*
* TODO: If we come up with other valid, useful reinterpretations, it would be trivial to create a general purpose
* syntax for use in view()/updateView() column expressions.
*
* The syntax I have in mind is: "<ColumnNameB>=<ColumnNameA>.as(<ClassName>)"
* "<ColumnName>.as(<ClassName>)"
*
* Making this work would consist of any one of: 1. Adding a V1 version and updating SelectColumnFactory and
* SelectColumnAdaptor 2. Adding the appropriate if-regex-matches to realColumn selection in V2 SwitchColumn 3. Creating
* a V2-native SelectColumnFactory
*/
public class ReinterpretedColumn implements SelectColumn {
@NotNull
private final String sourceName;
@NotNull
private final Class sourceDataType;
@NotNull
private final String destName;
@NotNull
private final Class destDataType;
private final Object[] reinterpParams;
private ColumnSource sourceColumnSource;
private ZoneId zone;
/**
* Create a {@link ReinterpretedColumn} that attempts to convert the source column into the destination type,
* optionally with parameters.
*
* @param sourceName the name of the Source column within the table
* @param sourceDataType the type of the source column
* @param destName the name of the desired destination column
* @param destDataType the type to try to convert to
* @param reinterpParams a varargs set of parameters for the arguments if required.
*/
public ReinterpretedColumn(
@NotNull String sourceName,
@NotNull Class sourceDataType,
@NotNull String destName,
@NotNull Class destDataType,
Object... reinterpParams) {
Assert.gtZero(destName.length(), "destName.length()");
this.sourceName = NameValidator.validateColumnName(sourceName);
this.sourceDataType = Require.neqNull(sourceDataType, "sourceDataType");
this.destName = NameValidator.validateColumnName(destName);
this.destDataType = Require.neqNull(destDataType, "destDataType");
this.reinterpParams = reinterpParams;
}
@Override
public String toString() {
return "reinterpretAs(" + sourceName + ',' + destName + ')';
}
@Override
public List initInputs(TrackingRowSet rowSet, Map> columnsOfInterest) {
// noinspection unchecked
final ColumnSource localSourceColumnSource = (ColumnSource) columnsOfInterest.get(sourceName);
if (localSourceColumnSource == null) {
throw new NoSuchColumnException(columnsOfInterest.keySet(), sourceName);
}
if (!localSourceColumnSource.getType().equals(sourceDataType)) {
throw new IllegalArgumentException("Source column " + sourceName + " has wrong data type "
+ localSourceColumnSource.getType() + ", expected " + sourceDataType);
}
if (!(localSourceColumnSource.allowsReinterpret(destDataType))) {
if (TableTimeConversions.requiresZone(destDataType)) {
if (reinterpParams == null || reinterpParams.length != 1 || !(reinterpParams[0] instanceof ZoneId)) {
throw new IllegalArgumentException("Incorrect arguments for ZonedDateTime conversion");
}
zone = (ZoneId) reinterpParams[0];
} else {
zone = null;
}
final boolean isDestTypeTimeIsh =
destDataType == ZonedDateTime.class
|| destDataType == LocalDate.class
|| destDataType == LocalTime.class
|| destDataType == Instant.class
|| destDataType == long.class
|| destDataType == Long.class;
if (localSourceColumnSource instanceof ConvertibleTimeSource
&& ((ConvertibleTimeSource) localSourceColumnSource).supportsTimeConversion()) {
if (!isDestTypeTimeIsh) {
throw new IllegalArgumentException(
"Source column " + sourceName + " (Class=" + localSourceColumnSource.getClass()
+ ") - cannot be reinterpreted as " + destDataType);
}
} else if (sourceDataType == Instant.class
|| sourceDataType == ZonedDateTime.class
|| sourceDataType == long.class
|| sourceDataType == Long.class) {
if (!isDestTypeTimeIsh) {
throw new IllegalArgumentException(
"Source column " + sourceName + " (Class=" + localSourceColumnSource.getClass()
+ ") - cannot be reinterpreted as " + destDataType);
}
} else {
throw new IllegalArgumentException("Source column " + sourceName + " (Class="
+ localSourceColumnSource.getClass() + ") - cannot be reinterpreted as " + destDataType);
}
}
sourceColumnSource = localSourceColumnSource;
return getColumns();
}
@Override
public List initDef(Map> columnDefinitionMap) {
// noinspection unchecked
final ColumnDefinition sourceColumnDefinition = (ColumnDefinition) columnDefinitionMap.get(sourceName);
if (sourceColumnDefinition == null) {
throw new NoSuchColumnException(columnDefinitionMap.keySet(), sourceName);
}
if (!sourceColumnDefinition.getDataType().equals(sourceDataType)) {
throw new IllegalArgumentException("Source column " + sourceName + " has wrong data type "
+ sourceColumnDefinition.getDataType() + ", expected " + sourceDataType);
}
return getColumns();
}
@Override
public Class> getReturnedType() {
return destDataType;
}
@Override
public List getColumns() {
return Collections.singletonList(sourceName);
}
@Override
public List getColumnArrays() {
return Collections.emptyList();
}
@NotNull
@Override
public ColumnSource getDataView() {
final Function, ColumnSource> checkResult = result -> {
if (!result.getType().equals(destDataType)) {
throw new IllegalArgumentException("Reinterpreted column from " + sourceName + " has wrong data type "
+ result.getType() + ", expected " + destDataType);
}
// noinspection unchecked
return (ColumnSource) result;
};
if (sourceColumnSource.allowsReinterpret(destDataType)) {
return checkResult.apply(sourceColumnSource.reinterpret(destDataType));
}
// The only other conversions we will do are various time permutations.
// If we can just reinterpret as time, great!
if (sourceColumnSource instanceof ConvertibleTimeSource &&
((ConvertibleTimeSource) sourceColumnSource).supportsTimeConversion()) {
if (destDataType == ZonedDateTime.class) {
return checkResult.apply(((ConvertibleTimeSource) sourceColumnSource).toZonedDateTime(zone));
} else if (destDataType == LocalDate.class) {
return checkResult.apply(((ConvertibleTimeSource) sourceColumnSource).toLocalDate(zone));
} else if (destDataType == LocalTime.class) {
return checkResult.apply(((ConvertibleTimeSource) sourceColumnSource).toLocalTime(zone));
} else if (destDataType == Instant.class) {
return checkResult.apply(((ConvertibleTimeSource) sourceColumnSource).toInstant());
} else if (destDataType == long.class || destDataType == Long.class) {
return checkResult.apply(((ConvertibleTimeSource) sourceColumnSource).toEpochNano());
}
}
if (sourceDataType == ZonedDateTime.class &&
(destDataType == LocalDate.class || destDataType == LocalTime.class)) {
// We can short circuit some ZDT conversions to try to be less wasteful
if (destDataType == LocalDate.class) {
// noinspection unchecked
return checkResult.apply(new LocalDateWrapperSource(
(ColumnSource) sourceColumnSource, zone));
} else {
// noinspection unchecked
return checkResult.apply(new LocalTimeWrapperSource(
(ColumnSource) sourceColumnSource, zone));
}
}
// If we just want to go from X to long, this is fairly straightforward. Note that we skip LocalDate and
// LocalTime these are not linked to nanos of epoch in any way. You could argue that LocalDate is, but then
// we have to create even more garbage objects just to get the "time at midnight". Users should just do that
// directly.
final Function, ColumnSource> toLong;
if (sourceDataType == Instant.class) {
// noinspection unchecked
toLong = s -> ReinterpretUtils.instantToLongSource((ColumnSource) s);
} else if (sourceDataType == ZonedDateTime.class) {
// noinspection unchecked
toLong = s -> ReinterpretUtils.zonedDateTimeToLongSource((ColumnSource) s);
} else if (sourceDataType == long.class || sourceDataType == Long.class) {
// noinspection unchecked
toLong = s -> (ColumnSource) sourceColumnSource;
} else {
throw new IllegalArgumentException("Source column " + sourceName + " (Class="
+ sourceColumnSource.getClass() + ") - cannot be reinterpreted as " + destDataType);
}
// Otherwise we'll have to go from long back to a wrapped typed source.
if (destDataType == Long.class || destDataType == long.class) {
return checkResult.apply(toLong.apply(sourceColumnSource));
} else if (destDataType == ZonedDateTime.class) {
return checkResult.apply(new LongAsZonedDateTimeColumnSource(toLong.apply(sourceColumnSource), zone));
} else if (destDataType == Instant.class) {
return checkResult.apply(new LongAsInstantColumnSource(toLong.apply(sourceColumnSource)));
} else if (destDataType == LocalDate.class) {
return checkResult.apply(new LongAsLocalDateColumnSource(toLong.apply(sourceColumnSource), zone));
} else if (destDataType == LocalTime.class) {
return checkResult.apply(new LongAsLocalTimeColumnSource(toLong.apply(sourceColumnSource), zone));
}
throw new IllegalArgumentException("Source column " + sourceName + " (Class=" + sourceColumnSource.getClass()
+ ") - cannot be reinterpreted as " + destDataType);
}
@NotNull
@Override
public ColumnSource getLazyView() {
return getDataView();
}
@Override
public String getName() {
return destName;
}
@Override
public MatchPair getMatchPair() {
throw new UnsupportedOperationException();
}
@Override
public WritableColumnSource> newDestInstance(long size) {
throw new UnsupportedOperationException("ReinterpretedColumn should only be used with updateView() clauses.");
}
@Override
public WritableColumnSource> newFlatDestInstance(long size) {
throw new UnsupportedOperationException();
}
@Override
public boolean isRetain() {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ReinterpretedColumn, ?> that = (ReinterpretedColumn, ?>) o;
return sourceName.equals(that.sourceName) && sourceDataType.equals(that.sourceDataType)
&& destName.equals(that.destName) && destDataType.equals(that.destDataType);
}
@Override
public int hashCode() {
int result = sourceName.hashCode();
result = 31 * result + sourceDataType.hashCode();
result = 31 * result + destName.hashCode();
result = 31 * result + destDataType.hashCode();
result = 31 * result + Arrays.hashCode(reinterpParams);
return result;
}
@Override
public boolean isStateless() {
return sourceColumnSource.isStateless();
}
@Override
public ReinterpretedColumn copy() {
return new ReinterpretedColumn<>(sourceName, sourceDataType, destName, destDataType, reinterpParams);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy