This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push: new 8e338b7 Resolve links during the optimization phase of a filter expression. It allows SQL statements where we previously missed opportunities. 8e338b7 is described below commit 8e338b7cf5d7e3737873770b769af0a2664bbbb7 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Sun Dec 26 21:51:30 2021 +0100 Resolve links during the optimization phase of a filter expression. It allows SQL statements where we previously missed opportunities. --- .../java/org/apache/sis/filter/PropertyValue.java | 63 ++++++++++++++++++++-- .../sis/internal/sql/feature/FeatureStream.java | 19 +++++-- .../org/apache/sis/storage/sql/SQLStoreTest.java | 21 ++++++++ 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java index 594999a..8a20ddc 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java +++ b/core/sis-feature/src/main/java/org/apache/sis/filter/PropertyValue.java @@ -16,8 +16,10 @@ */ package org.apache.sis.filter; +import java.util.Optional; import java.util.Collection; import java.util.Collections; +import org.apache.sis.feature.Features; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ObjectConverter; import org.apache.sis.util.ObjectConverters; @@ -45,7 +47,7 @@ import org.opengis.filter.ValueReference; * * @author Johann Sorel (Geomatys) * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.2 * * @param <V> the type of value computed by the expression. * @@ -136,7 +138,9 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V> implements Val * An expression fetching property values as {@code Object}. * This expression does not need to apply any type conversion. */ - private static final class AsObject extends PropertyValue<Object> { + private static final class AsObject extends PropertyValue<Object> + implements Optimization.OnExpression<Feature,Object> + { /** For cross-version compatibility. */ private static final long serialVersionUID = 2854731969723006038L; @@ -161,6 +165,21 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V> implements Val } return null; } + + /** + * If the evaluated property is a link, replaces this expression + * by a more direct reference to the target property. + */ + @Override + public Expression<Feature,?> optimize(final Optimization optimization) { + final FeatureType type = optimization.getFeatureType(); + if (type != null) try { + return Features.getLinkTarget(type.getProperty(name)).map(AsObject::new).orElse(this); + } catch (PropertyNotFoundException e) { + warning(e, true); + } + return this; + } } @@ -189,6 +208,14 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V> implements Val } /** + * Creates a new {@code Converted} fetching values for a property of different name. + * The given name should be the target of a link that the caller has resolved. + */ + protected Converted<V> rename(final String target) { + return new Converted<>(type, target); + } + + /** * Returns the type of values computed by this expression. */ @Override @@ -222,13 +249,25 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V> implements Val public final Expression<Feature, ? extends V> optimize(final Optimization optimization) { final FeatureType featureType = optimization.getFeatureType(); if (featureType != null) try { - final PropertyType property = featureType.getProperty(name); + String targetName = name; + PropertyType property = featureType.getProperty(targetName); + Optional<String> target = Features.getLinkTarget(property); + if (target.isPresent()) try { + targetName = target.get(); + property = featureType.getProperty(targetName); + } catch (PropertyNotFoundException e) { + targetName = name; + warning(e, true); + } if (property instanceof AttributeType<?>) { final Class<?> source = ((AttributeType<?>) property).getValueClass(); if (source != null && source != Object.class && !source.isAssignableFrom(getSourceClass())) { - return new CastedAndConverted<>(source, type, name); + return new CastedAndConverted<>(source, type, targetName); } } + if (!targetName.equals(name)) { + return rename(targetName); + } } catch (PropertyNotFoundException e) { warning(e, true); } @@ -301,6 +340,22 @@ abstract class PropertyValue<V> extends LeafExpression<Feature,V> implements Val converter = ObjectConverters.find(source, type); } + /** Creates a new expression derived from an existing one except for the target name. */ + private CastedAndConverted(final CastedAndConverted<S,V> other, final String name) { + super(other.type, name); + source = other.source; + converter = other.converter; + } + + /** + * Creates a new {@code CastedAndConverted} fetching values for a property of different name. + * The given name should be the target of a link that the caller has resolved. + */ + @Override + protected Converted<V> rename(final String target) { + return new CastedAndConverted<>(this, target); + } + /** * Returns the type of values fetched from {@link Feature} instance. */ diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java index 2b4ab6b..2660249 100644 --- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureStream.java @@ -32,6 +32,7 @@ import org.apache.sis.internal.metadata.sql.SQLBuilder; import org.apache.sis.internal.stream.DeferredStream; import org.apache.sis.internal.stream.PaginedStream; import org.apache.sis.internal.filter.SortByComparator; +import org.apache.sis.internal.util.Strings; import org.apache.sis.storage.DataStoreException; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.ArgumentChecks; @@ -75,7 +76,7 @@ final class FeatureStream extends DeferredStream<Feature> { /** * The SQL fragment on the right side of the {@code WHERE} keyword. * This buffer does not including the {@code WHERE} keyword. - * IT is created when first needed. + * It is created when first needed and discarded after the iterator is created. */ private SelectionClause selection; @@ -392,8 +393,7 @@ final class FeatureStream extends DeferredStream<Feature> { @Override protected Spliterator<Feature> createSourceIterator() throws Exception { final String filter = (selection != null && !selection.isEmpty()) ? selection.toString() : null; - selection = null; // Let the garbage collector do its work. - filterToSQL = null; + selection = null; // Let the garbage collector do its work. final Connection connection = getConnection(); setCloseHandler(connection); // Executed only if `FeatureIterator` creation fails, discarded later otherwise. @@ -402,4 +402,17 @@ final class FeatureStream extends DeferredStream<Feature> { setCloseHandler(features); return features; } + + /** + * Returns a string representation of this stream for debugging purposes. + */ + @Override + public String toString() { + return Strings.toString(getClass(), "table", table.name.table, + "predicates", hasPredicates ? (filterToSQL != null ? "mixed" : "Java") : (filterToSQL != null ? "SQL" : null), + "comparator", hasComparator ? (sort != null ? "mixed" : "Java") : (sort != null ? "SQL" : null), + "distinct", distinct ? Boolean.TRUE : null, + "offset", offset != 0 ? offset : null, + "count", count != 0 ? count : null); + } } diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java index b4111b1..a1ee95e 100644 --- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java @@ -207,6 +207,7 @@ public final strictfp class SQLStoreTest extends TestCase { verifySimpleQuerySorting(store); verifySimpleQueryWithLimit(store); verifySimpleWhere(store); + verifyWhereOnLink(store); verifyStreamOperations(store.findResource("Cities")); } canada = null; @@ -432,6 +433,26 @@ public final strictfp class SQLStoreTest extends TestCase { } /** + * Requests a new set of features filtered by a condition on the "sis:identifier" property. + * The optimizer should replace that link by a condition on the actual column. + * + * @param dataset the store on which to query the features. + * @throws DataStoreException if an error occurred during query execution. + */ + private void verifyWhereOnLink(SQLStore dataset) throws Exception { + final String desiredProperty = "native_name"; + final String[] expectedValues = {"Canada"}; + final FeatureSet countries = dataset.findResource("Countries"); + final FeatureQuery query = new FeatureQuery(); + query.setSelection(FF.equal(FF.property("sis:identifier"), FF.literal("CAN"))); + final Object[] names; + try (Stream<Feature> features = countries.subset(query).features(false)) { + names = features.map(f -> f.getPropertyValue(desiredProperty)).toArray(); + } + assertArrayEquals(expectedValues, names); + } + + /** * Checks that operations stacked on feature stream are well executed. * This test focuses on mapping and peeking actions overloaded by SQL streams. * Operations used here are meaningless; we just want to ensure that the pipeline does not skip any operation.