You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/08/27 17:20:59 UTC

[sis] 04/11: Add a method for getting filters as a `F₀ AND F₁ AND F₂ AND F₃ AND ...` sequence. It allows to translate some Fₙ to SQL statements without forcing an "all or nothing" policy.

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

commit 8918191d6b97c332a996dc1e5d9b58845bc54687
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Aug 25 16:18:34 2021 +0200

    Add a method for getting filters as a `F₀ AND F₁ AND F₂ AND F₃ AND ...` sequence.
    It allows to translate some Fₙ to SQL statements without forcing an "all or nothing" policy.
---
 .../java/org/apache/sis/filter/Optimization.java   | 93 ++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
index d8c1cce..b582bce 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
@@ -18,14 +18,20 @@ package org.apache.sis.filter;
 
 import java.util.Map;
 import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.ConcurrentModificationException;
+import org.opengis.util.CodeList;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.CollectionsExt;
 
 // Branch-dependent imports
 import org.opengis.filter.Filter;
 import org.opengis.filter.Literal;
 import org.opengis.filter.Expression;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
 
 
 /**
@@ -294,4 +300,91 @@ public class Optimization {
             return this;
         }
     }
+
+    /**
+     * Optimizes or simplifies the given filter and returns it as a list of {@code AND} operands.
+     * If such list can not be built, then this method returns the optimized filter in a singleton list.
+     *
+     * <h4>Use case</h4>
+     * This method tries to transform a filter into a {@code F₀ AND F₁ AND F₂ AND F₃ AND ...} sequence.
+     * This transformation is useful when some operands can be handled by the storage engine
+     * (for example a SQL database) and other operands can not.
+     * For example when reading features from a relational database,
+     * the implementation may choose to express the F₁ and F₃ operands as SQL statements
+     * and apply the other operands in Java code.
+     *
+     * @param  <R>     the type of resources (e.g. {@code Feature}) used as inputs.
+     * @param  filter  the filter to decompose.
+     * @return a sequence of {@code AND} operands, or an empty list if the given filter was null.
+     * @throws ClassCastException if a filter declares the {@code AND}, {@code OR} or {@code NOT} type
+     *         without implementing the {@link LogicalOperator} interface.
+     */
+    @SuppressWarnings("unchecked")
+    public <R> List<Filter<? super R>> applyAndDecompose(final Filter<R> filter) {
+        /*
+         * This unsafe cast is okay if `toAndOperands(…)` do not invoke any `filter` method having a
+         * return value (directly or indirectly as list elements) restricted to the exact `<R>` type.
+         * Such methods do not exist in the GeoAPI interfaces, so we should be safe.
+         */
+        return toAndOperands((Filter<R>) apply(filter));
+    }
+
+    /**
+     * Returns the given filter as a list of {@code AND} operands.
+     * If such list can not be built, then this method returns the given filter in a singleton list.
+     *
+     * @param  <R>     the type of resources (e.g. {@code Feature}) used as inputs.
+     * @param  filter  the filter to decompose.
+     * @return a sequence of {@code AND} operands, or an empty list if the given filter was null.
+     * @throws ClassCastException if a filter declares the {@code AND}, {@code OR} or {@code NOT} type
+     *         without implementing the {@link LogicalOperator} interface.
+     */
+    private static <R> List<Filter<? super R>> toAndOperands(final Filter<R> filter) {
+        if (filter == null) {
+            return Collections.emptyList();
+        }
+        final CodeList<?> type = filter.getOperatorType();
+        if (type == LogicalOperatorName.AND) {
+            return ((LogicalOperator<R>) filter).getOperands();
+        }
+        if (type == LogicalOperatorName.NOT) {
+            final Filter<? super R> nop = getNotOperand(filter);
+            if (nop.getOperatorType() == LogicalOperatorName.OR) {
+                /*
+                 * The cast to `<R>` instead of `<? super R>` should be okay because we do not invoke
+                 * any method with a `<R>` return value. All invoked methods return `<? super R>`.
+                 * So what we actually have is a kind of `<? super ? super R>`.
+                 */
+                @SuppressWarnings("unchecked")
+                final List<Filter<? super R>> operands = ((LogicalOperator<R>) nop).getOperands();
+                final List<Filter<? super R>> result = new ArrayList<>(operands.size());
+                for (Filter<? super R> operand : operands) {
+                    if (operand.getOperatorType() == LogicalOperatorName.NOT) {
+                        operand = getNotOperand(operand);
+                    } else {
+                        operand = new LogicalFunction.Not<>(operand);
+                    }
+                    result.add(operand);
+                }
+                return result;
+            }
+        }
+        return Collections.singletonList(filter);
+    }
+
+    /**
+     * Returns the singleton operand of the given {@code NOT} filter.
+     *
+     * @param  filter  the {@code NOT} filter.
+     * @return the single operand of the {@code NOT} filter (never null).
+     * @throws ClassCastException if the filter does not implement the {@link LogicalOperator} interface.
+     * @throws IllegalArgumentException if the filter does not have a single operand.
+     */
+    private static <R> Filter<? super R> getNotOperand(final Filter<R> filter) {
+        final Filter<? super R> operand = CollectionsExt.singletonOrNull(((LogicalOperator<R>) filter).getOperands());
+        if (operand != null) {
+            return operand;
+        }
+        throw new IllegalArgumentException();
+    }
 }