sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Refactor the way EPSGCodeFinder create the list of conditions to put in the WHERE clause. This make easier to support the case of CompoundCRS, which was not supported before this commit (previous behavior was to construct and compare all CRS in the dabase, which was very slow). The problem with CompoundCRS could be seen by the log messages much more numerous than expected.
Date Thu, 30 May 2019 21:26:08 GMT
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 5d0678a  Refactor the way EPSGCodeFinder create the list of conditions to put in
the WHERE clause. This make easier to support the case of CompoundCRS, which was not supported
before this commit (previous behavior was to construct and compare all CRS in the dabase,
which was very slow). The problem with CompoundCRS could be seen by the log messages much
more numerous than expected.
5d0678a is described below

commit 5d0678a45651bd46d62e2631bf1a70f51a6cbe6f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Thu May 30 23:24:08 2019 +0200

    Refactor the way EPSGCodeFinder create the list of conditions to put in the WHERE clause.
    This make easier to support the case of CompoundCRS, which was not supported before this
commit
    (previous behavior was to construct and compare all CRS in the dabase, which was very
slow).
    The problem with CompoundCRS could be seen by the log messages much more numerous than
expected.
---
 .../referencing/factory/sql/EPSGCodeFinder.java    | 478 +++++++++++++++------
 .../sis/referencing/factory/sql/TableInfo.java     |   4 +-
 src/main/config/logging.properties                 |  11 +-
 3 files changed, 348 insertions(+), 145 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index 0e7609a..d9bf57f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.factory.sql;
 
 import java.util.Set;
+import java.util.List;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.sql.Statement;
@@ -26,11 +27,18 @@ import org.opengis.util.FactoryException;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.IdentifiedObject;
-import org.opengis.referencing.crs.GeneralDerivedCRS;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.SingleCRS;
+import org.opengis.referencing.crs.CompoundCRS;
+import org.opengis.referencing.crs.GeodeticCRS;
+import org.opengis.referencing.crs.TemporalCRS;
+import org.opengis.referencing.crs.VerticalCRS;
+import org.opengis.referencing.crs.GeneralDerivedCRS;
+import org.opengis.referencing.cs.CoordinateSystem;
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.GeodeticDatum;
+import org.opengis.referencing.datum.TemporalDatum;
 import org.opengis.referencing.datum.VerticalDatum;
 import org.opengis.referencing.datum.VerticalDatumType;
 import org.apache.sis.internal.metadata.ReferencingServices;
@@ -41,6 +49,8 @@ import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.referencing.factory.ConcurrentAuthorityFactory;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.collection.Containers;
 import org.apache.sis.util.logging.Logging;
@@ -64,6 +74,15 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
     private final EPSGDataAccess dao;
 
     /**
+     * The type of object to search, or {@code null} for using {@code object.getClass()}.
+     * This is set to a non-null value when searching for dependencies, in order to avoid
+     * confusion in an implementation class implements more than one GeoAPI interfaces.
+     *
+     * @see #isInstance(Class, IdentifiedObject)
+     */
+    private Class<? extends IdentifiedObject> declaredType;
+
+    /**
      * Creates a new finder for the given data access object.
      */
     EPSGCodeFinder(final EPSGDataAccess dao) {
@@ -87,151 +106,326 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
     }
 
     /**
+     * Returns a description of the condition to put in a {@code WHERE} clause for an object
having
+     * the given dependency.
+     *
+     * <div class="section"><b>Implementation note:</b>
+     * The {@code super.find(…)} method performs a check (not documented in public API)
for detecting
+     * when it is invoked recursively, which is the case here. Consequently the {@code super.find(…)}
+     * behavior below is slightly different than usual: since invoked recursively, {@code
super.find(…)}
+     * checks the cache of the {@link ConcurrentAuthorityFactory} wrapper. If found, the
dependency will
+     * also be stored in the cache. This is desirable because this method may be invoked
(indirectly) in
+     * a loop for many CRS objects sharing the same {@link CoordinateSystem} or {@link Datum}
dependencies.
+     * </div>
+     *
+     * @param  column      column in the SQL query containing EPSG codes of dependency.
+     * @param  type        GeoAPI interface implemented by the dependency to search.
+     * @param  dependency  the dependency for which to search EPSG codes, or {@code null}.
+     * @param  ignoreAxes  whether to force ignoring axes. Useful for base CRS of projected
CRS.
+     * @return EPSG codes of given dependency, or {@code null} if no dependency were found.
+     */
+    private <T extends IdentifiedObject> Condition dependencies(final String column,
+            final Class<T> type, final T dependency, final boolean ignoreAxes) throws
FactoryException
+    {
+        if (dependency != null) {
+            final Class<? extends IdentifiedObject> pt = declaredType;
+            final boolean previous = isIgnoringAxes();
+            final Set<IdentifiedObject> find;
+            try {
+                setIgnoringAxes(ignoreAxes | previous);
+                declaredType = type;
+                find = find(dependency);
+            } finally {
+                declaredType = pt;
+                setIgnoringAxes(previous);
+            }
+            final Set<Number> filters = new LinkedHashSet<>(Containers.hashMapCapacity(find.size()));
+            for (final IdentifiedObject dep : find) {
+                Identifier id = IdentifiedObjects.getIdentifier(dep, Citations.EPSG);
+                if (id != null) try {                                                   //
Should never be null, but let be safe.
+                    filters.add(Integer.parseInt(id.getCode()));
+                } catch (NumberFormatException e) {
+                    Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY),
EPSGCodeFinder.class, "getCodeCandidates", e);
+                }
+            }
+            if (!filters.isEmpty()) {
+                return new Condition(column, filters);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * A condition to put in a SQL {@code WHERE} clause. SQL query will be one of the forms
shown below,
+     * where {@code <column>} and {@code <values>} are {@link #column} and {@link
#values} respectively.
+     *
+     * {@preformat sql
+     *     SELECT <codeColumn> FROM <table> WHERE <column> IN (<values>)
+     *     SELECT <codeColumn> FROM <table> WHERE <column> >= <value
- ε> AND <column> <= <value + ε>
+     * }
+     *
+     * The later form is used if {@code <filters>} is a floating point value.
+     * Otherwise, {@code <filters>} are typically EPSG codes of dependencies.
+     */
+    private static class Condition {
+        /** A sentinel value for filtering by name. */
+        static final Condition NAME = new Condition("NAME", Collections.emptySet());
+
+        /** The column on which the condition apply. */
+        final String column;
+
+        /** The values of the conditions. */
+        final Set<Number> values;
+
+        /** Creates a new condition. */
+        Condition(final String column, final Set<Number> values) {
+            this.column = column;
+            this.values = values;
+        }
+
+        /**
+         * Appends this condition into the given buffer. If {@code isNext} is {@code true},
then an {@code "AND"}
+         * keyword is appended before the condition. Otherwise this method presumes that
the given buffer already
+         * ends with {@code "WHERE "} or {@code "AND "} keyword. This method does <strong>not</strong>
append a
+         * new {@code "AND"} keyword after the condition.
+         *
+         * @param  isNext  whether to append a {@code "AND"} keyword before the condition.
+         * @param  buffer  where to append the SQL fragment.
+         * @return whether a SQL fragment has been appended.
+         */
+        boolean appendToWhere(final StringBuilder buffer, final boolean isNext) {
+            if (values.isEmpty()) return false;
+            if (isNext) buffer.append(" AND ");
+            buffer.append(column);
+            if (values.size() == 1) {
+                buffer.append('=').append(CollectionsExt.first(values));
+            } else {
+                buffer.append(" IN (");
+                for (final Number code : values) {
+                    buffer.append(code).append(',');
+                }
+                buffer.setCharAt(buffer.length() - 1, ')');
+            }
+            return true;
+        }
+
+        /**
+         * Appends an ordering criterion. The buffer shall ends with {@code "ORDER BY"} keyword
before
+         * this method is invoked. If this method appends a criterion, then a comma will
be appended
+         * after that criterion for allowing chaining.
+         *
+         * @param  buffer  where to append the SQL fragment.
+         */
+        void appendToOrderBy(final StringBuilder buffer) {
+        }
+
+        /**
+         * Returns a string representation of this condition for debugging purpose.
+         */
+        @Override
+        public final String toString() {
+            final StringBuilder buffer = new StringBuilder(50);
+            appendToWhere(buffer, false);
+            return buffer.toString();
+        }
+    }
+
+    /**
+     * A condition for a floating point value with a tolerance.
+     */
+    private static final class FloatCondition extends Condition {
+        /** Creates a new condition for the given value. */
+        FloatCondition(final String column, final double value) {
+            super(column, Collections.singleton(value));
+        }
+
+        /**
+         * Appends a condition with a numeric value assumed a linear distance in metres.
+         * The tolerance threshold is 1 centimetre for a planet of the size of Earth.
+         */
+        @Override
+        boolean appendToWhere(final StringBuilder buffer, final boolean isNext) {
+            if (isNext) buffer.append(" AND ");
+            final double value = values.iterator().next().doubleValue();
+            final double tolerance = Math.abs((Formulas.LINEAR_TOLERANCE / ReferencingServices.AUTHALIC_RADIUS)
* value);
+            buffer.append(column).append(">=").append(value - tolerance).append(" AND
")
+                  .append(column).append("<=").append(value + tolerance);
+            return true;
+        }
+
+        /**
+         * Appends an ordering condition.
+         */
+        @Override
+        void appendToOrderBy(final StringBuilder buffer) {
+            final double value = values.iterator().next().doubleValue();
+            buffer.append("ABS(").append(column).append('-').append(value).append("), ");
+        }
+    }
+
+    /**
+     * Returns {@code true} if the given object implements the given interface, ignoring
interfaces
+     * that are not subtypes of {@link #declaredType}. This method is used for avoiding confusion
if
+     * the given object implements more than one GeoAPI interfaces. Test order matter: CRS
should be
+     * tested first in order to have precedence over datum types.
+     */
+    private boolean isInstance(final Class<? extends IdentifiedObject> type, final
IdentifiedObject object) {
+        return (declaredType == null || type.isAssignableFrom(declaredType)) && type.isInstance(object);
+    }
+
+    /**
      * Returns a set of authority codes that <strong>may</strong> identify the
same object than the specified one.
      * This implementation tries to get a smaller set than what {@link EPSGDataAccess#getAuthorityCodes(Class)}
      * would produce. Deprecated objects must be last in iteration order.
      */
     @Override
     protected Set<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException
{
-        /*
-         * SQL query will be of the form shown below, except that the WHERE clause will be
modified
-         * to accommodate the cases where <filters> is a floating point value or a
list of integers.
-         *
-         *     SELECT <codeColumn> FROM <table> WHERE <where> = <filters>
-         */
-        final String      where;                // Column to use for filtering, or null if
none.
-        final TableInfo   table;                // Contains 'codeColumn' and 'table' names.
-        final Set<Number> filters;              // Values to put in the WHERE clause,
or null if none.
-        boolean isFloat = false;                // Whether 'filters' shall be handled as
a floating point value.
-        Set<String>   namePatterns = null;      // SQL patterns for filtering by names,
or null for no filtering.
-        StringBuilder buffer       = null;      // Temporary buffer for building SQL queries,
created when first needed.
-        if (object instanceof Ellipsoid) {
-            where   = "SEMI_MAJOR_AXIS";
-            table   = TableInfo.ELLIPSOID;
-            filters = Collections.singleton(((Ellipsoid) object).getSemiMajorAxis());
-            isFloat = true;
-        } else {
-            final IdentifiedObject dependency;
-            if (object instanceof GeneralDerivedCRS) {
-                dependency = ((GeneralDerivedCRS) object).getBaseCRS();
-                where      = "SOURCE_GEOGCRS_CODE";
-                table      = TableInfo.CRS;
-            } else if (object instanceof SingleCRS) {
-                dependency = ((SingleCRS) object).getDatum();
-                where      = "DATUM_CODE";
-                table      = TableInfo.CRS;
-            } else if (object instanceof Datum) {
-                table      = TableInfo.DATUM;
-                if (object instanceof GeodeticDatum) {
-                    dependency = ((GeodeticDatum) object).getEllipsoid();
-                    where      = "ELLIPSOID_CODE";
-                } else {
-                    dependency = null;
-                    where      = null;
-                    if (object instanceof VerticalDatum) {
-                        final VerticalDatumType type = ((VerticalDatum) object).getVerticalDatumType();
-                        if (type != null && !type.equals(EPSGDataAccess.VERTICAL_DATUM_TYPE))
{
-                            return Collections.emptySet();
+        final TableInfo   table;                        // Contains `codeColumn` and `table`
names.
+        final Condition[] filters;                      // Conditions to put in the WHERE
clause.
+crs:    if (isInstance(CoordinateReferenceSystem.class, object)) {
+            /*
+             * For compound CRS, the SQL statement may be something like below
+             *
+             *   SELECT COORD_REF_SYS_CODE FROM "Coordinate Reference System"
+             *     WHERE CAST(COORD_REF_SYS_KIND AS VARCHAR(80)) LIKE 'compound%'
+             *       AND CMPD_HORIZCRS_CODE IN (?,…)
+             *       AND CMPD_VERTCRS_CODE IN (?,…)
+             */
+            table = TableInfo.CRS;
+            if (isInstance(CompoundCRS.class, object)) {
+                final List<CoordinateReferenceSystem> components = ((CompoundCRS) object).getComponents();
+                if (components != null) switch (components.size()) {        // Paranoiac
check.
+                    case 1: return getCodeCandidates(components.get(0));    // Should not
happen.
+                    case 2: {
+                        filters = new Condition[2];
+                        for (int i=0; i<=1; i++) {
+                            if ((filters[i] = dependencies((i==0) ? "CMPD_HORIZCRS_CODE"
: "CMPD_VERTCRS_CODE",
+                                    CoordinateReferenceSystem.class, components.get(i), false))
== null)
+                            {
+                                return Collections.emptySet();
+                            }
                         }
+                        break crs;
                     }
                 }
-                /*
-                 * We currently have no better way to filter datum (or reference frames)
than their names.
-                 * Filtering must be at least as tolerant as AbstractDatum.isHeuristicMatchForName(String).
-                 * We initialize a larger StringBuilder since SQL query using names may be
long.
-                 */
-                buffer = new StringBuilder(350);
-                namePatterns = new LinkedHashSet<>();
-                namePatterns.add(toDatumPattern(object.getName().getCode(), buffer));
-                for (final GenericName id : object.getAlias()) {
-                    namePatterns.add(toDatumPattern(id.tip().toString(), buffer));
-                }
+            }
+            /*
+             * For Coordinate Reference System, the SQL statement may be something like below
+             * (with DATUM_CODE replaced by SOURCE_GEOGCRS_CODE in a projected CRS):
+             *
+             *   SELECT COORD_REF_SYS_CODE FROM "Coordinate Reference System"
+             *     WHERE CAST(COORD_REF_SYS_KIND AS VARCHAR(80)) LIKE 'geographic%'
+             *       AND DATUM_CODE IN (?,…) AND DEPRECATED=0
+             *     ORDER BY COORD_REF_SYS_CODE
+             */
+            final Condition filter;
+            if (object instanceof GeneralDerivedCRS) {              // No need to use isInstance(Class,
Object) from here.
+                filter = dependencies("SOURCE_GEOGCRS_CODE", SingleCRS.class, ((GeneralDerivedCRS)
object).getBaseCRS(), true);
+            } else if (object instanceof GeodeticCRS) {
+                filter = dependencies("DATUM_CODE", GeodeticDatum.class, ((GeodeticCRS) object).getDatum(),
true);
+            } else if (object instanceof VerticalCRS) {
+                filter = dependencies("DATUM_CODE", VerticalDatum.class, ((VerticalCRS) object).getDatum(),
true);
+            } else if (object instanceof TemporalCRS) {
+                filter = dependencies("DATUM_CODE", TemporalDatum.class, ((TemporalCRS) object).getDatum(),
true);
+            } else if (object instanceof SingleCRS) {
+                filter = dependencies("DATUM_CODE", Datum.class, ((SingleCRS) object).getDatum(),
true);
             } else {
-                // Not a supported type. Returns all codes.
-                return super.getCodeCandidates(object);
+                return Collections.emptySet();
             }
+            if (filter == null) {
+                return Collections.emptySet();
+            }
+            filters = new Condition[] {filter};
+        } else if (isInstance(Datum.class, object)) {
             /*
-             * Search for the dependency.  The super.find(…) method performs a check (not
documented in public API)
-             * for detecting when it is invoked recursively, which is the case here. Consequently
the super.find(…)
-             * behavior below is slightly different than usual: since invoked recursively,
super.find(…) checks the
-             * cache of the ConcurrentAuthorityFactory wrapper. If found, the dependency
will also be stored in the
-             * cache. This is desirable since this method may be invoked (indirectly) in
a loop for many CRS objects
-             * sharing the same CoordinateSystem or Datum dependencies.
+             * We currently have no better way to filter datum (or reference frames) than
their names.
+             * Filtering must be at least as tolerant as AbstractDatum.isHeuristicMatchForName(String).
+             * The SQL statement will be something like below:
+             *
+             *   SELECT DATUM_CODE FROM "Datum"
+             *    WHERE ELLIPSOID_CODE IN (?,…)
+             *      AND (LOWER(DATUM_NAME) LIKE '?%')
              */
-            if (dependency != null) {
-                final boolean previous = isIgnoringAxes();
-                final Set<IdentifiedObject> find;
-                try {
-                    setIgnoringAxes(true);
-                    find = find(dependency);
-                } finally {
-                    setIgnoringAxes(previous);
-                }
-                filters = new LinkedHashSet<>(Containers.hashMapCapacity(find.size()));
-                for (final IdentifiedObject dep : find) {
-                    Identifier id = IdentifiedObjects.getIdentifier(dep, Citations.EPSG);
-                    if (id != null) try {           // Should never be null, but let be safe.
-                        filters.add(Integer.parseInt(id.getCode()));
-                    } catch (NumberFormatException e) {
-                        Logging.recoverableException(Logging.getLogger(Loggers.CRS_FACTORY),
EPSGCodeFinder.class, "getCodeCandidates", e);
-                    }
-                }
-                if (filters.isEmpty()) {
-                    // Dependency not found.
+            table = TableInfo.DATUM;
+            if (isInstance(GeodeticDatum.class, object)) {
+                filters = new Condition[] {
+                    dependencies("ELLIPSOID_CODE", Ellipsoid.class, ((GeodeticDatum) object).getEllipsoid(),
true),
+                    Condition.NAME
+                };
+                if (filters[0] == null) {
                     return Collections.emptySet();
                 }
             } else {
-                filters = null;
+                if (isInstance(VerticalDatum.class, object)) {
+                    final VerticalDatumType type = ((VerticalDatum) object).getVerticalDatumType();
+                    if (type != null && !type.equals(EPSGDataAccess.VERTICAL_DATUM_TYPE))
{
+                        return Collections.emptySet();
+                    }
+                }
+                filters = new Condition[] {
+                    Condition.NAME
+                };
             }
+        } else if (isInstance(Ellipsoid.class, object)) {
+            /*
+             * The SQL query will be something like below:
+             *
+             *   SELECT ELLIPSOID_CODE FROM "Ellipsoid"
+             *     WHERE SEMI_MAJOR_AXIS >= ?-ε AND SEMI_MAJOR_AXIS <= ?+ε
+             *     ORDER BY ABS(SEMI_MAJOR_AXIS-?)
+             */
+            table   = TableInfo.ELLIPSOID;
+            filters = new Condition[] {
+                new FloatCondition("SEMI_MAJOR_AXIS", ((Ellipsoid) object).getSemiMajorAxis())
+            };
+        } else {
+            // Not a supported type. Returns all codes.
+            return super.getCodeCandidates(object);
         }
         /*
-         * At this point we collected the information needed for creating the main SQL query.
We need an
-         * additional query if we are going to filter by names, since we will need to take
aliases in account.
+         * At this point we collected the information needed for creating the main SQL query.
+         * If the filters include a filter by names, we will need to take aliases in account.
+         * The following block prepares in advance the SQL query that we will need to execute,
+         * but does not execute it now. Note that this block overwrites the `buffer` content,
+         * so that buffer shall not contain valuable information yet.
          */
-        if (buffer == null) {
-            buffer = new StringBuilder(200);
-        }
+        final StringBuilder buffer = new StringBuilder(350);     // Temporary buffer for
building SQL query.
+        final Set<String> namePatterns;
         final String aliasSQL;
-        if (namePatterns != null) {
+        if (ArraysExt.containsIdentity(filters, Condition.NAME)) {
+            namePatterns = new LinkedHashSet<>();
+            namePatterns.add(toDatumPattern(object.getName().getCode(), buffer));
+            for (final GenericName id : object.getAlias()) {
+                namePatterns.add(toDatumPattern(id.tip().toString(), buffer));
+            }
             buffer.setLength(0);
-            buffer.append("SELECT OBJECT_CODE FROM [Alias] WHERE OBJECT_TABLE_NAME='Datum'");
+            buffer.append("SELECT OBJECT_CODE FROM [Alias] WHERE OBJECT_TABLE_NAME='").append(table.unquoted()).append("'
AND ");
             // PostgreSQL does not require explicit cast when the value is a literal instead
than "?".
-            String separator = " AND (";
-            for (final String pattern : namePatterns) {
-                appendFilterByName(buffer.append(separator), "ALIAS", pattern);
-                separator = " OR ";
-            }
-            aliasSQL = dao.translator.apply(buffer.append(')').toString());
+            appendFilterByName(namePatterns, "ALIAS", buffer);
+            aliasSQL = dao.translator.apply(buffer.toString());
+            buffer.setLength(0);
         } else {
+            namePatterns = null;
             aliasSQL = null;
         }
         /*
-         * Prepare the first part of SQL statement:
+         * Prepare the first part of SQL statement, which may be like below:
          *
-         *    SELECT <codeColumn> FROM <table> WHERE <where> = <filters>
+         *    SELECT <codeColumn> FROM <table>
+         *      WHERE CAST(<typeColumn> AS VARCHAR(80)) LIKE 'type%'
+         *        AND <filter.column> IN (<filter.values>)
+         *        AND (LOWER(<nameColumn>) LIKE '<name>%')
          *
-         * The filters depend on whether the search criterion is any code in a list of EPSG
codes or a numeric value.
-         * In the later case, the numeric value is assumed a linear distance in metres and
the tolerance threshold is
-         * 1 cm for a planet of the size of Earth.
+         * The query is assembled in the `buffer`. The first WHERE condition specifies the
desired type.
+         * That condition may be absent. The next conditions specify desired values. It may
be EPSG codes
+         * of dependencies or parameter values as floating points. The last condition is
on the object name.
+         * It may be absent (typically, only datums or reference frames have that condition).
          */
-        buffer.setLength(0);
         buffer.append("SELECT ").append(table.codeColumn).append(" FROM ").append(table.table);
-        if (filters != null) {
-            table.where(object.getClass(), buffer);
-            buffer.append(where);
-            if (isFloat) {
-                final double value = filters.iterator().next().doubleValue();
-                final double tolerance = Math.abs(value * (Formulas.LINEAR_TOLERANCE / ReferencingServices.AUTHALIC_RADIUS));
-                buffer.append(">=").append(value - tolerance).append(" AND ").append(where)
-                      .append("<=").append(value + tolerance);
-            } else {
-                String separator = " IN (";
-                for (Number code : filters) {
-                    buffer.append(separator).append(code.intValue());
-                    separator = ",";
-                }
-                buffer.append(')');
-            }
+        table.where(object.getClass(), buffer);         // Unconditionally append a "WHERE"
clause.
+        boolean isNext = false;
+        for (final Condition filter : filters) {
+            isNext |= filter.appendToWhere(buffer, isNext);
         }
         /*
          * We did not finished to build the SQL query, but the remaining part may require
a JDBC connection.
@@ -240,36 +434,30 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
          */
         try (Statement stmt = dao.connection.createStatement()) {
             if (namePatterns != null) {
-                String separator = (where == null) ? " WHERE (" : " AND (";
-                for (final String pattern : namePatterns) {
-                    appendFilterByName(buffer.append(separator), table.nameColumn, pattern);
-                    separator = " OR ";
-                }
-                boolean hasAlias = false;
+                if (isNext) buffer.append(" AND ");
+                isNext = false;
+                appendFilterByName(namePatterns, table.nameColumn, buffer);
                 try (ResultSet result = stmt.executeQuery(aliasSQL)) {
                     while (result.next()) {
                         final int code = result.getInt(1);
                         if (!result.wasNull()) {            // Should never be null but we
are paranoiac.
-                            buffer.append(separator);
-                            if (!hasAlias) {
-                                hasAlias = true;
-                                buffer.append(table.codeColumn).append(" IN (");
+                            if (!isNext) {
+                                isNext = true;
+                                buffer.append(" OR ").append(table.codeColumn).append(" IN
(");
+                            } else {
+                                buffer.append(',');
                             }
                             buffer.append(code);
-                            separator = ", ";
                         }
                     }
                 }
-                if (hasAlias) buffer.append(')');
-                buffer.append(')');
+                if (isNext) buffer.append(')');
             }
             buffer.append(getSearchDomain() == Domain.ALL_DATASET
                           ? " ORDER BY ABS(DEPRECATED), "
                           : " AND DEPRECATED=0 ORDER BY ");     // Do not put spaces around
"=" - SQLTranslator searches for this exact match.
-            if (isFloat) {
-                @SuppressWarnings("null")
-                final double value = filters.iterator().next().doubleValue();
-                buffer.append("ABS(").append(where).append('-').append(value).append("),
");
+            for (final Condition filter : filters) {
+                filter.appendToOrderBy(buffer);
             }
             buffer.append(table.codeColumn);          // Only for making order determinist.
             /*
@@ -285,7 +473,7 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
             result.remove(null);                    // Should not have null element, but
let be safe.
             if (result.size() > 1) {
                 final Object[] id = result.toArray();
-                if (dao.sort(table.tableName(), id)) {
+                if (dao.sort(table.unquoted(), id)) {
                     result.clear();
                     for (final Object c : id) {
                         result.add((String) c);
@@ -294,7 +482,7 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
             }
             return result;
         } catch (SQLException exception) {
-            throw dao.databaseFailure(Identifier.class, String.valueOf(CollectionsExt.first(filters)),
exception);
+            throw dao.databaseFailure(Identifier.class, String.valueOf(CollectionsExt.first(filters[0].values)),
exception);
         }
     }
 
@@ -326,9 +514,25 @@ final class EPSGCodeFinder extends IdentifiedObjectFinder {
 
     /**
      * Appends to the given buffer the SQL statement for filtering datum names using a pattern
created by
-     * {@link #toDatumPattern(String, StringBuilder)}.
+     * {@link #toDatumPattern(String, StringBuilder)}. This method append a SQL fragment
like below:
+     *
+     * {@preformat sql
+     *     (LOWER(<column>) LIKE '<pattern>' OR …)
+     * }
+     *
+     * This method assumes that {@code namePatterns} contains at least one element.
+     *
+     * @param  namePatterns  the patterns created by {@link #toDatumPattern(String, StringBuilder)}.
+     * @param  column        column where the search for the names.
+     * @param  buffer        buffer where to add the SQL fragment.
      */
-    private static void appendFilterByName(final StringBuilder buffer, final String column,
final String pattern) {
-        buffer.append("LOWER(").append(column).append(") LIKE '").append(pattern).append('\'');
+    private static void appendFilterByName(final Set<String> namePatterns, final String
column, final StringBuilder buffer) {
+        String separator = "(";
+        for (final String pattern : namePatterns) {
+            buffer.append(separator).append("LOWER(").append(column)
+                  .append(") LIKE '").append(pattern).append('\'');
+            separator = " OR ";
+        }
+        buffer.append(')');
     }
 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
index 53b991f..62fc01d 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -164,7 +164,7 @@ final class TableInfo {
     /**
      * The table name for SQL queries. May contains a {@code "JOIN"} clause.
      *
-     * @see #tableName()
+     * @see #unquoted()
      */
     final String table;
 
@@ -226,7 +226,7 @@ final class TableInfo {
     /**
      * Returns the table name without brackets.
      */
-    final String tableName() {
+    final String unquoted() {
         return table.substring(1, table.length() - 1);
     }
 
diff --git a/src/main/config/logging.properties b/src/main/config/logging.properties
index ff0136c..73a9b06 100644
--- a/src/main/config/logging.properties
+++ b/src/main/config/logging.properties
@@ -26,13 +26,12 @@ org.apache.sis.level = FINE
 # FINE provides debugging information normally hidden in production.
 java.util.logging.ConsoleHandler.level = FINE
 
-# MonolineFormatter is optional and specific to Apache SIS. Its default configuration
-# does not show source class and method names. We use this configuration because more
-# complete information are saved in the "workspace/java.log" file. Uncomment the next
-# line if class and method names are desired. See MonolineFormatter class javadoc for
-# more information about configuration options.
+# MonolineFormatter is optional and specific to Apache SIS. Its default configuration does
+# not show source class and method names.  The second line below configures that formatter
+# for showing simple class names. That line can be commented-out if more compact output is
+# desired. See MonolineFormatter class javadoc for more information about options.
 java.util.logging.ConsoleHandler.formatter = org.apache.sis.util.logging.MonolineFormatter
-#org.apache.sis.util.logging.MonolineFormatter.source = class.method
+org.apache.sis.util.logging.MonolineFormatter.source = class:short
 
 # Copy the logs in a file in the working directory. Those logs contain time stamp
 # together with source class and method names.  The file has unlimited length and


Mime
View raw message