io.deephaven.engine.table.impl.TimeTable 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;
import io.deephaven.base.clock.Clock;
import io.deephaven.chunk.LongChunk;
import io.deephaven.chunk.WritableChunk;
import io.deephaven.chunk.WritableLongChunk;
import io.deephaven.chunk.WritableObjectChunk;
import io.deephaven.chunk.attributes.Values;
import io.deephaven.engine.context.ExecutionContext;
import io.deephaven.engine.rowset.RowSequence;
import io.deephaven.engine.rowset.RowSet;
import io.deephaven.engine.rowset.RowSetBuilderRandom;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.rowset.WritableRowSet;
import io.deephaven.engine.rowset.chunkattributes.RowKeys;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.Table;
import io.deephaven.engine.table.impl.sources.FillUnordered;
import io.deephaven.engine.updategraph.UpdateSourceRegistrar;
import io.deephaven.engine.util.TableTools;
import io.deephaven.function.Numeric;
import io.deephaven.util.QueryConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import static io.deephaven.time.DateTimeUtils.currentClock;
import static io.deephaven.time.DateTimeUtils.epochNanos;
import static io.deephaven.time.DateTimeUtils.epochNanosToInstant;
import static io.deephaven.time.DateTimeUtils.isBefore;
import static io.deephaven.time.DateTimeUtils.minus;
import static io.deephaven.time.DateTimeUtils.parseInstant;
import static io.deephaven.time.DateTimeUtils.parseDurationNanos;
import static io.deephaven.time.DateTimeUtils.plus;
import static io.deephaven.util.type.TypeUtils.box;
/**
* A TimeTable adds rows at a fixed interval with a single column named "Timestamp".
*
* To create a TimeTable, you should use the {@link TableTools#timeTable} family of methods.
*
* @implNote The constructor publishes {@code this} to the {@link UpdateSourceRegistrar} and thus cannot be subclassed.
*/
public final class TimeTable extends QueryTable implements Runnable {
public static class Builder {
private UpdateSourceRegistrar registrar = ExecutionContext.getContext().getUpdateGraph();
private Clock clock;
private Instant startTime;
private long period;
private boolean blinkTable;
public Builder registrar(UpdateSourceRegistrar registrar) {
this.registrar = registrar;
return this;
}
public Builder clock(Clock clock) {
this.clock = clock;
return this;
}
public Builder startTime(Instant startTime) {
this.startTime = startTime;
return this;
}
public Builder startTime(String startTime) {
this.startTime = parseInstant(startTime);
return this;
}
public Builder period(Duration period) {
return period(period.toNanos());
}
public Builder period(long period) {
this.period = period;
return this;
}
public Builder period(String period) {
return period(parseDurationNanos(period));
}
public Builder blinkTable(boolean blinkTable) {
this.blinkTable = blinkTable;
return this;
}
public QueryTable build() {
return new TimeTable(registrar,
Objects.requireNonNullElse(clock, currentClock()),
startTime, period, blinkTable);
}
}
public static Builder newBuilder() {
return new Builder();
}
private static final String TIMESTAMP = "Timestamp";
private long lastIndex = -1;
private final SyntheticInstantSource columnSource;
private final Clock clock;
private final String name;
private final boolean isBlinkTable;
private final UpdateSourceRegistrar registrar;
private final SourceRefresher refresher;
public TimeTable(
UpdateSourceRegistrar registrar,
Clock clock,
@Nullable Instant startTime,
long period,
boolean isBlinkTable) {
super(RowSetFactory.empty().toTracking(), initColumn(startTime, period));
this.registrar = registrar;
this.isBlinkTable = isBlinkTable;
this.name = (isBlinkTable ? "TimeTableBlink" : "TimeTable") + "(" + startTime + "," + period + ")";
columnSource = (SyntheticInstantSource) getColumnSourceMap().get(TIMESTAMP);
this.clock = clock;
if (isBlinkTable) {
setAttribute(Table.BLINK_TABLE_ATTRIBUTE, Boolean.TRUE);
} else {
setAttribute(Table.ADD_ONLY_TABLE_ATTRIBUTE, Boolean.TRUE);
setAttribute(Table.APPEND_ONLY_TABLE_ATTRIBUTE, Boolean.TRUE);
setFlat();
}
refresher = new SourceRefresher();
if (startTime != null) {
refresh(false);
}
setRefreshing(true);
registrar.addSource(refresher);
}
private static Map> initColumn(Instant firstTime, long period) {
if (period <= 0) {
throw new IllegalArgumentException("Invalid time period: " + period + " nanoseconds");
}
return Collections.singletonMap(TIMESTAMP, new SyntheticInstantSource(firstTime, period));
}
@Override
public void run() {
refresh(true);
}
private class SourceRefresher extends InstrumentedTableUpdateSource {
public SourceRefresher() {
super(registrar, TimeTable.this, name);
}
@Override
protected void instrumentedRefresh() {
refresh(true);
}
}
private void refresh(final boolean notifyListeners) {
final Instant now = clock.instantNanos();
long rangeStart = lastIndex + 1;
if (columnSource.startTime == null) {
lastIndex = 0;
columnSource.startTime = epochNanosToInstant(
Numeric.lowerBin(epochNanos(now), columnSource.period));
} else if (now.compareTo(columnSource.startTime) >= 0) {
lastIndex = Math.max(lastIndex,
minus(now, columnSource.startTime) / columnSource.period);
}
final boolean rowsAdded = rangeStart <= lastIndex;
final boolean rowsRemoved = isBlinkTable && getRowSet().isNonempty();
if (rowsAdded || rowsRemoved) {
final RowSet addedRange = rowsAdded
? RowSetFactory.fromRange(rangeStart, lastIndex)
: RowSetFactory.empty();
final RowSet removedRange = rowsRemoved
? RowSetFactory.fromRange(getRowSet().firstRowKey(), rangeStart - 1)
: RowSetFactory.empty();
if (rowsAdded) {
getRowSet().writableCast().insertRange(rangeStart, lastIndex);
}
if (rowsRemoved) {
getRowSet().writableCast().removeRange(0, rangeStart - 1);
}
if (notifyListeners) {
notifyListeners(addedRange, removedRange, RowSetFactory.empty());
}
}
}
@Override
protected void destroy() {
super.destroy();
registrar.removeSource(refresher);
}
private static final class SyntheticInstantSource extends AbstractColumnSource implements
ImmutableColumnSourceGetDefaults.LongBacked,
FillUnordered {
private Instant startTime;
private final long period;
private SyntheticInstantSource(Instant startTime, long period) {
super(Instant.class);
this.startTime = startTime;
this.period = period;
}
private Instant computeInstant(long rowKey) {
return plus(startTime, period * rowKey);
}
@Override
public Instant get(long rowKey) {
if (rowKey < 0) {
return null;
}
return computeInstant(rowKey);
}
private long computeNanos(long rowKey) {
return epochNanos(startTime) + period * rowKey;
}
@Override
public long getLong(long rowKey) {
if (rowKey < 0) {
return QueryConstants.NULL_LONG;
}
return computeNanos(rowKey);
}
@Override
public WritableRowSet match(
final boolean invertMatch,
final boolean usePrev,
final boolean caseInsensitive,
@NotNull final RowSet selection,
final Object... keys) {
if (startTime == null) {
// there are no valid rows for this column source yet
return RowSetFactory.empty();
}
final RowSetBuilderRandom matchingSet = RowSetFactory.builderRandom();
for (Object o : keys) {
if (!(o instanceof Instant)) {
continue;
}
final Instant key = (Instant) o;
if (epochNanos(key) % period != epochNanos(startTime) % period || isBefore(key, startTime)) {
continue;
}
matchingSet.addKey(minus(key, startTime) / period);
}
if (invertMatch) {
try (final WritableRowSet matching = matchingSet.build()) {
return selection.minus(matching);
}
}
final WritableRowSet matching = matchingSet.build();
matching.retain(selection);
return matching;
}
@Override
public Map getValuesMapping(RowSet subRange) {
final Map result = new LinkedHashMap<>();
subRange.forAllRowKeys(
ii -> result.put(computeInstant(ii), RowSetFactory.fromKeys(ii)));
return result;
}
@Override
public boolean allowsReinterpret(
@NotNull final Class alternateDataType) {
return alternateDataType == long.class;
}
@Override
public ColumnSource doReinterpret(
@NotNull Class alternateDataType) {
// noinspection unchecked
return (ColumnSource) new SyntheticInstantAsLongSource();
}
@Override
public void fillChunk(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final RowSequence rowSequence) {
final WritableObjectChunk objectDest = dest.asWritableObjectChunk();
dest.setSize(0);
rowSequence.forAllRowKeys(rowKey -> objectDest.add(computeInstant(rowKey)));
}
@Override
public void fillPrevChunk(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final RowSequence rowSequence) {
fillChunk(context, dest, rowSequence);
}
@Override
public void fillChunkUnordered(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final LongChunk extends RowKeys> keys) {
final WritableObjectChunk objectDest = dest.asWritableObjectChunk();
objectDest.setSize(keys.size());
for (int ii = 0; ii < keys.size(); ++ii) {
long rowKey = keys.get(ii);
if (rowKey < 0) {
objectDest.set(ii, null);
} else {
objectDest.set(ii, computeInstant(rowKey));
}
}
}
@Override
public void fillPrevChunkUnordered(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final LongChunk extends RowKeys> keys) {
fillChunkUnordered(context, dest, keys);
}
@Override
public boolean providesFillUnordered() {
return true;
}
private class SyntheticInstantAsLongSource extends AbstractColumnSource implements
ImmutableColumnSourceGetDefaults.LongBacked,
FillUnordered {
SyntheticInstantAsLongSource() {
super(long.class);
}
@Override
public Long get(long rowKey) {
if (rowKey < 0) {
return null;
}
return box(computeNanos(rowKey));
}
@Override
public long getLong(long rowKey) {
if (rowKey < 0) {
return QueryConstants.NULL_LONG;
}
return computeNanos(rowKey);
}
@Override
public void fillChunk(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final RowSequence rowSequence) {
WritableLongChunk super Values> longDest = dest.asWritableLongChunk();
dest.setSize(0);
rowSequence.forAllRowKeys(rowKey -> longDest.add(computeNanos(rowKey)));
}
@Override
public void fillPrevChunk(
@NotNull final FillContext context,
@NotNull final WritableChunk super Values> dest,
@NotNull final RowSequence rowSequence) {
fillChunk(context, dest, rowSequence);
}
@Override
public WritableRowSet match(
final boolean invertMatch,
final boolean usePrev,
final boolean caseInsensitive,
@NotNull final RowSet selection,
final Object... keys) {
if (startTime == null) {
// there are no valid rows for this column source yet
return RowSetFactory.empty();
}
final RowSetBuilderRandom matchingSet = RowSetFactory.builderRandom();
for (Object o : keys) {
if (!(o instanceof Long)) {
continue;
}
final long key = (Long) o;
if (key % period != epochNanos(startTime) % period || key < epochNanos(startTime)) {
continue;
}
matchingSet.addKey((key - epochNanos(startTime)) / period);
}
if (invertMatch) {
try (final WritableRowSet matching = matchingSet.build()) {
return selection.minus(matching);
}
}
final WritableRowSet matching = matchingSet.build();
matching.retain(selection);
return matching;
}
@Override
public Map getValuesMapping(RowSet subRange) {
final Map result = new LinkedHashMap<>();
subRange.forAllRowKeys(
ii -> result.put(box(computeNanos(ii)), RowSetFactory.fromKeys(ii)));
return result;
}
@Override
public boolean allowsReinterpret(
@NotNull final Class alternateDataType) {
return alternateDataType == Instant.class;
}
@Override
public ColumnSource doReinterpret(
@NotNull Class alternateDataType) {
// noinspection unchecked
return (ColumnSource) SyntheticInstantSource.this;
}
@Override
public void fillChunkUnordered(@NotNull FillContext context, @NotNull WritableChunk super Values> dest,
@NotNull LongChunk extends RowKeys> keys) {
final WritableLongChunk super Values> longDest = dest.asWritableLongChunk();
longDest.setSize(keys.size());
for (int ii = 0; ii < keys.size(); ++ii) {
long rowKey = keys.get(ii);
if (rowKey < 0) {
longDest.set(ii, QueryConstants.NULL_LONG);
} else {
longDest.set(ii, computeNanos(rowKey));
}
}
}
@Override
public void fillPrevChunkUnordered(@NotNull FillContext context,
@NotNull WritableChunk super Values> dest, @NotNull LongChunk extends RowKeys> keys) {
fillChunkUnordered(context, dest, keys);
}
@Override
public boolean providesFillUnordered() {
return true;
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy