All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.deephaven.engine.table.impl.util.DynamicTableWriter Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
/**
 * Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
 */
package io.deephaven.engine.table.impl.util;

import io.deephaven.base.verify.Assert;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.table.WritableColumnSource;
import io.deephaven.engine.table.Table;
import io.deephaven.engine.updategraph.UpdateGraph;
import io.deephaven.qst.column.header.ColumnHeader;
import io.deephaven.qst.table.TableHeader;
import io.deephaven.qst.type.Type;
import io.deephaven.tablelogger.Row;
import io.deephaven.tablelogger.RowSetter;
import io.deephaven.tablelogger.TableWriter;
import io.deephaven.engine.table.TableDefinition;
import io.deephaven.util.QueryConstants;
import io.deephaven.engine.table.impl.UpdateSourceQueryTable;
import io.deephaven.engine.table.impl.sources.ArrayBackedColumnSource;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.impl.sources.SingleValueColumnSource;
import org.apache.commons.lang3.mutable.MutableInt;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Collectors;

/**
 * The DynamicTableWriter creates an in-memory table using ArrayBackedColumnSources of the type specified in the
 * constructor. You can retrieve the table using the {@code getTable} function.
 * 

* This class is not thread safe, you must synchronize externally. However, multiple setters may safely log * concurrently. * * @implNote The constructor publishes {@code this} to the {@link UpdateGraph} and thus cannot be subclassed. */ public final class DynamicTableWriter implements TableWriter { private final UpdateSourceQueryTable table; private final WritableColumnSource[] arrayColumnSources; private final String[] columnNames; private int allocatedSize; private final Map> factoryMap = new HashMap<>(); private DynamicTableRow primaryRow; private int lastCommittedRow = -1; private int lastSetterRow; /** * Creates a TableWriter that produces an in-memory table using the provided column names and types. * * @param header the names and types of the columns in the output table (and our input) * @param constantValues a Map of columns with constant values */ public DynamicTableWriter(final TableHeader header, final Map constantValues) { this(getSources(header, constantValues, 256), constantValues, 256); } /** * Creates a TableWriter that produces an in-memory table using the provided column names and types. * * @param header the names and types of the columns in the output table (and our input) */ public DynamicTableWriter(final TableHeader header) { this(header, Collections.emptyMap()); } // This constructor is no longer public to simplify access from python: jpy cannot resolve // calls with arguments of list type when there is more than one alternative with array element type // on the java side. Prefer the constructor taking qst.table.TableHeader or an array of qst.type.Type // objects. @SuppressWarnings("WeakerAccess") DynamicTableWriter( final String[] columnNames, final Class[] columnTypes, final Map constantValues) { this(columnNames, (int i) -> columnTypes[i], constantValues); } /** * Creates a TableWriter that produces an in-memory table using the provided column names and types. * * @param columnNames the names of the columns in the output table (and our input) * @param columnTypes the types of the columns in the output table (must be compatible with the input) * @param constantValues a Map of columns with constant values */ @SuppressWarnings("WeakerAccess") public DynamicTableWriter( final String[] columnNames, final Type[] columnTypes, final Map constantValues) { this(columnNames, (int i) -> columnTypes[i].clazz(), constantValues); } // This constructor is no longer public to simplify access from python: jpy cannot resolve // calls with arguments of list type when there is more than one alternative with array element type // on the java side. Prefer the constructor taking qst.table.TableHeader or an array of qst.type.Type // objects. DynamicTableWriter(final String[] columnNames, final Class[] columnTypes) { this(columnNames, columnTypes, Collections.emptyMap()); } /** * Creates a TableWriter that produces an in-memory table using the provided column names and types. * * @param columnNames the names of the columns in the output table (and our input) * @param columnTypes the types of the columns in the output table (must be compatible with the input) */ public DynamicTableWriter(final String[] columnNames, final Type[] columnTypes) { this(columnNames, columnTypes, Collections.emptyMap()); } /** * Creates a write object that would write an object at a given location * * @param definition The table definition to create the dynamic table writer for */ public DynamicTableWriter(@NotNull TableDefinition definition) { this(definition.getColumnNamesArray(), definition.getColumnTypesArray()); } /** * Creates a write object that would write an object at a given location * * @param definition The table definition to create the dynamic table writer for */ public DynamicTableWriter(TableDefinition definition, Map constantValues) { this(definition.getColumnNamesArray(), definition.getColumnTypesArray(), constantValues); } /** * Gets the table created by this DynamicTableWriter. *

* The returned table is registered with the PeriodicUpdateGraph, and new rows become visible within the run loop. * * @return a live table with the output of this log */ public UpdateSourceQueryTable getTable() { return table; } /** * Returns a row writer, which allocates the row. You may get setters for the row, and then call addRowToTableIndex * when you are finished. Because the row is allocated when you call this function, it is possible to get several * Row objects before calling addRowToTableIndex. *

* This contrasts with {@code DynamicTableWriter.getSetter}, which allocates a single row; and you must call * {@code DynamicTableWriter.addRowToTableIndex} before advancing to the next row. * * @return a Row from which you can retrieve setters and call write row. */ @Override public Row getRowWriter() { return new DynamicTableRow(); } /** * Returns a RowSetter for the given column. If required, a Row object is allocated. You can not mix calls with * {@code getSetter} and {@code getRowWriter}. After setting each column, you must call {@code addRowToTableIndex}, * before beginning to write the next row. * * @param name column name. * @return a RowSetter for the given column */ @Override public PermissiveRowSetter getSetter(String name) { if (primaryRow == null) { primaryRow = new DynamicTableRow(); } return primaryRow.getSetter(name); } private RowSetterImpl getSetter(final int columnIndex) { if (primaryRow == null) { primaryRow = new DynamicTableRow(); } return primaryRow.setters[columnIndex]; } @Override public void setFlags(Row.Flags flags) { if (primaryRow == null) { primaryRow = new DynamicTableRow(); } primaryRow.setFlags(flags); } /** * Writes the current row created with the {@code getSetter} call, and advances the current row by one. *

* The row will be made visible in the table after the PeriodicUpdateGraph run cycle completes. */ @Override public void writeRow() { Assert.neqNull(primaryRow, "primaryRow"); primaryRow.writeRow(); } private void addRangeToTableIndex(int first, int last) { table.addRowKeyRange(first, last); } private void ensureCapacity(int row) { if (row < allocatedSize) { return; } int newSize = allocatedSize; while (row >= newSize) { newSize = 2 * newSize; } for (final WritableColumnSource arrayColumnSource : arrayColumnSources) { if (arrayColumnSource != null) { arrayColumnSource.ensureCapacity(newSize); } } allocatedSize = newSize; } /** * This is a convenience function so that you can log an entire row at a time using a Map. You must specify all * values in the setters map (and can't have any extras). The type of the value must be castable to the type of the * setter. * * @param values a map from column name to value for the row to be logged */ @SuppressWarnings("unused") public void logRow(Map values) { if (values.size() != factoryMap.size()) { throw new RuntimeException("Incompatible logRow call: " + values.keySet() + " != " + factoryMap.keySet()); } for (final Map.Entry value : values.entrySet()) { // noinspection unchecked getSetter(value.getKey()).set(value.getValue()); } writeRow(); flush(); } /** * This is a convenience function so that you can log an entire row at a time. * * @param values an array containing values to be logged, in order of the fields specified by the constructor */ @SuppressWarnings("unused") public void logRow(Object... values) { if (values.length != factoryMap.size()) { throw new RuntimeException( "Incompatible logRow call, values length=" + values.length + " != setters=" + factoryMap.size()); } for (int ii = 0; ii < values.length; ++ii) { // noinspection unchecked getSetter(ii).set(values[ii]); } writeRow(); flush(); } /** * This is a convenience function so that you can log an entire row at a time using a Map. You must specify all * values in the setters map (and can't have any extras). The type of the value must be convertible (safely or * unsafely) to the type of the permissive setter. * * @param values a map from column name to value for the row to be logged */ @SuppressWarnings("unused") public void logRowPermissive(Map values) { if (values.size() != factoryMap.size()) { throw new RuntimeException( "Incompatible logRowPermissive call: " + values.keySet() + " != " + factoryMap.keySet()); } for (final Map.Entry value : values.entrySet()) { getSetter(value.getKey()).setPermissive(value.getValue()); } writeRow(); flush(); } /** * This is a convenience function so that you can log an entire row at a time. You must specify all values in the * setters map (and can't have any extras). The type of the value must be convertible (safely or unsafely) to the * type of the permissive setter. * * @param values an array containing values to be logged, in order of the fields specified by the constructor */ @SuppressWarnings("unused") public void logRowPermissive(Object... values) { if (values.length != factoryMap.size()) { throw new RuntimeException( "Incompatible logRowPermissive call, values length=" + values.length + " != setters=" + factoryMap.size()); } for (int ii = 0; ii < values.length; ++ii) { getSetter(ii).setPermissive(values[ii]); } writeRow(); flush(); } @Override public void flush() {} @Override public long size() { return table.size(); } @Override public void close() throws IOException { flush(); table.close(); } @Override public Class[] getColumnTypes() { return table.getDefinition().getColumnTypesArray(); } @Override public String[] getColumnNames() { return columnNames; } private static Map> getSources( final TableHeader header, final Map constantValues, final int allocatedSize) { final Map> sources = new LinkedHashMap<>(); final Iterator> it = header.iterator(); while (it.hasNext()) { final ColumnHeader colHeader = it.next(); final String colName = colHeader.name(); final Class colType = colHeader.componentType().clazz(); if (constantValues.containsKey(colName)) { final SingleValueColumnSource singleValueColumnSource = SingleValueColumnSource.getSingleValueColumnSource(colType); // noinspection unchecked singleValueColumnSource.set(constantValues.get(colName)); sources.put(colName, singleValueColumnSource); } else { ColumnSource source = ArrayBackedColumnSource.getMemoryColumnSource(allocatedSize, colType); sources.put(colName, source); } } return sources; } private static Map> getSources( final String[] columnNames, final IntFunction> columnTypes, final Map constantValues, final int allocatedSize) { final Map> sources = new LinkedHashMap<>(); for (int i = 0; i < columnNames.length; i++) { if (constantValues.containsKey(columnNames[i])) { final SingleValueColumnSource singleValueColumnSource = SingleValueColumnSource.getSingleValueColumnSource(columnTypes.apply(i)); // noinspection unchecked singleValueColumnSource.set(constantValues.get(columnNames[i])); sources.put(columnNames[i], singleValueColumnSource); } else { WritableColumnSource source = ArrayBackedColumnSource.getMemoryColumnSource(allocatedSize, columnTypes.apply(i)); sources.put(columnNames[i], source); } } return sources; } // Convenience implementation method. private DynamicTableWriter( final String[] columnNames, final IntFunction> columnTypes, final Map constantValues) { this(getSources(columnNames, columnTypes, constantValues, 256), constantValues, 256); } private DynamicTableWriter(final Map> sources, final Map constantValues, final int allocatedSize) { this.allocatedSize = allocatedSize; table = new UpdateSourceQueryTable(RowSetFactory.fromKeys().toTracking(), sources); table.setFlat(); table.setAttribute(Table.ADD_ONLY_TABLE_ATTRIBUTE, true); table.setAttribute(Table.APPEND_ONLY_TABLE_ATTRIBUTE, true); final int nCols = sources.size();; this.columnNames = new String[nCols]; this.arrayColumnSources = new WritableColumnSource[nCols]; int ii = 0; for (Map.Entry> entry : sources.entrySet()) { final String columnName = columnNames[ii] = entry.getKey(); final ColumnSource source = entry.getValue(); if (constantValues.containsKey(columnName)) { continue; } if (source instanceof WritableColumnSource) { arrayColumnSources[ii] = (WritableColumnSource) source; } else { throw new IllegalStateException( "Expected ArrayBackedColumnSource, instead found " + source.getClass()); } factoryMap.put(columnName, (currentRow) -> createRowSetter(source.getType(), (WritableColumnSource) source)); ++ii; } UpdateGraph updateGraph = table.getUpdateGraph(); updateGraph.addSource(table); } @SuppressWarnings("unchecked") private RowSetterImpl createRowSetter(Class type, WritableColumnSource buffer) { final RowSetterImpl result; if (type == boolean.class || type == Boolean.class) { result = new BooleanRowSetterImpl((WritableColumnSource) buffer); } else if (type == byte.class || type == Byte.class) { result = new ByteRowSetterImpl((WritableColumnSource) buffer); } else if (type == char.class || type == Character.class) { result = new CharRowSetterImpl((WritableColumnSource) buffer); } else if (type == double.class || type == Double.class) { result = new DoubleRowSetterImpl((WritableColumnSource) buffer); } else if (type == float.class || type == Float.class) { result = new FloatRowSetterImpl((WritableColumnSource) buffer); } else if (type == int.class || type == Integer.class) { result = new IntRowSetterImpl((WritableColumnSource) buffer); } else if (type == long.class || type == Long.class) { result = new LongRowSetterImpl((WritableColumnSource) buffer); } else if (type == short.class || type == Short.class) { result = new ShortRowSetterImpl((WritableColumnSource) buffer); } else if (CharSequence.class.isAssignableFrom(type)) { result = new StringRowSetterImpl((WritableColumnSource) buffer); } else { result = new ObjectRowSetterImpl<>(buffer, type); } return (RowSetterImpl) result; } public interface PermissiveRowSetter extends RowSetter { void setPermissive(Object value); } private static abstract class RowSetterImpl implements PermissiveRowSetter { protected final WritableColumnSource columnSource; protected int row; private final Class type; RowSetterImpl(WritableColumnSource columnSource, Class type) { this.columnSource = columnSource; this.type = type; } void setRow(int row) { this.row = row; writeToColumnSource(); } abstract void writeToColumnSource(); @Override public Class getType() { return type; } @Override public void set(T value) { throw new UnsupportedOperationException(); } @Override public void setPermissive(Object value) { // noinspection unchecked set((T) value); } @Override public void setBoolean(Boolean value) { throw new UnsupportedOperationException(); } @Override public void setByte(byte value) { throw new UnsupportedOperationException(); } @Override public void setChar(char value) { throw new UnsupportedOperationException(); } @Override public void setDouble(double value) { throw new UnsupportedOperationException(); } @Override public void setFloat(float value) { throw new UnsupportedOperationException(); } @Override public void setInt(int value) { throw new UnsupportedOperationException(); } @Override public void setLong(long value) { throw new UnsupportedOperationException(); } @Override public void setShort(short value) { throw new UnsupportedOperationException(); } } private static class BooleanRowSetterImpl extends RowSetterImpl { BooleanRowSetterImpl(WritableColumnSource array) { super(array, Boolean.class); } Boolean pendingBoolean; @Override public void set(Boolean value) { setBoolean(value == null ? QueryConstants.NULL_BOOLEAN : value); } @Override public void setBoolean(Boolean value) { pendingBoolean = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingBoolean); } } private static class ByteRowSetterImpl extends RowSetterImpl { ByteRowSetterImpl(WritableColumnSource array) { super(array, byte.class); } byte pendingByte = QueryConstants.NULL_BYTE; @Override public void set(Byte value) { setByte(value == null ? QueryConstants.NULL_BYTE : value); } @Override public void setPermissive(Object value) { setByte(value == null ? QueryConstants.NULL_BYTE : ((Number) value).byteValue()); } @Override public void setByte(byte value) { pendingByte = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingByte); } } private static class CharRowSetterImpl extends RowSetterImpl { CharRowSetterImpl(WritableColumnSource array) { super(array, char.class); } char pendingChar = QueryConstants.NULL_CHAR; @Override public void set(Character value) { setChar(value == null ? QueryConstants.NULL_CHAR : value); } @Override public void setChar(char value) { pendingChar = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingChar); } } private static class IntRowSetterImpl extends RowSetterImpl { IntRowSetterImpl(WritableColumnSource array) { super(array, int.class); } int pendingInt = QueryConstants.NULL_INT; @Override public void set(Integer value) { setInt(value == null ? QueryConstants.NULL_INT : value); } @Override public void setPermissive(Object value) { setInt(value == null ? QueryConstants.NULL_INT : ((Number) value).intValue()); } @Override public void setInt(int value) { pendingInt = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingInt); } } private static class DoubleRowSetterImpl extends RowSetterImpl { DoubleRowSetterImpl(WritableColumnSource array) { super(array, double.class); } double pendingDouble = QueryConstants.NULL_DOUBLE; @Override public void set(Double value) { setDouble(value == null ? QueryConstants.NULL_DOUBLE : value); } @Override public void setPermissive(Object value) { setDouble(value == null ? QueryConstants.NULL_DOUBLE : ((Number) value).doubleValue()); } @Override public void setDouble(double value) { pendingDouble = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingDouble); } } private static class FloatRowSetterImpl extends RowSetterImpl { FloatRowSetterImpl(WritableColumnSource array) { super(array, float.class); } float pendingFloat = QueryConstants.NULL_FLOAT; @Override public void set(Float value) { setFloat(value == null ? QueryConstants.NULL_FLOAT : value); } @Override public void setPermissive(Object value) { setFloat(value == null ? QueryConstants.NULL_FLOAT : ((Number) value).floatValue()); } @Override public void setFloat(float value) { pendingFloat = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingFloat); } } private static class LongRowSetterImpl extends RowSetterImpl { LongRowSetterImpl(WritableColumnSource array) { super(array, long.class); } long pendingLong = QueryConstants.NULL_LONG; @Override public void set(Long value) { setLong(value == null ? QueryConstants.NULL_LONG : value); } @Override public void setPermissive(Object value) { setLong(value == null ? QueryConstants.NULL_LONG : ((Number) value).longValue()); } @Override public void setLong(long value) { pendingLong = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingLong); } } private static class ShortRowSetterImpl extends RowSetterImpl { ShortRowSetterImpl(WritableColumnSource array) { super(array, short.class); } short pendingShort = QueryConstants.NULL_SHORT; @Override public void set(Short value) { setShort(value == null ? QueryConstants.NULL_SHORT : value); } @Override public void setPermissive(Object value) { setShort(value == null ? QueryConstants.NULL_SHORT : ((Number) value).shortValue()); } @Override public void setShort(short value) { pendingShort = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingShort); } } private static class ObjectRowSetterImpl extends RowSetterImpl { ObjectRowSetterImpl(WritableColumnSource array, Class type) { super(array, type); } T pendingObject; @Override public void set(T value) { pendingObject = value; } @Override void writeToColumnSource() { columnSource.set(row, pendingObject); } } private static class StringRowSetterImpl extends ObjectRowSetterImpl { StringRowSetterImpl(@NotNull final WritableColumnSource array) { super(array, String.class); } } private class DynamicTableRow implements Row { private final RowSetterImpl[] setters; private final Map> columnToSetter; private int row = lastSetterRow; private Row.Flags flags = Flags.SingleRow; private DynamicTableRow() { setters = Arrays.stream(columnNames).map(cn -> factoryMap.get(cn).apply(row)).toArray(RowSetterImpl[]::new); final MutableInt ci = new MutableInt(0); columnToSetter = Arrays.stream(columnNames) .collect(Collectors.toMap(Function.identity(), cn -> setters[ci.getAndIncrement()])); } @Override public PermissiveRowSetter getSetter(final String name) { final PermissiveRowSetter rowSetter = columnToSetter.get(name); if (rowSetter == null) { if (table.hasColumns(name)) { throw new RuntimeException("Column has a constant value, can not get setter " + name); } else { throw new RuntimeException("Unknown column name " + name); } } return rowSetter; } @Override public void writeRow() { synchronized (DynamicTableWriter.this) { boolean doFlush = false; switch (flags) { case SingleRow: doFlush = true; case StartTransaction: if (lastCommittedRow != lastSetterRow) { lastSetterRow = lastCommittedRow + 1; } break; case EndTransaction: doFlush = true; break; case None: break; } row = lastSetterRow++; // Before this row can be returned to a pool, it needs to ensure that the underlying sources // are appropriately sized to avoid race conditions. ensureCapacity(row); columnToSetter.values().forEach((x) -> x.setRow(row)); // The row has been committed during set, we just need to insert the row keys into the table if (doFlush) { DynamicTableWriter.this.addRangeToTableIndex(lastCommittedRow + 1, row); lastCommittedRow = row; } } } @Override public long size() { return DynamicTableWriter.this.size(); } @Override public void setFlags(Row.Flags flags) { this.flags = flags; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy