From 861cd24b048f76c419f19484e92285f8407febab Mon Sep 17 00:00:00 2001 From: Nathaniel Bauernfeind Date: Sat, 24 Feb 2024 12:04:42 -0700 Subject: [PATCH] Stateful Selectables and SelectColumns --- .../deephaven/engine/table/ColumnSource.java | 2 +- .../table/impl/AbstractFilterExecution.java | 4 +- .../table/impl/PartitionAwareSourceTable.java | 8 +- .../BaseTableTransformationColumn.java | 5 - .../impl/partitioned/LongConstantColumn.java | 5 - .../select/BaseIncrementalReleaseFilter.java | 2 +- .../table/impl/select/ComposedFilter.java | 4 +- .../table/impl/select/ConditionFilter.java | 6 - .../table/impl/select/DhFormulaColumn.java | 34 ------ .../impl/select/DownsampledWhereFilter.java | 2 +- .../table/impl/select/FilterToListImpl.java | 14 +-- .../table/impl/select/FunctionalColumn.java | 5 - .../select/MultiSourceFunctionalColumn.java | 5 - .../table/impl/select/NullSelectColumn.java | 5 - .../impl/select/RollingReleaseFilter.java | 2 +- .../table/impl/select/SelectColumn.java | 19 ++-- .../table/impl/select/SortedClockFilter.java | 2 +- .../impl/select/StatefulSelectColumn.java | 106 ++++++++++++++++++ .../impl/select/StatefulWhereFilter.java | 85 ++++++++++++++ .../impl/select/UnsortedClockFilter.java | 2 +- .../engine/table/impl/select/WhereFilter.java | 2 +- .../table/impl/select/WhereFilterAdapter.java | 34 ++++-- .../select/python/FormulaColumnPython.java | 6 - .../engine/table/impl/QueryTableTest.java | 7 +- .../client/impl/BatchTableRequestBuilder.java | 23 ++-- .../io/deephaven/sql/FilterSimplifier.java | 22 ++-- .../java/io/deephaven/api/Selectable.java | 10 ++ .../io/deephaven/api/StatefulSelectable.java | 35 ++++++ .../main/java/io/deephaven/api/Strings.java | 36 ++++-- .../deephaven/api/expression/Expression.java | 2 + .../api/expression/StatefulExpression.java | 28 +++++ .../io/deephaven/api/filter/ExtractAnds.java | 5 + .../java/io/deephaven/api/filter/Filter.java | 6 + .../deephaven/api/filter/StatefulFilter.java | 39 +++++++ .../java/io/deephaven/api/StringsTest.java | 28 ++--- .../api/expression/ExpressionTest.java | 24 ++++ .../io/deephaven/api/filter/FilterTest.java | 31 +++++ 37 files changed, 508 insertions(+), 147 deletions(-) create mode 100644 engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulSelectColumn.java create mode 100644 engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulWhereFilter.java create mode 100644 table-api/src/main/java/io/deephaven/api/StatefulSelectable.java create mode 100644 table-api/src/main/java/io/deephaven/api/expression/StatefulExpression.java create mode 100644 table-api/src/main/java/io/deephaven/api/filter/StatefulFilter.java diff --git a/engine/api/src/main/java/io/deephaven/engine/table/ColumnSource.java b/engine/api/src/main/java/io/deephaven/engine/table/ColumnSource.java index 9299fcfca7b..ec39de09489 100644 --- a/engine/api/src/main/java/io/deephaven/engine/table/ColumnSource.java +++ b/engine/api/src/main/java/io/deephaven/engine/table/ColumnSource.java @@ -202,7 +202,7 @@ default ColumnSource cast(Class clazz, @Nullable Cl /** * Most column sources will return the same value for a given row without respect to the order that the rows are * read. Those columns sources are considered "stateless" and should return true. - * + *

* Some column sources, however may be dependent on evaluation order. For example, a formula that updates a Map must * be evaluated from the first row to the last row. A column source that has the potential to depend on the order of * evaluation must return false. diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/AbstractFilterExecution.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/AbstractFilterExecution.java index f8a02c442f6..2e073f980e7 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/AbstractFilterExecution.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/AbstractFilterExecution.java @@ -320,7 +320,7 @@ boolean shouldParallelizeFilter(WhereFilter filter, long numberOfRows) { return permitParallelization() && numberOfRows != 0 && (QueryTable.FORCE_PARALLEL_WHERE || numberOfRows / 2 > QueryTable.PARALLEL_WHERE_ROWS_PER_SEGMENT) - && filter.permitParallelization(); + && filter.isStateless(); } /** @@ -338,6 +338,6 @@ static boolean permitParallelization(WhereFilter[] filters) { if (QueryTable.DISABLE_PARALLEL_WHERE) { return false; } - return Arrays.stream(filters).anyMatch(WhereFilter::permitParallelization); + return Arrays.stream(filters).anyMatch(WhereFilter::isStateless); } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/PartitionAwareSourceTable.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/PartitionAwareSourceTable.java index 166e088657d..a582f2d2d89 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/PartitionAwareSourceTable.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/PartitionAwareSourceTable.java @@ -157,8 +157,9 @@ public Table selectDistinctInternal(Collection columns) { } catch (Exception e) { return null; } - if (!((PartitionAwareSourceTable) table).isValidAgainstColumnPartitionTable(selectColumn.getColumns(), - selectColumn.getColumnArrays())) { + if (!selectColumn.isStateless() || + !((PartitionAwareSourceTable) table).isValidAgainstColumnPartitionTable( + selectColumn.getColumns(), selectColumn.getColumnArrays())) { return null; } } @@ -325,7 +326,8 @@ public final Table selectDistinct(Collection columns) { final List selectColumns = Arrays.asList(SelectColumn.from(columns)); for (SelectColumn selectColumn : selectColumns) { selectColumn.initDef(definition.getColumnNameMap()); - if (!isValidAgainstColumnPartitionTable(selectColumn.getColumns(), selectColumn.getColumnArrays())) { + if (selectColumn.isStateless() || + !isValidAgainstColumnPartitionTable(selectColumn.getColumns(), selectColumn.getColumnArrays())) { // Be sure to invoke the super-class version of this method, rather than the array-based one that // delegates to this method. return super.selectDistinct(selectColumns); diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/BaseTableTransformationColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/BaseTableTransformationColumn.java index be54278bcd9..44a3adf0ab0 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/BaseTableTransformationColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/BaseTableTransformationColumn.java @@ -59,11 +59,6 @@ public final boolean isRetain() { return false; } - @Override - public final boolean isStateless() { - return true; - } - static ColumnSource getAndValidateInputColumnSource( @NotNull final String inputColumnName, @NotNull final Map> columnsOfInterest) { diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/LongConstantColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/LongConstantColumn.java index 14d476211bf..dc4ebc4c689 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/LongConstantColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/partitioned/LongConstantColumn.java @@ -105,11 +105,6 @@ public final boolean isRetain() { return false; } - @Override - public final boolean isStateless() { - return true; - } - private static final class OutputFormulaFillContext implements Formula.FillContext { private static final Formula.FillContext INSTANCE = new OutputFormulaFillContext(); diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/BaseIncrementalReleaseFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/BaseIncrementalReleaseFilter.java index 065558b0f8a..f5c71097acb 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/BaseIncrementalReleaseFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/BaseIncrementalReleaseFilter.java @@ -258,7 +258,7 @@ protected void destroy() { } @Override - public boolean permitParallelization() { + public boolean isStateless() { return false; } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ComposedFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ComposedFilter.java index 4248b6d9208..ff4e0f45206 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ComposedFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ComposedFilter.java @@ -124,7 +124,7 @@ public int hashCode() { } @Override - public boolean permitParallelization() { - return Arrays.stream(componentFilters).allMatch(WhereFilter::permitParallelization); + public boolean isStateless() { + return Arrays.stream(componentFilters).allMatch(WhereFilter::isStateless); } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ConditionFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ConditionFilter.java index 3b4f3040ca3..da2124ab7df 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ConditionFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/ConditionFilter.java @@ -614,10 +614,4 @@ public ConditionFilter copy() { public ConditionFilter renameFilter(Map renames) { return new ConditionFilter(formula, renames); } - - @Override - public boolean permitParallelization() { - // TODO (https://github.com/deephaven/deephaven-core/issues/4896): Assume statelessness by default. - return false; - } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DhFormulaColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DhFormulaColumn.java index 4a807eb2c79..64fe9b50b18 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DhFormulaColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DhFormulaColumn.java @@ -834,38 +834,4 @@ String makeGetExpression(boolean usePrev) { return String.format("%s.%s(k)", name, getGetterName(columnDefinition.getDataType(), usePrev)); } } - - /** - * Is this parameter immutable, and thus would contribute no state to the formula? - *

- * If any query scope parameter is not a primitive, String, or known immutable class; then it may be a mutable - * object that results in undefined results when the column is not evaluated strictly in order. - * - * @return true if this query scope parameter is immutable - */ - private static boolean isImmutableType(QueryScopeParam param) { - final Object value = param.getValue(); - if (value == null) { - return true; - } - final Class type = value.getClass(); - if (type == String.class || type == BigInteger.class || type == BigDecimal.class - || type == Instant.class || type == ZonedDateTime.class || Table.class.isAssignableFrom(type)) { - return true; - } - // if it is a boxed type, then it is immutable; otherwise we don't know what to do with it - return TypeUtils.isBoxedType(type); - } - - private boolean isUsedColumnStateless(String columnName) { - return columnSources.get(columnName).isStateless(); - } - - @Override - public boolean isStateless() { - return Arrays.stream(params).allMatch(DhFormulaColumn::isImmutableType) - && usedColumns.stream().allMatch(this::isUsedColumnStateless) - && usedColumnArrays.stream().allMatch(this::isUsedColumnStateless); - } - } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DownsampledWhereFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DownsampledWhereFilter.java index 9f1c9288844..856440647a4 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DownsampledWhereFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/DownsampledWhereFilter.java @@ -159,7 +159,7 @@ public DownsampledWhereFilter copy() { } @Override - public boolean permitParallelization() { + public boolean isStateless() { return false; } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FilterToListImpl.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FilterToListImpl.java index 91d24ceb2f9..f701d2b30f0 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FilterToListImpl.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FilterToListImpl.java @@ -3,14 +3,7 @@ import io.deephaven.api.RawString; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; +import io.deephaven.api.filter.*; import java.util.List; @@ -79,4 +72,9 @@ public List visit(Method method) { public List visit(RawString rawString) { return List.of(rawString); } + + @Override + public List visit(StatefulFilter filter) { + return List.of(filter); + } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FunctionalColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FunctionalColumn.java index 2dda0e38dda..81cf41b1188 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FunctionalColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/FunctionalColumn.java @@ -218,11 +218,6 @@ public boolean isRetain() { return false; } - @Override - public boolean isStateless() { - return false; - } - @Override public FunctionalColumn copy() { return new FunctionalColumn<>(sourceName, sourceDataType, destName, destDataType, function); diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/MultiSourceFunctionalColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/MultiSourceFunctionalColumn.java index 29d77004a69..cda444ff3ae 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/MultiSourceFunctionalColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/MultiSourceFunctionalColumn.java @@ -197,11 +197,6 @@ public boolean isRetain() { return false; } - @Override - public boolean isStateless() { - return false; - } - @Override public MultiSourceFunctionalColumn copy() { return new MultiSourceFunctionalColumn<>(sourceNames, destName, destDataType, function); diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/NullSelectColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/NullSelectColumn.java index 8a544d80048..1cc33476e85 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/NullSelectColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/NullSelectColumn.java @@ -88,11 +88,6 @@ public boolean isRetain() { return false; } - @Override - public boolean isStateless() { - return true; - } - @Override public SelectColumn copy() { // noinspection unchecked,rawtypes diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/RollingReleaseFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/RollingReleaseFilter.java index dac007e0554..f4003f8d53a 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/RollingReleaseFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/RollingReleaseFilter.java @@ -124,7 +124,7 @@ protected void destroy() { } @Override - public boolean permitParallelization() { + public boolean isStateless() { return false; } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SelectColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SelectColumn.java index 31295a0e0ea..da5980c0bb7 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SelectColumn.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SelectColumn.java @@ -3,13 +3,11 @@ */ package io.deephaven.engine.table.impl.select; -import io.deephaven.api.ColumnName; -import io.deephaven.api.RawString; -import io.deephaven.api.Selectable; -import io.deephaven.api.Strings; +import io.deephaven.api.*; import io.deephaven.api.expression.Expression; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; +import io.deephaven.api.expression.StatefulExpression; import io.deephaven.api.filter.Filter; import io.deephaven.api.literal.Literal; import io.deephaven.engine.context.QueryCompiler; @@ -173,10 +171,12 @@ default void validateSafeForRefresh(final BaseTable sourceTable) { } /** - * Returns true if this column is stateless (i.e. one row does not depend on the order of evaluation for another - * row). + * @return true if this column is stateless (i.e. one row does not depend on the order of evaluation for another + * row). */ - boolean isStateless(); + default boolean isStateless() { + return true; + } /** * Create a copy of this SelectColumn. @@ -222,6 +222,11 @@ public SelectColumn visit(RawString rhs) { return makeSelectColumn(Strings.of(rhs)); } + @Override + public SelectColumn visit(StatefulExpression statefulExpression) { + return StatefulSelectColumn.of(statefulExpression.innerExpression().walk(this)); + } + private SelectColumn makeSelectColumn(String rhs) { // TODO(deephaven-core#3740): Remove engine crutch on io.deephaven.api.Strings return SelectColumnFactory.getExpression(String.format("%s=%s", lhs.name(), rhs)); diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SortedClockFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SortedClockFilter.java index b72b9ff5644..020b381a9c9 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SortedClockFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/SortedClockFilter.java @@ -83,7 +83,7 @@ protected WritableRowSet updateAndGetAddedIndex() { } @Override - public boolean permitParallelization() { + public boolean isStateless() { return false; } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulSelectColumn.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulSelectColumn.java new file mode 100644 index 00000000000..6bb9a69f391 --- /dev/null +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulSelectColumn.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending + */ +package io.deephaven.engine.table.impl.select; + +import io.deephaven.annotations.SimpleStyle; +import io.deephaven.engine.rowset.TrackingRowSet; +import io.deephaven.engine.table.ColumnDefinition; +import io.deephaven.engine.table.ColumnSource; +import io.deephaven.engine.table.WritableColumnSource; +import io.deephaven.engine.table.impl.MatchPair; +import org.immutables.value.Value; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Map; + +/** + * A {@link SelectColumn} that wraps another {@code SelectColumn}, and is used to indicate that the wrapped column has + * side effects that prevent parallelization. (e.g. a column that depends on processing rows in order, or would suffer + * from lock contention if parallelized) + */ +@Value.Immutable +@SimpleStyle +public abstract class StatefulSelectColumn implements SelectColumn { + + public static StatefulSelectColumn of(@NotNull final SelectColumn innerSelectColumn) { + return ImmutableStatefulSelectColumn.of(innerSelectColumn); + } + + @Value.Parameter + public abstract SelectColumn innerSelectColumn(); + + @Override + public List initInputs( + @NotNull final TrackingRowSet rowSet, + @NotNull final Map> columnsOfInterest) { + return innerSelectColumn().initInputs(rowSet, columnsOfInterest); + } + + @Override + public List initDef( + @NotNull final Map> columnDefinitionMap) { + return innerSelectColumn().initDef(columnDefinitionMap); + } + + @Override + public Class getReturnedType() { + return innerSelectColumn().getReturnedType(); + } + + @Override + public List getColumns() { + return innerSelectColumn().getColumns(); + } + + @Override + public List getColumnArrays() { + return innerSelectColumn().getColumnArrays(); + } + + @Override + public @NotNull ColumnSource getDataView() { + return innerSelectColumn().getDataView(); + } + + @Override + public @NotNull ColumnSource getLazyView() { + return innerSelectColumn().getLazyView(); + } + + @Override + public String getName() { + return innerSelectColumn().getName(); + } + + @Override + public MatchPair getMatchPair() { + return innerSelectColumn().getMatchPair(); + } + + @Override + public WritableColumnSource newDestInstance(long size) { + return innerSelectColumn().newDestInstance(size); + } + + @Override + public WritableColumnSource newFlatDestInstance(long size) { + return innerSelectColumn().newFlatDestInstance(size); + } + + @Override + public boolean isRetain() { + return innerSelectColumn().isRetain(); + } + + @Override + public SelectColumn copy() { + return StatefulSelectColumn.of(innerSelectColumn().copy()); + } + + @Override + public boolean isStateless() { + return false; + } +} diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulWhereFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulWhereFilter.java new file mode 100644 index 00000000000..a38e16ceff2 --- /dev/null +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/StatefulWhereFilter.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending + */ +package io.deephaven.engine.table.impl.select; + +import io.deephaven.annotations.SimpleStyle; +import io.deephaven.engine.rowset.RowSet; +import io.deephaven.engine.rowset.WritableRowSet; +import io.deephaven.engine.table.Table; +import io.deephaven.engine.table.TableDefinition; +import org.immutables.value.Value; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * A {@link WhereFilter} that wraps another {@code WhereFilter}, and is used to indicate that the wrapped filter has + * side effects that prevent parallelization. (e.g. a filter that depends on processing rows in order, or would suffer + * from lock contention if parallelized) + */ +@Value.Immutable +@SimpleStyle +public abstract class StatefulWhereFilter implements WhereFilter { + + public static StatefulWhereFilter of(@NotNull final WhereFilter inner) { + return ImmutableStatefulWhereFilter.of(inner); + } + + @Value.Parameter + public abstract WhereFilter innerFilter(); + + @Override + public List getColumns() { + return innerFilter().getColumns(); + } + + @Override + public List getColumnArrays() { + return innerFilter().getColumnArrays(); + } + + @Override + public void init(TableDefinition tableDefinition) { + innerFilter().init(tableDefinition); + } + + @Override + public @NotNull WritableRowSet filter( + @NotNull final RowSet selection, + @NotNull final RowSet fullSet, + @NotNull final Table table, + boolean usePrev) { + return innerFilter().filter(selection, fullSet, table, usePrev); + } + + @Override + public boolean isSimpleFilter() { + return innerFilter().isSimpleFilter(); + } + + @Override + public void setRecomputeListener(RecomputeListener result) { + innerFilter().setRecomputeListener(result); + } + + @Override + public boolean isAutomatedFilter() { + return innerFilter().isAutomatedFilter(); + } + + @Override + public void setAutomatedFilter(boolean value) { + innerFilter().setAutomatedFilter(value); + } + + @Override + public WhereFilter copy() { + return StatefulWhereFilter.of(innerFilter().copy()); + } + + @Override + public boolean isStateless() { + return false; + } +} diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/UnsortedClockFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/UnsortedClockFilter.java index e69cd053f58..58711689b1f 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/UnsortedClockFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/UnsortedClockFilter.java @@ -143,7 +143,7 @@ protected WritableRowSet updateAndGetAddedIndex() { } @Override - public boolean permitParallelization() { + public boolean isStateless() { return false; } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilter.java index e62154dc016..78ef0d67b1e 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilter.java @@ -211,7 +211,7 @@ default boolean isRefreshing() { /** * @return if this filter can be applied in parallel */ - default boolean permitParallelization() { + default boolean isStateless() { return true; } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilterAdapter.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilterAdapter.java index 6d96b279bf7..c8234db15db 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilterAdapter.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/WhereFilterAdapter.java @@ -6,14 +6,8 @@ import io.deephaven.api.expression.Expression; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; +import io.deephaven.api.expression.StatefulExpression; +import io.deephaven.api.filter.*; import io.deephaven.api.literal.Literal; import io.deephaven.engine.table.impl.select.MatchFilter.MatchType; import io.deephaven.gui.table.filters.Condition; @@ -77,6 +71,10 @@ public static WhereFilter of(Filter filter, boolean inverted) { return filter.walk(new WhereFilterAdapter(inverted)); } + public static WhereFilter of(StatefulFilter filter, boolean inverted) { + return filter.walk(new WhereFilterAdapter(inverted)); + } + public static WhereFilter of(FilterNot not, boolean inverted) { return not.filter().walk(new WhereFilterAdapter(!inverted)); } @@ -205,6 +203,11 @@ public WhereFilter visit(RawString rawString) { return of(rawString, inverted); } + @Override + public WhereFilter visit(StatefulFilter filter) { + return of(filter, inverted); + } + private static class FilterComparisonAdapter implements Expression.Visitor { public static WhereFilter of(FilterComparison condition) { @@ -342,6 +345,11 @@ public WhereFilter visit(RawString rawString) { return original(); } + @Override + public WhereFilter visit(StatefulExpression statefulExpression) { + return StatefulWhereFilter.of(statefulExpression.innerExpression().walk(this)); + } + private RangeConditionFilter range(String rhsValue) { // TODO(deephaven-core#3730): More efficient io.deephaven.api.filter.FilterComparison to RangeFilter switch (preferred.operator()) { @@ -385,6 +393,11 @@ public WhereFilter visit(Method lhs) { public WhereFilter visit(RawString lhs) { return original(); } + + @Override + public WhereFilter visit(StatefulExpression statefulExpression) { + return StatefulWhereFilter.of(statefulExpression.innerExpression().walk(this)); + } } private static class ExpressionIsNullAdapter implements Expression.Visitor { @@ -446,5 +459,10 @@ public WhereFilter visit(Method method) { public WhereFilter visit(RawString rawString) { return getExpression(Strings.of(rawString)); } + + @Override + public WhereFilter visit(StatefulExpression statefulExpression) { + return StatefulWhereFilter.of(statefulExpression.innerExpression().walk(this)); + } } } diff --git a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/python/FormulaColumnPython.java b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/python/FormulaColumnPython.java index cc84b09739d..8b009fa4eab 100644 --- a/engine/table/src/main/java/io/deephaven/engine/table/impl/select/python/FormulaColumnPython.java +++ b/engine/table/src/main/java/io/deephaven/engine/table/impl/select/python/FormulaColumnPython.java @@ -51,12 +51,6 @@ public final List initDef(Map> columnDefinit return usedColumns; } - @Override - public boolean isStateless() { - // we can't control python - return false; - } - @Override protected final FormulaSourceDescriptor getSourceDescriptor() { return new FormulaSourceDescriptor( diff --git a/engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableTest.java b/engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableTest.java index 58ef8ac5996..ceffe55fa31 100644 --- a/engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableTest.java +++ b/engine/table/src/test/java/io/deephaven/engine/table/impl/QueryTableTest.java @@ -6,6 +6,7 @@ import com.google.common.primitives.Ints; import io.deephaven.UncheckedDeephavenException; import io.deephaven.api.Selectable; +import io.deephaven.api.StatefulSelectable; import io.deephaven.api.agg.spec.AggSpec; import io.deephaven.api.filter.Filter; import io.deephaven.api.snapshot.SnapshotWhenOptions.Flag; @@ -1413,7 +1414,8 @@ public void testSnapshotDependencies() { final Table snappedFirst = base.snapshotWhen(trigger, Flag.INITIAL); validateUpdates(snappedFirst); - final Table snappedDep = snappedFirst.select("B=testSnapshotDependenciesCounter.incrementAndGet()"); + final Table snappedDep = snappedFirst.select(Collections.singleton(StatefulSelectable.of( + Selectable.parse("B=testSnapshotDependenciesCounter.incrementAndGet()")))); final Table snappedOfSnap = snappedDep.snapshotWhen(trigger, Flag.INITIAL); validateUpdates(snappedOfSnap); @@ -1553,7 +1555,8 @@ public void testSnapshotIncrementalDependencies() { QueryScope.addParam("testSnapshotDependenciesCounter", new AtomicInteger()); final Table snappedFirst = base.snapshotWhen(trigger, Flag.INCREMENTAL); - final Table snappedDep = snappedFirst.select("B=testSnapshotDependenciesCounter.incrementAndGet()"); + final Table snappedDep = snappedFirst.select(Collections.singleton(StatefulSelectable.of( + Selectable.parse("B=testSnapshotDependenciesCounter.incrementAndGet()")))); final Table snappedOfSnap = snappedDep.snapshotWhen(trigger, Flag.INCREMENTAL); final ControlledUpdateGraph updateGraph = ExecutionContext.getContext().getUpdateGraph().cast(); diff --git a/java-client/session/src/main/java/io/deephaven/client/impl/BatchTableRequestBuilder.java b/java-client/session/src/main/java/io/deephaven/client/impl/BatchTableRequestBuilder.java index 7f92e29850a..0195f86edaa 100644 --- a/java-client/session/src/main/java/io/deephaven/client/impl/BatchTableRequestBuilder.java +++ b/java-client/session/src/main/java/io/deephaven/client/impl/BatchTableRequestBuilder.java @@ -16,15 +16,9 @@ import io.deephaven.api.expression.Expression; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; +import io.deephaven.api.expression.StatefulExpression; +import io.deephaven.api.filter.*; import io.deephaven.api.filter.FilterComparison.Operator; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; import io.deephaven.api.literal.Literal; import io.deephaven.api.snapshot.SnapshotWhenOptions; import io.deephaven.api.snapshot.SnapshotWhenOptions.Flag; @@ -720,6 +714,13 @@ public Value visit(RawString rawString) { throw new UnsupportedOperationException( "Unable to create a io.deephaven.proto.backplane.grpc.Value from a raw string"); } + + @Override + public Value visit(StatefulExpression statefulExpression) { + // TODO(deephaven-core#3609): Update gRPC expression / filter / literal structures + throw new UnsupportedOperationException( + "Unable to create a io.deephaven.proto.backplane.grpc.Value from a stateful expression"); + } } enum LiteralAdapter implements Literal.Visitor { @@ -903,5 +904,11 @@ public Condition visit(RawString rawString) { // TODO(deephaven-core#3609): Update gRPC expression / filter / literal structures throw new UnsupportedOperationException("Can't build Condition with raw string"); } + + @Override + public Condition visit(StatefulFilter filter) { + // TODO(deephaven-core#3609): Update gRPC expression / filter / literal structures + throw new UnsupportedOperationException("Can't build Condition with stateful wrapper"); + } } } diff --git a/sql/src/main/java/io/deephaven/sql/FilterSimplifier.java b/sql/src/main/java/io/deephaven/sql/FilterSimplifier.java index 0d32b34b3e7..e378bfb69b4 100644 --- a/sql/src/main/java/io/deephaven/sql/FilterSimplifier.java +++ b/sql/src/main/java/io/deephaven/sql/FilterSimplifier.java @@ -3,15 +3,8 @@ import io.deephaven.api.RawString; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; +import io.deephaven.api.filter.*; import io.deephaven.api.filter.Filter.Visitor; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; import io.deephaven.api.literal.Literal; import java.util.ArrayList; @@ -100,4 +93,17 @@ public Filter visit(boolean literal) { public Filter visit(RawString rawString) { return rawString; } + + @Override + public Filter visit(StatefulFilter filter) { + final Filter simplifiedInner = filter.innerFilter().walk(this); + if (simplifiedInner instanceof StatefulFilter) { + return simplifiedInner; + } + if (simplifiedInner instanceof FilterNot) { + return FilterNot.of(StatefulFilter.of(((FilterNot) simplifiedInner).filter())); + } else { + return StatefulFilter.of(simplifiedInner); + } + } } diff --git a/table-api/src/main/java/io/deephaven/api/Selectable.java b/table-api/src/main/java/io/deephaven/api/Selectable.java index 3fcb1018d84..45763996c0a 100644 --- a/table-api/src/main/java/io/deephaven/api/Selectable.java +++ b/table-api/src/main/java/io/deephaven/api/Selectable.java @@ -20,6 +20,16 @@ */ public interface Selectable { + /** + * Wrap a selectable to mark it as stateful. + * + * @param innerSelectable the selectable that is stateful + * @return the wrapped selectable + */ + static StatefulSelectable stateful(Selectable innerSelectable) { + return StatefulSelectable.of(innerSelectable); + } + static Selectable of(ColumnName newColumn, Expression expression) { if (newColumn.equals(expression)) { return newColumn; diff --git a/table-api/src/main/java/io/deephaven/api/StatefulSelectable.java b/table-api/src/main/java/io/deephaven/api/StatefulSelectable.java new file mode 100644 index 00000000000..a46d7b39029 --- /dev/null +++ b/table-api/src/main/java/io/deephaven/api/StatefulSelectable.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending + */ +package io.deephaven.api; + +import io.deephaven.annotations.SimpleStyle; +import io.deephaven.api.expression.Expression; +import io.deephaven.api.expression.StatefulExpression; +import org.immutables.value.Value; + +/** + * A {@link Selectable} that wraps another {@code Selectable}, and is used to indicate that the wrapped selectable has + * side effects that prevent parallelization. (e.g. an expression that depends on processing rows in order, or would + * suffer from lock contention if parallelized) + */ +@Value.Immutable +@SimpleStyle +public abstract class StatefulSelectable implements Selectable { + public static StatefulSelectable of(Selectable inner) { + return ImmutableStatefulSelectable.of(inner); + } + + @Value.Parameter + public abstract Selectable innerSelectable(); + + @Override + public ColumnName newColumn() { + return innerSelectable().newColumn(); + } + + @Override + public Expression expression() { + return StatefulExpression.of(innerSelectable().expression()); + } +} diff --git a/table-api/src/main/java/io/deephaven/api/Strings.java b/table-api/src/main/java/io/deephaven/api/Strings.java index af285661400..2a62fa339ff 100644 --- a/table-api/src/main/java/io/deephaven/api/Strings.java +++ b/table-api/src/main/java/io/deephaven/api/Strings.java @@ -8,14 +8,8 @@ import io.deephaven.api.expression.Expression; import io.deephaven.api.expression.Function; import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; +import io.deephaven.api.expression.StatefulExpression; +import io.deephaven.api.filter.*; import io.deephaven.api.literal.Literal; import org.apache.commons.text.StringEscapeUtils; @@ -231,6 +225,22 @@ public static String of(Method method, boolean invert) { + method.arguments().stream().map(Strings::of).collect(Collectors.joining(", ", "(", ")")); } + public static String of(StatefulFilter filter) { + return of(filter, false); + } + + public static String of(StatefulFilter filter, boolean invert) { + return (invert ? "!" : "") + "stateful(" + of(filter.innerFilter()) + ")"; + } + + public static String of(StatefulExpression expression) { + return of(expression, false); + } + + public static String of(StatefulExpression expression, boolean invert) { + return (invert ? "!" : "") + "stateful(" + of(expression.innerExpression()) + ")"; + } + public static String of(boolean literal) { return Boolean.toString(literal); } @@ -322,6 +332,11 @@ public String visit(Function function) { public String visit(Method method) { return of(method, invert); } + + @Override + public String visit(StatefulExpression statefulExpression) { + return of(statefulExpression, invert); + } } private static class FilterAdapter implements Filter.Visitor { @@ -379,6 +394,11 @@ public String visit(Method method) { return of(method, invert); } + @Override + public String visit(StatefulFilter filter) { + return of(filter, invert); + } + @Override public String visit(boolean literal) { return of(literal ^ invert); diff --git a/table-api/src/main/java/io/deephaven/api/expression/Expression.java b/table-api/src/main/java/io/deephaven/api/expression/Expression.java index dea67a41e9a..d066d87dbb8 100644 --- a/table-api/src/main/java/io/deephaven/api/expression/Expression.java +++ b/table-api/src/main/java/io/deephaven/api/expression/Expression.java @@ -34,5 +34,7 @@ interface Visitor { T visit(Method method); T visit(RawString rawString); + + T visit(StatefulExpression statefulExpression); } } diff --git a/table-api/src/main/java/io/deephaven/api/expression/StatefulExpression.java b/table-api/src/main/java/io/deephaven/api/expression/StatefulExpression.java new file mode 100644 index 00000000000..e3f22ce645d --- /dev/null +++ b/table-api/src/main/java/io/deephaven/api/expression/StatefulExpression.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending + */ +package io.deephaven.api.expression; + +import io.deephaven.annotations.SimpleStyle; +import org.immutables.value.Value; + +/** + * A {@link Expression} that wraps another {@code Expression}, and is used to indicate that the wrapped filter has side + * effects that prevent parallelization. (e.g. a filter that depends on processing rows in order, or would suffer from + * lock contention if parallelized) + */ +@Value.Immutable +@SimpleStyle +public abstract class StatefulExpression implements Expression { + public static StatefulExpression of(Expression innerExpression) { + return ImmutableStatefulExpression.of(innerExpression); + } + + @Value.Parameter + public abstract Expression innerExpression(); + + @Override + public T walk(Visitor visitor) { + return visitor.visit(this); + } +} diff --git a/table-api/src/main/java/io/deephaven/api/filter/ExtractAnds.java b/table-api/src/main/java/io/deephaven/api/filter/ExtractAnds.java index dc7f61cdacb..4fd60bb9a47 100644 --- a/table-api/src/main/java/io/deephaven/api/filter/ExtractAnds.java +++ b/table-api/src/main/java/io/deephaven/api/filter/ExtractAnds.java @@ -74,4 +74,9 @@ public Collection visit(boolean literal) { public Collection visit(RawString rawString) { return Collections.singleton(rawString); } + + @Override + public Collection visit(StatefulFilter filter) { + return Collections.singleton(filter); + } } diff --git a/table-api/src/main/java/io/deephaven/api/filter/Filter.java b/table-api/src/main/java/io/deephaven/api/filter/Filter.java index 205c4a3f3ad..dacfa059142 100644 --- a/table-api/src/main/java/io/deephaven/api/filter/Filter.java +++ b/table-api/src/main/java/io/deephaven/api/filter/Filter.java @@ -34,6 +34,10 @@ */ public interface Filter extends Expression { + static Filter stateful(Filter innerFilter) { + return StatefulFilter.of(innerFilter); + } + static Collection from(String... expressions) { return from(Arrays.asList(expressions)); } @@ -233,5 +237,7 @@ interface Visitor { T visit(boolean literal); T visit(RawString rawString); + + T visit(StatefulFilter filter); } } diff --git a/table-api/src/main/java/io/deephaven/api/filter/StatefulFilter.java b/table-api/src/main/java/io/deephaven/api/filter/StatefulFilter.java new file mode 100644 index 00000000000..7d1b1730962 --- /dev/null +++ b/table-api/src/main/java/io/deephaven/api/filter/StatefulFilter.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2016-2024 Deephaven Data Labs and Patent Pending + */ +package io.deephaven.api.filter; + +import io.deephaven.annotations.SimpleStyle; +import io.deephaven.api.expression.Expression; +import org.immutables.value.Value; + +/** + * A {@link Filter} that wraps another {@code Filter}, and is used to indicate that the wrapped filter has side effects + * that prevent parallelization. (e.g. a filter that depends on processing rows in order, or would suffer from lock + * contention if parallelized) + */ +@Value.Immutable +@SimpleStyle +public abstract class StatefulFilter implements Filter { + public static StatefulFilter of(Filter innerFilter) { + return ImmutableStatefulFilter.of(innerFilter); + } + + @Value.Parameter + public abstract Filter innerFilter(); + + @Override + public T walk(Expression.Visitor visitor) { + return visitor.visit(this); + } + + @Override + public Filter invert() { + return StatefulFilter.of(innerFilter().invert()); + } + + @Override + public T walk(Visitor visitor) { + return visitor.visit(this); + } +} diff --git a/table-api/src/test/java/io/deephaven/api/StringsTest.java b/table-api/src/test/java/io/deephaven/api/StringsTest.java index 330833be1a9..c98b63ea606 100644 --- a/table-api/src/test/java/io/deephaven/api/StringsTest.java +++ b/table-api/src/test/java/io/deephaven/api/StringsTest.java @@ -1,18 +1,7 @@ package io.deephaven.api; -import io.deephaven.api.expression.Expression; -import io.deephaven.api.expression.ExpressionTest; -import io.deephaven.api.expression.Function; -import io.deephaven.api.expression.Method; -import io.deephaven.api.filter.Filter; -import io.deephaven.api.filter.FilterAnd; -import io.deephaven.api.filter.FilterComparison; -import io.deephaven.api.filter.FilterIn; -import io.deephaven.api.filter.FilterIsNull; -import io.deephaven.api.filter.FilterNot; -import io.deephaven.api.filter.FilterOr; -import io.deephaven.api.filter.FilterPattern; -import io.deephaven.api.filter.FilterTest; +import io.deephaven.api.expression.*; +import io.deephaven.api.filter.*; import io.deephaven.api.literal.Literal; import io.deephaven.api.literal.LiteralTest; import org.junit.jupiter.api.Test; @@ -133,6 +122,18 @@ public Void visit(RawString rawString) { return null; } + @Override + public Void visit(StatefulExpression statefulExpression) { + ensureExplicitStringOf(StatefulExpression.class); + return null; + } + + @Override + public Void visit(StatefulFilter filter) { + ensureExplicitStringOf(StatefulFilter.class); + return null; + } + @Override public Void visit(boolean literal) { @@ -188,5 +189,6 @@ public Void visit(String literal) { ensureExplicitStringOf(String.class); return null; } + } } diff --git a/table-api/src/test/java/io/deephaven/api/expression/ExpressionTest.java b/table-api/src/test/java/io/deephaven/api/expression/ExpressionTest.java index 0e29dae01fd..a9bf514b328 100644 --- a/table-api/src/test/java/io/deephaven/api/expression/ExpressionTest.java +++ b/table-api/src/test/java/io/deephaven/api/expression/ExpressionTest.java @@ -82,6 +82,11 @@ void expressionMethod() { stringsOf(Method.of(FOO, "myMethod", BAR), "Foo.myMethod(Bar)"); } + @Test + void expressionStateful() { + stringsOf(StatefulExpression.of(Method.of(FOO, "myMethod2", BAR)), "stateful(Foo.myMethod2(Bar))"); + } + @Test void literals() { stringsOf(Literal.of(true), "true"); @@ -141,6 +146,11 @@ public String visit(Method method) { public String visit(RawString rawString) { return of(rawString); } + + @Override + public String visit(StatefulExpression statefulExpression) { + return of(statefulExpression); + } } /** @@ -155,6 +165,7 @@ public static void visitAll(Visitor visitor) { visitor.visit((Function) null); visitor.visit((Method) null); visitor.visit((RawString) null); + visitor.visit((StatefulExpression) null); } private static class CountingVisitor implements Expression.Visitor { @@ -195,6 +206,12 @@ public CountingVisitor visit(RawString rawString) { ++count; return this; } + + @Override + public CountingVisitor visit(StatefulExpression statefulExpression) { + ++count; + return null; + } } public static class Examples implements Expression.Visitor { @@ -247,5 +264,12 @@ public Void visit(RawString rawString) { out.add(RawString.of("blerg9")); return null; } + + @Override + public Void visit(StatefulExpression statefulExpression) { + out.add(StatefulExpression.of(Function.of("my_function", FOO))); + out.add(StatefulExpression.of(Method.of(FOO, "whats", BAR))); + return null; + } } } diff --git a/table-api/src/test/java/io/deephaven/api/filter/FilterTest.java b/table-api/src/test/java/io/deephaven/api/filter/FilterTest.java index 43f57b05f8e..b251666f3d9 100644 --- a/table-api/src/test/java/io/deephaven/api/filter/FilterTest.java +++ b/table-api/src/test/java/io/deephaven/api/filter/FilterTest.java @@ -140,6 +140,19 @@ void filterMethod() { stringsOf(not(Method.of(FOO, "MyFunction3", BAR, BAZ)), "!Foo.MyFunction3(Bar, Baz)"); } + @Test + void filterStateful() { + stringsOf(StatefulFilter.of(Function.of("MyFunction1")), "stateful(MyFunction1())"); + stringsOf(StatefulFilter.of(Method.of(FOO, "MyFunction1")), "stateful(Foo.MyFunction1())"); + + stringsOf(not(StatefulFilter.of(Function.of("MyFunction2", BAR))), "!stateful(MyFunction2(Bar))"); + stringsOf(not(StatefulFilter.of(Method.of(FOO, "MyFunction2", BAR))), "!stateful(Foo.MyFunction2(Bar))"); + + stringsOf(StatefulFilter.of(not(Function.of("MyFunction2", BAR, BAZ))), "stateful(!MyFunction2(Bar, Baz))"); + stringsOf(StatefulFilter.of(not(Method.of(FOO, "MyFunction2", BAR, BAZ))), + "stateful(!Foo.MyFunction2(Bar, Baz))"); + } + @Test void filterPattern() { stringsOf(FilterPattern.of(FOO, Pattern.compile("myregex"), Mode.FIND, false), @@ -210,6 +223,7 @@ public static void visitAll(Visitor visitor) { visitor.visit((Method) null); visitor.visit(false); visitor.visit((RawString) null); + visitor.visit((StatefulFilter) null); } private enum FilterSpecificString implements Filter.Visitor { @@ -270,6 +284,11 @@ public String visit(boolean literal) { public String visit(RawString rawString) { return of(rawString); } + + @Override + public String visit(StatefulFilter filter) { + return of(filter); + } } private static class CountingVisitor implements Visitor { @@ -340,6 +359,12 @@ public CountingVisitor visit(RawString rawString) { ++count; return this; } + + @Override + public CountingVisitor visit(StatefulFilter filter) { + ++count; + return null; + } } public static class Examples implements Filter.Visitor { @@ -431,5 +456,11 @@ public Void visit(RawString rawString) { out.add(RawString.of("aoeuaoeu")); return null; } + + @Override + public Void visit(StatefulFilter filter) { + out.add(StatefulFilter.of(Function.of("my_function", FOO))); + return null; + } } }