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 d430daadd1 Replace the `LIKE` statement by collation when searching 
CRS by name. This is faster (allow to use the database index), give better 
results (ignore cases and accents) and simpler (remove checks for false 
positives).
d430daadd1 is described below

commit d430daadd1112729430d204ed9b294f7a7da7fb1
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Oct 15 18:54:34 2025 +0200

    Replace the `LIKE` statement by collation when searching CRS by name.
    This is faster (allow to use the database index), give better results
    (ignore cases and accents) and simpler (remove checks for false positives).
---
 .../org/apache/sis/metadata/sql/Installer.java     |   9 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |   2 +-
 .../sql/internal/shared/LocalDataSource.java       |  51 ++++---
 .../metadata/sql/internal/shared/SQLUtilities.java | 109 +--------------
 .../metadata/sql/internal/shared/ScriptRunner.java | 122 +++++++++++++---
 .../sis/metadata/sql/internal/shared/Syntax.java   |   2 +-
 .../metadata/sql/internal/shared/TypeMapper.java   |  56 +++-----
 .../sql/internal/shared/SQLUtilitiesTest.java      |  32 +----
 .../sql/internal/shared/ScriptRunnerTest.java      |   2 +
 .../sql/internal/shared/TypeMapperTest.java        |  48 -------
 .../referencing/factory/sql/AuthorityCodes.java    |  34 ++---
 .../referencing/factory/sql/EPSGCodeFinder.java    |  68 +++++----
 .../referencing/factory/sql/EPSGDataAccess.java    | 153 +++++++++++----------
 .../sis/referencing/factory/sql/EPSGInstaller.java |  57 +++-----
 .../sis/referencing/factory/sql/SQLTranslator.java |   8 +-
 .../referencing/factory/sql/EPSGFactoryTest.java   |   8 +-
 .../sis/storage/sql/feature/InfoStatements.java    |  29 ++--
 .../org/apache/sis/storage/geopackage/Content.java |   2 +-
 .../sis/storage/geopackage/GpkgStoreProvider.java  |   2 +-
 .../sis/resources/embedded/EmbeddedResources.java  |   9 +-
 .../sis/referencing/factory/sql/epsg/Prepare.sql   |   6 +
 .../sis/referencing/factory/sql/epsg/Changes.md    |  46 +++++++
 .../sis/referencing/factory/sql/epsg/README.md     |  70 ++--------
 23 files changed, 413 insertions(+), 512 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Installer.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Installer.java
index 23687c082c..b8246afce8 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Installer.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/Installer.java
@@ -20,7 +20,6 @@ import java.io.IOException;
 import java.sql.Connection;
 import java.sql.SQLException;
 import org.apache.sis.metadata.sql.internal.shared.ScriptRunner;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.StringBuilders;
 
 
@@ -81,16 +80,16 @@ final class Installer extends ScriptRunner {
     }
 
     /**
-     * Invoked for each line of the SQL installation script to execute.
+     * Invoked for each statement of the <abbr>SQL</abbr> installation script 
which creates a table.
      * If the database does not support enumerations, replaces enumeration 
columns by {@code VARCHAR}.
      */
     @Override
-    protected int execute(final StringBuilder sql) throws SQLException, 
IOException {
-        if (!isEnumTypeSupported && CharSequences.startsWith(sql, "CREATE 
TABLE", true)) {
+    protected void editTableCreation(final StringBuilder sql) {
+        super.editTableCreation(sql);
+        if (!isEnumTypeSupported) {
             for (final String type : enumTypes) {
                 StringBuilders.replace(sql, type, "VARCHAR(25)");
             }
         }
-        return super.execute(sql);
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
index cac410fe31..16850c2092 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
@@ -468,7 +468,7 @@ public class MetadataSource implements AutoCloseable {
                 return;
             }
         }
-        final Installer installer = new Installer(connection);
+        final var installer = new Installer(connection);
         installer.run();
     }
 
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/LocalDataSource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/LocalDataSource.java
index c9af2161c6..5e218446d6 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/LocalDataSource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/LocalDataSource.java
@@ -29,6 +29,7 @@ import java.nio.file.Path;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
+import java.util.concurrent.Callable;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.system.Loggers;
@@ -56,8 +57,8 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
     private static final String DERBY_HOME_KEY = "derby.system.home";
 
     /**
-     * The database product to use. Currently supported values are
-     * {@link Dialect#DERBY} and {@link Dialect#HSQL}.
+     * The database product to use.
+     * Currently supported values are {@link Dialect#DERBY} and {@link 
Dialect#HSQL}.
      */
     private final Dialect dialect;
 
@@ -79,8 +80,9 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
     final boolean create;
 
     /**
-     * Prepares a new data source for the given database file. This 
construction is incomplete;
-     * after return either {@link #initialize()} shall be invoked or this 
{@code LocalDataSource} is discarded.
+     * Prepares a new data source for the given database file. This 
construction is incomplete:
+     * the {@link #initialize()} method shall be invoked after construction,
+     * unless the caller decides to discard this {@code LocalDataSource} 
instance.
      *
      * @param  dialect  {@link Dialect#DERBY} or {@link Dialect#HSQL}.
      * @param  dbFile   path to the database to open on the local file system.
@@ -146,7 +148,7 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
                  * SIS_DATA and get the impression that their setting is 
ignored.
                  */
                 final Path path = Path.of(home);
-                create = !Files.exists(path.resolve(database)) && 
Files.isDirectory(path);
+                create = Files.notExists(path.resolve(database)) && 
Files.isDirectory(path);
                 dbFile = database;
             } else {
                 continue;
@@ -165,23 +167,23 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
 
     /**
      * Wraps an existing data source for adding a shutdown method to it.
-     * This method is used for source of data embedded in a separated JAR file.
+     * This method is used for source of data embedded in a separated 
<abbr>JAR</abbr> file.
      *
-     * @param  ds  the data source, usually given by {@link 
Initializer#embedded()}.
+     * @param  source  the data source, usually given by {@link 
Initializer#embedded()}.
      * @return the data source wrapped with a shutdown method, or {@code ds}.
      */
-    static DataSource wrap(final DataSource ds) {
+    static DataSource wrap(final DataSource source) {
         final Dialect dialect;
-        final String cn = ds.getClass().getName();
+        final String cn = source.getClass().getName();
         if (cn.startsWith("org.apache.derby.")) {
             dialect = Dialect.DERBY;
         } else if (cn.startsWith("org.hsqldb.")) {
             dialect = Dialect.HSQL;
         } else {
-            return ds;
+            return source;
         }
-        final LocalDataSource local = new LocalDataSource(dialect, null, 
false);
-        local.source = ds;
+        final var local = new LocalDataSource(dialect, null, false);
+        local.source = source;
         return local;
     }
 
@@ -245,29 +247,36 @@ public final class LocalDataSource implements DataSource, 
Comparable<LocalDataSo
     }
 
     /**
-     * Creates the database if needed. For Derby we need to explicitly allow 
creation.
+     * Creates the database if needed.
+     * For Derby we need to explicitly allow creation.
      * For HSQLDB the creation is enabled by default.
      */
     final void createDatabase() throws Exception {
         if (create) {
-            final Method enabler;
+            Callable<?> finisher = null;
             switch (dialect) {
+                // More cases may be added in future versions.
                 case DERBY: {
-                    enabler = source.getClass().getMethod("setCreateDatabase", 
String.class);
-                    enabler.invoke(source, "create");
+                    final Class<?> c = source.getClass();
+                    final Method setter = c.getMethod("setCreateDatabase", 
String.class);
+                    finisher = () -> setter.invoke(source, "no");      // Any 
value other than "create".
+                    setter.invoke(source, "create");
+                    /*
+                     * Make the database uses case-insensitive and 
accent-insensitive searches.
+                     * 
https://db.apache.org/derby/docs/10.17/devguide/tdevdvlpcaseinscoll.html
+                     */
+                    c.getMethod("setConnectionAttributes", String.class)
+                            .invoke(source, 
"territory=en_GB;collation=TERRITORY_BASED:PRIMARY");
                     break;
                 }
-                // More cases may be added in future versions.
-                default: enabler = null; break;
             }
             try (Connection c = source.getConnection()) {
                 for (Initializer init : Initializer.load()) {
                     init.createSchema(c);
                 }
             } finally {
-                switch (dialect) {
-                    // More cases may be added in future versions.
-                    case DERBY: enabler.invoke(source, "no"); break;        // 
Any value other than "create".
+                if (finisher != null) {
+                    finisher.call();
                 }
             }
         }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/SQLUtilities.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/SQLUtilities.java
index a238fe525a..a9fe257f96 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/SQLUtilities.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/SQLUtilities.java
@@ -19,9 +19,6 @@ package org.apache.sis.metadata.sql.internal.shared;
 import java.sql.SQLException;
 import java.sql.SQLDataException;
 import java.sql.DatabaseMetaData;
-import org.apache.sis.util.Characters;
-import org.apache.sis.util.CharSequences;
-import org.apache.sis.util.Workaround;
 import org.apache.sis.util.internal.shared.Strings;
 import org.apache.sis.util.resources.Errors;
 
@@ -58,12 +55,12 @@ public final class SQLUtilities {
     }
 
     /**
-     * Converts the given string to a boolean value, or returns {@code null} 
if the value is unrecognized.
+     * Converts the given string to a Boolean value, or returns {@code null} 
if the value is unrecognized.
      * This method recognizes "true", "false", "yes", "no", "t", "f", 0 and 1 
(case insensitive).
      * An empty string is interpreted as {@code null}.
      *
-     * @param  text  the characters to convert to a boolean value, or {@code 
null}.
-     * @return the given characters as a boolean value, or {@code null} if the 
given text was null or empty.
+     * @param  text  the characters to convert to a Boolean value, or {@code 
null}.
+     * @return the given characters as a Boolean value, or {@code null} if the 
given text was null or empty.
      * @throws SQLDataException if the given text is non-null and non-empty 
but not recognized.
      *
      * @see Boolean#parseBoolean(String)
@@ -115,7 +112,7 @@ public final class SQLUtilities {
      * @param  escape  value of {@link 
DatabaseMetaData#getSearchStringEscape()}. May be null or empty.
      * @return the given text with wildcard characters escaped.
      */
-    public static String escape(final String text, final String escape) {
+    public static String escapeWildcards(final String text, final String 
escape) {
         if (text != null && !Strings.isNullOrEmpty(escape)) {
             final char escapeChar = escape.charAt(0);
             StringBuilder buffer = null;
@@ -134,102 +131,4 @@ public final class SQLUtilities {
         }
         return text;
     }
-
-    /**
-     * Returns a string like the given string but with accented letters 
replaced by any character ({@code '_'})
-     * and all characters that are not letter or digit replaced by the 
wildcard ({@code '%'}).
-     *
-     * @param  text         the text to get as a SQL LIKE pattern.
-     * @param  toLowerCase  whether to convert characters to lower case.
-     * @param  escape       value of {@link 
DatabaseMetaData#getSearchStringEscape()}. May be null or empty.
-     * @return the {@code LIKE} pattern for the given text.
-     */
-    public static String toLikePattern(final String text, final boolean 
toLowerCase, final String escape) {
-        final var buffer = new StringBuilder(text.length());
-        toLikePattern(text, 0, text.length(), false, toLowerCase, escape, 
buffer);
-        return buffer.toString();
-    }
-
-    /**
-     * Returns a <abbr>SQL</abbr> LIKE pattern for the given text. The text is 
optionally returned in all lower cases
-     * for allowing case-insensitive searches. Punctuations are replaced by 
any sequence of characters ({@code '%'})
-     * and non-<abbr>ASCII</abbr> Latin letters are replaced by any single 
character ({@code '_'}).
-     * Ideograms (Japanese, Chinese, …) and hiragana (Japanese) are kept 
unchanged.
-     * This method avoids to put a {@code '%'} symbol as the first character
-     * because such character prevents some databases to use their index.
-     *
-     * @param  text         the text to get as a <abbr>SQL</abbr> {@code LIKE} 
pattern.
-     * @param  textStart    index of the first character to use in the given 
{@code text}.
-     * @param  textEnd      index after the last character to use in the given 
{@code text}.
-     * @param  allowSuffix  whether to append a final {@code '%'} wildcard at 
the end of the pattern.
-     * @param  toLowerCase  whether to convert characters to lower case.
-     * @param  escape       value of {@link 
DatabaseMetaData#getSearchStringEscape()}. May be null or empty.
-     * @param  buffer       buffer where to append the <abbr>SQL</abbr> {@code 
LIKE} pattern.
-     */
-    public static void toLikePattern(final String text, int textStart, final 
int textEnd, final boolean allowSuffix,
-                                     final boolean toLowerCase, final String 
escape, final StringBuilder buffer)
-    {
-        final int bufferStart = buffer.length();
-        while (textStart < textEnd) {
-            final int c = text.codePointAt(textStart);
-            if (Character.isLetterOrDigit(c)) {
-                // Ignore accented letters and Greek letters (before `U+0400`) 
in the search.
-                if (c < 0x80 || c >= 0x400) {
-                    buffer.appendCodePoint(toLowerCase ? 
Character.toLowerCase(c) : c);
-                } else {
-                    appendIfNotRedundant(buffer, '_');
-                }
-            } else {
-                final int length = buffer.length();
-                if (length == bufferStart) {
-                    // Do not use wildcard in the first character.
-                    if (escape != null && (c == '%' || c == '_' || 
text.startsWith(escape, textStart))) {
-                        // Note: there will be bug if `escape` is a repetition 
of the same character.
-                        // But we assume that this corner case is too rare for 
being worth a check.
-                        buffer.append(escape);
-                    }
-                    buffer.appendCodePoint(c);
-                } else if (buffer.charAt(length - 1) != '%') {
-                    buffer.append('%');
-                }
-            }
-            textStart += Character.charCount(c);
-        }
-        if (allowSuffix) {
-            appendIfNotRedundant(buffer, '%');
-        }
-        for (int i=bufferStart; (i = buffer.indexOf("_%", i)) >= 0;) {
-            buffer.deleteCharAt(i);
-        }
-    }
-
-    /**
-     * Appends the given wildcard character to the given buffer if the buffer 
does not ends with {@code '%'}.
-     */
-    private static void appendIfNotRedundant(final StringBuilder buffer, final 
char wildcard) {
-        final int length = buffer.length();
-        if (length == 0 || buffer.charAt(length - 1) != '%') {
-            buffer.append(wildcard);
-        }
-    }
-
-    /**
-     * Workaround for what seems to be a Derby 10.11 bug, which seems to 
behave as if the LIKE pattern
-     * had a trailing % wildcard. This can be verified with the following 
query on the EPSG database:
-     *
-     * {@snippet lang="sql" :
-     *     SELECT COORD_REF_SYS_CODE, COORD_REF_SYS_NAME FROM EPSG."Coordinate 
Reference System"
-     *      WHERE COORD_REF_SYS_NAME LIKE 'NTF%Paris%Lambert%zone%I'
-     *     }
-     *
-     * which returns "NTF (Paris) / Lambert zone I" as expected but also zones 
II and III.
-     *
-     * @param  expected  the string to search.
-     * @param  actual    the string found in the database.
-     * @return {@code true} if the given string can be accepted.
-     */
-    @Workaround(library = "Derby", version = "10.11")
-    public static boolean filterFalsePositive(final String expected, final 
String actual) {
-        return CharSequences.equalsFiltered(expected, actual, 
Characters.Filter.LETTERS_AND_DIGITS, false);
-    }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/ScriptRunner.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/ScriptRunner.java
index 0223fbe946..84aefe219b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/ScriptRunner.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/ScriptRunner.java
@@ -55,6 +55,26 @@ import org.apache.sis.util.resources.Errors;
  * @author  Johann Sorel (Geomatys)
  */
 public class ScriptRunner implements AutoCloseable {
+    /**
+     * The <abbr>SQL</abbr> {@value} keyword. In the current {@code 
ScriptRunner} implementation,
+     * this keyword must be last before the comma in the declaration of a 
column. Example:
+     *
+     * {@snippet lang="sql" :
+     * coord_sys_name VARCHAR(254) NOT NULL COLLATE "Ignore Accent and Case",
+     * }
+     *
+     * @see #editTableCreation(StringBuilder)
+     */
+    private static final String COLLATE = "COLLATE";
+
+    /**
+     * The <abbr>SQL</abbr> {@value} keyword. In the current {@code 
ScriptRunner} implementation,
+     * this keyword must be last before the end of a {@code CREATE TABLE} 
statement.
+     *
+     * @see #editTableCreation(StringBuilder)
+     */
+    private static final String INHERITS = "INHERITS";
+
     /**
      * The sequence for SQL comments. Leading lines starting by those 
characters will be ignored.
      */
@@ -98,7 +118,7 @@ public class ScriptRunner implements AutoCloseable {
     protected final String identifierQuote;
 
     /**
-     * {@code true} if the database supports enums.
+     * {@code true} if the database supports enumerations in the way used by 
Apache <abbr>SIS</abbr>.
      * Example:
      *
      * {@snippet lang="sql" :
@@ -117,6 +137,17 @@ public class ScriptRunner implements AutoCloseable {
      */
     protected final boolean isEnumTypeSupported;
 
+    /**
+     * {@code true} if the database supports collations in the way used by 
Apache <abbr>SIS</abbr>.
+     * The way to use collations vary a lot between databases, so the 
"<abbr>SIS</abbr> way" is the
+     * PostgreSQL way for now. A value of {@code false} does not necessarily 
means that the database
+     * does not support collations at all, but the database does not support 
{@code CREATE COLLATION}
+     * statements and collations declared in column definitions.
+     *
+     * @see #statementsToSkip
+     */
+    protected final boolean isCollationSupported;
+
     /**
      * The maximum number of rows allowed per {@code "INSERT"} statement.
      * This is 1 if the database does not support multi-rows insertion.
@@ -131,12 +162,13 @@ public class ScriptRunner implements AutoCloseable {
     private final Statement statement;
 
     /**
-     * If non-null, the SQL statements to skip (typically because not 
supported by the database).
+     * If non-null, the <abbr>SQL</abbr> statements to skip (typically because 
not supported by the database).
      * The matcher is built as an alternation of many regular expressions 
separated by the pipe symbol.
      * The list of statements to skip depends on which {@code is*Supported} 
fields are set to {@code true}:
      *
      * <ul>
      *   <li>{@link #isEnumTypeSupported} for {@code "CREATE TYPE …"} or 
{@code "CREATE CAST …"} statements.</li>
+     *   <li>{@link #isCollationSupported} for {@code "CREATE COLLATION …"} 
statements.</li>
      *   <li>{@link Dialect#supportsGrantUsageOnSchema} for {@code "GRANT 
USAGE ON SCHEMA …"} statements.</li>
      *   <li>{@link Dialect#supportsGrantSelectOnTable} for {@code "GRANT 
SELECT ON TABLE …"} statements.</li>
      *   <li>{@link Dialect#supportsComment} for {@code "COMMENT ON …"} 
statements.</li>
@@ -208,16 +240,19 @@ public class ScriptRunner implements AutoCloseable {
         statement = connection.createStatement();
         switch (dialect) {
             default: {
-                isEnumTypeSupported = false;
+                isEnumTypeSupported  = false;
+                isCollationSupported = false;
                 break;
             }
             case POSTGRESQL: {
                 final int version = metadata.getDatabaseMajorVersion();
-                isEnumTypeSupported = (version == 8) ? 
metadata.getDatabaseMinorVersion() >= 4 : version >= 8;
+                isEnumTypeSupported  = (version >=  9);
+                isCollationSupported = (version >= 15);     // Version when 
ICU collation provider is available.
                 break;
             }
             case HSQL: {
-                isEnumTypeSupported = false;
+                isEnumTypeSupported  = false;
+                isCollationSupported = false;
                 /*
                  * HSQLDB stores tables in memory by default. For storing the 
tables on files, we have to
                  * use "CREATE CACHED TABLE" statement, which is 
HSQL-specific. For avoiding SQL dialect,
@@ -237,6 +272,9 @@ public class ScriptRunner implements AutoCloseable {
         if (!isEnumTypeSupported) {
             addStatementToSkip("CREATE\\s+(?:TYPE|CAST)\\s+.*");
         }
+        if (!isCollationSupported) {
+            addStatementToSkip("CREATE\\s+COLLATION\\s+.*");
+        }
         if (!dialect.supportsAllGrants()) {
             addStatementToSkip("GRANT\\s+\\w+\\s+ON\\s+");
             if (dialect.supportsGrantUsageOnSchema()) {
@@ -271,6 +309,7 @@ public class ScriptRunner implements AutoCloseable {
      *
      * <ul>
      *   <li>{@code "CREATE TYPE …"} or {@code "CREATE CAST …"} if {@link 
#isEnumTypeSupported} is {@code false}.</li>
+     *   <li>{@code "CREATE COLLATION …"} if {@link #isCollationSupported} is 
{@code false}.</li>
      *   <li>{@code "GRANT USAGE ON SCHEMA …"} if {@link 
Dialect#supportsGrantUsageOnSchema} is {@code false}.</li>
      *   <li>{@code "GRANT SELECT ON TABLE …"} if {@link 
Dialect#supportsGrantSelectOnTable} is {@code false}.</li>
      *   <li>{@code "COMMENT ON …"} if {@link Dialect#supportsComment} is 
{@code false}.</li>
@@ -567,7 +606,7 @@ parseLine:  while (pos < length) {
      * @param  to    index after the last character of the fragment.
      * @return whether the given fragment seems outside quotes.
      */
-    private static boolean isOutsideQuotes(final CharSequence sql, int from, 
final int to) {
+    private static boolean isOutsideQuotes(final StringBuilder sql, int from, 
final int to) {
         int nq = 0, ni = 0;
         while (from < to) {
             switch (sql.charAt(from++)) {
@@ -588,6 +627,60 @@ parseLine:  while (pos < length) {
         return ((nq | ni) & 1) == 0;
     }
 
+    /**
+     * Invoked for each {@code CREATE TABLE} statement.
+     * The default implementation removes the declarations listed below if 
they are unsupported.
+     *
+     * <h4>Table inheritance</h4>
+     * Removes {@code INHERITS} declarations if they are unsupported by the 
target database.
+     * This method expects <abbr>SQL</abbr> statements for a PostgreSQL 
database like below.
+     * The {@code INHERITS} fragment must be last because everything after may 
be ignored:
+     *
+     * {@snippet lang="sql" :
+     *   CREATE TABLE metadata."Organisation" (
+     *     "ID"   VARCHAR(15) NOT NULL PRIMARY KEY,
+     *     "name" VARCHAR(120))
+     *     INHERITS (metadata."Party");
+     *   }
+     *
+     * <h4>Collation</h4>
+     * Removes {@code COLLATE} declarations if they are unsupported by the 
target database.
+     * This method expects <abbr>SQL</abbr> statements for a PostgreSQL 
database like below.
+     * The {@code COLLATE} fragment must be last before the comma because 
everything between
+     * the keyword and the comma will be ignored:
+     *
+     * {@snippet lang="sql" :
+     *   CREATE TABLE "Coordinate System" (
+     *     coord_sys_code INTEGER NOT NULL,
+     *     coord_sys_name VARCHAR(254) NOT NULL COLLATE "Ignore Accent and 
Case",
+     *     CONSTRAINT pk_coordinatesystem PRIMARY KEY (coord_sys_code))
+     *   }
+     *
+     * @param sql the statement from where to remove {@code COLLATE} 
declarations.
+     */
+    protected void editTableCreation(final StringBuilder sql) {
+        if (!dialect.supportsTableInheritance()) {
+            final int i = sql.lastIndexOf(INHERITS);
+            if (i >= 0 && isOutsideQuotes(sql, i + INHERITS.length(), 
sql.length())) {
+                sql.setLength(CharSequences.skipTrailingWhitespaces(sql, 0, 
i));
+            }
+        }
+        if (!isCollationSupported) {
+            int i = sql.length();
+            while ((i = sql.lastIndexOf(COLLATE, i - 1)) > 0) {
+                final int w = i + COLLATE.length();
+                final int s = sql.indexOf(",", w);
+                if (s > w && isOutsideQuotes(sql, w, s)) {
+                    if (Character.isWhitespace(sql.codePointAt(w)) &&
+                        Character.isWhitespace(sql.codePointBefore(i)))
+                    {
+                        sql.delete(i, s);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Returns {@code true} if the given SQL statements is supported by the 
database engine,
      * or {@code false} if this statement should be ignored. The default 
implementation checks
@@ -613,13 +706,12 @@ parseLine:  while (pos < length) {
     }
 
     /**
-     * Executes the given SQL statement.
+     * Executes the given <abbr>SQL</abbr> statement.
      * This method performs the following choices:
      *
      * <ul>
      *   <li>If {@link #isSupported(CharSequence)} returns {@code false}, then 
this method does nothing.</li>
-     *   <li>If the statement is {@code CREATE TABLE ... INHERITS ...} but the 
database does not support
-     *       table inheritance, then this method drops the {@code INHERITS 
...} part.</li>
+     *   <li>If the statement starts with {@code CREATE TABLE}, invokes {@link 
#editTableCreation(StringBuilder)}.</li>
      *   <li>If the {@code maxRowsPerInsert} argument given at construction 
time was zero,
      *       then this method skips {@code "INSERT INTO"} statements but 
executes all other.</li>
      *   <li>Otherwise this method executes the given statement with the 
following modification:
@@ -631,7 +723,7 @@ parseLine:  while (pos < length) {
      * Subclasses that override this method can freely edit the {@link 
StringBuilder} content before
      * to invoke this method.
      *
-     * @param  sql  the SQL statement to execute.
+     * @param  sql  the <abbr>SQL</abbr> statement to execute.
      * @return the number of rows added or modified as a result of the 
statement execution.
      * @throws SQLException if an error occurred while executing the SQL 
statement.
      * @throws IOException if an I/O operation was required and failed.
@@ -640,15 +732,11 @@ parseLine:  while (pos < length) {
         if (!isSupported(sql)) {
             return 0;
         }
-        String subSQL = currentSQL = 
CharSequences.trimWhitespaces(sql).toString();
-        if (!dialect.supportsTableInheritance() && subSQL.startsWith("CREATE 
TABLE")) {
-            final int s = sql.lastIndexOf("INHERITS");
-            if (s >= 0 && isOutsideQuotes(sql, s+8, sql.length())) {           
  // 8 is the length of "INHERITS".
-                sql.setLength(CharSequences.skipTrailingWhitespaces(sql, 0, 
s));
-                subSQL = currentSQL = sql.toString();
-            }
+        if (CharSequences.startsWith(sql, "CREATE TABLE", true)) {
+            editTableCreation(sql);
         }
         int count = 0;
+        String subSQL = currentSQL = 
CharSequences.trimWhitespaces(sql).toString();
         /*
          * The scripts usually do not contain any SELECT statement. One 
exception is the creation
          * of geometry columns in a PostGIS database, which use "SELECT 
AddGeometryColumn(…)".
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/Syntax.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/Syntax.java
index 71abf67e19..5638794a08 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/Syntax.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/Syntax.java
@@ -166,7 +166,7 @@ public class Syntax {
      * @return the given text with wildcard characters escaped.
      */
     public final String escapeWildcards(final String text) {
-        return SQLUtilities.escape(text, wildcardEscape);
+        return SQLUtilities.escapeWildcards(text, wildcardEscape);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/TypeMapper.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/TypeMapper.java
index 601660b935..b9df12b181 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/TypeMapper.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/internal/shared/TypeMapper.java
@@ -18,10 +18,11 @@ package org.apache.sis.metadata.sql.internal.shared;
 
 import java.util.Date;
 import java.sql.Types;
+import java.sql.JDBCType;
 
 
 /**
- * Maps a few basic Java types to JDBC types.
+ * Maps a few basic Java types to <abbr>JDBC</abbr> types.
  *
  * @author  Martin Desruisseaux (Geomatys)
  */
@@ -34,15 +35,15 @@ final class TypeMapper {
      * <p>The types declared here matches both the Derby and PostgreSQL 
mapping.</p>
      */
     private static final TypeMapper[] TYPES = {
-        new TypeMapper(Boolean.class, Types.BOOLEAN,   "BOOLEAN"),
-        new TypeMapper(Date   .class, Types.TIMESTAMP, "TIMESTAMP"),
-        new TypeMapper(Double .class, Types.DOUBLE,    "DOUBLE PRECISION"),
-        new TypeMapper(Float  .class, Types.REAL,      "REAL"),
-        new TypeMapper(Long   .class, Types.BIGINT,    "BIGINT"),
-        new TypeMapper(Integer.class, Types.INTEGER,   "INTEGER"),
-        new TypeMapper(Short  .class, Types.SMALLINT,  "SMALLINT"),
-        new TypeMapper(Byte   .class, Types.TINYINT,   "SMALLINT"),     // 
Derby does not support TINYINT.
-        new TypeMapper(Number .class, Types.DECIMAL,   "DECIMAL")       // 
Implemented by BigDecimal.
+        new TypeMapper(Boolean.class, JDBCType.BOOLEAN),
+        new TypeMapper(Date   .class, JDBCType.TIMESTAMP),
+        new TypeMapper(Double .class, JDBCType.DOUBLE),
+        new TypeMapper(Float  .class, JDBCType.REAL),
+        new TypeMapper(Long   .class, JDBCType.BIGINT),
+        new TypeMapper(Integer.class, JDBCType.INTEGER),
+        new TypeMapper(Short  .class, JDBCType.SMALLINT),
+        new TypeMapper(Byte   .class, JDBCType.TINYINT),
+        new TypeMapper(Number .class, JDBCType.DECIMAL)     // Implemented by 
BigDecimal.
     };
 
     /**
@@ -53,20 +54,14 @@ final class TypeMapper {
     /**
      * A constant from the SQL {@link Types} enumeration.
      */
-    private final int type;
-
-    /**
-     * The SQL keyword for that type.
-     */
-    private final String keyword;
+    private final JDBCType type;
 
     /**
      * For internal use only.
      */
-    private TypeMapper(final Class<?> classe, final int type, final String 
keyword) {
-        this.classe  = classe;
-        this.type    = type;
-        this.keyword = keyword;
+    private TypeMapper(final Class<?> classe, final JDBCType type) {
+        this.classe = classe;
+        this.type   = type;
     }
 
     /**
@@ -81,25 +76,14 @@ final class TypeMapper {
         if (classe != null) {
             for (final TypeMapper type : TYPES) {
                 if (type.classe.isAssignableFrom(classe)) {
-                    return type.keyword;
+                    switch (type.type) {
+                        case DOUBLE:  return "DOUBLE PRECISION";
+                        case TINYINT: return "SMALLINT";   // Derby does not 
support TINYINT.
+                        default: return type.type.name();
+                    }
                 }
             }
         }
         return null;
     }
-
-    /**
-     * Return the Java class for the given SQL type, or {@code null} if none.
-     *
-     * @param  type  one of the {@link Types} constants.
-     * @return the Java class, or {@code null} if none.
-     */
-    public static Class<?> toJavaType(final int type) {
-        for (final TypeMapper t : TYPES) {
-            if (t.type == type) {
-                return t.classe;
-            }
-        }
-        return null;
-    }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/SQLUtilitiesTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/SQLUtilitiesTest.java
index 2c8a911ea9..70330a2ec3 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/SQLUtilitiesTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/SQLUtilitiesTest.java
@@ -35,35 +35,13 @@ public final class SQLUtilitiesTest extends TestCase {
     }
 
     /**
-     * Tests {@link SQLUtilities#escape(String, String)}.
+     * Tests {@link SQLUtilities#escapeWildcards(String, String)}.
      */
     @Test
     public void testEscape() {
-        assertEquals("foo",             SQLUtilities.escape("foo",          
"\\"));
-        assertEquals("foo\\_biz\\%bar", SQLUtilities.escape("foo_biz%bar",  
"\\"));
-        assertEquals("foo\\\\bar",      SQLUtilities.escape("foo\\bar",     
"\\"));
-        assertEquals("foo#!#!bar#not",  SQLUtilities.escape("foo#!bar#not", 
"#!"));
-    }
-
-    /**
-     * Tests {@link SQLUtilities#toLikePattern(String, int, int, boolean, 
boolean, StringBuilder)}.
-     */
-    @Test
-    public void testToLikePattern() {
-        final var buffer = new StringBuilder(30);
-        assertEquals("WGS84",                       toLikePattern(buffer, 
"WGS84"));
-        assertEquals("WGS%84",                      toLikePattern(buffer, "WGS 
84"));
-        assertEquals("A%text%with%random%symbols%", toLikePattern(buffer, "A 
text !* with_random:/symbols;+"));
-        assertEquals("*%With%non%letter%start",     toLikePattern(buffer, 
"*_+%=With non-letter  start"));
-        assertEquals("\\%Special%case",             toLikePattern(buffer, 
"%Special_case"));
-    }
-
-    /**
-     * Helper method for {@link #testToLikePattern()}.
-     */
-    private static String toLikePattern(final StringBuilder buffer, final 
String identifier) {
-        buffer.setLength(0);
-        SQLUtilities.toLikePattern(identifier, 0, identifier.length(), false, 
false, "\\", buffer);
-        return buffer.toString();
+        assertEquals("foo",             SQLUtilities.escapeWildcards("foo",    
      "\\"));
+        assertEquals("foo\\_biz\\%bar", 
SQLUtilities.escapeWildcards("foo_biz%bar",  "\\"));
+        assertEquals("foo\\\\bar",      
SQLUtilities.escapeWildcards("foo\\bar",     "\\"));
+        assertEquals("foo#!#!bar#not",  
SQLUtilities.escapeWildcards("foo#!bar#not", "#!"));
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/ScriptRunnerTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/ScriptRunnerTest.java
index dbbe7cf356..c54bd45a21 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/ScriptRunnerTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/ScriptRunnerTest.java
@@ -64,6 +64,7 @@ public final class ScriptRunnerTest extends TestCase {
     @TestStep
     public static void testSupportedFlags(final ScriptRunner sr) {
         assertFalse(sr.isEnumTypeSupported);
+        assertFalse(sr.isCollationSupported);
     }
 
     /**
@@ -76,6 +77,7 @@ public final class ScriptRunnerTest extends TestCase {
     public static void testRegularExpressions(final ScriptRunner sr) {
         assertFalse(sr.isSupported("CREATE TYPE CI_DateTypeCode AS ENUM 
('creation', 'publication')"));
         assertFalse(sr.isSupported("CREATE CAST (VARCHAR AS CI_DateTypeCode) 
WITH INOUT AS ASSIGNMENT"));
+        assertFalse(sr.isSupported("CREATE COLLATION \"Ignore Accent and 
Case\" (provider=icu, locale='und-u-kn-ks-level1')"));
         assertTrue (sr.isSupported("CREATE TABLE CI_Citation (…)"));
         assertFalse(sr.isSupported("GRANT USAGE ON SCHEMA metadata TO 
PUBLIC"));
         assertFalse(sr.isSupported("GRANT SELECT ON TABLE \"Coordinate 
Reference System\" TO PUBLIC"));
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/TypeMapperTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/TypeMapperTest.java
deleted file mode 100644
index b3384aa091..0000000000
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/internal/shared/TypeMapperTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sis.metadata.sql.internal.shared;
-
-import java.sql.Types;
-
-// Test dependencies
-import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-import org.apache.sis.test.TestCase;
-
-
-/**
- * Tests {@link TypeMapper}.
- *
- * @author  Martin Desruisseaux (Geomatys)
- */
-public final class TypeMapperTest extends TestCase {
-    /**
-     * Creates a new test case.
-     */
-    public TypeMapperTest() {
-    }
-
-    /**
-     * Tests {@link TypeMapper#toJavaType(int)}.
-     */
-    @Test
-    public void testToJavaType() {
-        assertEquals(Integer.class, TypeMapper.toJavaType(Types.INTEGER));
-        assertEquals(Boolean.class, TypeMapper.toJavaType(Types.BOOLEAN));
-        assertNull  (               TypeMapper.toJavaType(Types.LONGVARCHAR));
-    }
-}
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
index b2ca6ba0a4..66bdc86982 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AuthorityCodes.java
@@ -25,7 +25,6 @@ import java.sql.SQLException;
 import java.sql.PreparedStatement;
 import java.sql.Statement;
 import org.opengis.referencing.IdentifiedObject;
-import org.apache.sis.metadata.sql.internal.shared.SQLUtilities;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.collection.IntegerList;
 import org.apache.sis.util.internal.shared.AbstractMap;
@@ -162,31 +161,21 @@ final class AuthorityCodes extends 
AbstractMap<String,String> implements Seriali
         }
         sql[ALL_CODES] = buffer.append(" ORDER BY 
").append(table.codeColumn).toString();
         /*
-         * Build the SQL query for fetching the codes of object having a name 
matching a pattern.
+         * Build the SQL query for fetching the codes of object having the 
given name.
          * It is of the form:
          *
-         *     SELECT code FROM table WHERE name LIKE ? AND DEPRECATED=FALSE 
ORDER BY code;
+         *     SELECT code FROM table WHERE name=? AND DEPRECATED=FALSE ORDER 
BY code;
          */
-        if (NUM_QUERIES > CODES_FOR_NAME) {
-            sql[CODES_FOR_NAME] = buffer.insert(conditionStart, 
table.nameColumn + " LIKE ? AND ").toString();
-            /*
-             * Workaround for Derby bug. See 
`SQLUtilities.filterFalsePositive(…)`.
-             */
-            String t = sql[CODES_FOR_NAME];
-            t = t.substring(0, columnNameEnd) + ", " + table.nameColumn + 
t.substring(columnNameEnd);
-            sql[CODES_FOR_NAME] = t;
-        }
+        sql[CODES_FOR_NAME] = buffer.insert(conditionStart, table.nameColumn + 
"=? AND ").toString();
         /*
          * Build the SQL query for fetching the name of a single object for a 
given code.
          * This query will also be used for testing object existence. It is of 
the form:
          *
          *     SELECT name FROM table WHERE code = ?
          */
-        if (NUM_QUERIES > NAME_FOR_CODE) {
-            buffer.setLength(conditionStart);
-            buffer.replace(columnNameStart, columnNameEnd, table.nameColumn);
-            sql[NAME_FOR_CODE] = buffer.append(table.codeColumn).append(" = 
?").toString();
-        }
+        buffer.setLength(conditionStart);
+        buffer.replace(columnNameStart, columnNameEnd, table.nameColumn);
+        sql[NAME_FOR_CODE] = 
buffer.append(table.codeColumn).append("=?").toString();
         for (int i=0; i<NUM_QUERIES; i++) {
             sql[i] = factory.translator.apply(sql[i]);
         }
@@ -217,19 +206,18 @@ final class AuthorityCodes extends 
AbstractMap<String,String> implements Seriali
     /**
      * Puts codes associated to the given name in the given collection.
      *
-     * @param  pattern  the {@code LIKE} pattern of the name to search.
-     * @param  name     the original name. This is a temporary workaround for 
a Derby bug (see {@code filterFalsePositive(…)}).
-     * @param  addTo    the collection where to add the codes.
+     * @param  name   the name of the object to search.
+     * @param  addTo  the collection where to add the codes.
      * @throws SQLException if an error occurred while querying the database.
      */
-    final void findCodesFromName(final String pattern, final String name, 
final Collection<Integer> addTo) throws SQLException {
+    final void findCodesFromName(final String name, final Collection<Integer> 
addTo) throws SQLException {
         synchronized (factory) {
             final PreparedStatement statement = 
prepareStatement(CODES_FOR_NAME);
-            statement.setString(1, pattern);
+            statement.setString(1, name);
             try (ResultSet result = statement.executeQuery()) {
                 while (result.next()) {
                     final int code = result.getInt(1);
-                    if (!result.wasNull() && 
SQLUtilities.filterFalsePositive(name, result.getString(2))) {
+                    if (!result.wasNull()) {
                         addTo.add(code);
                     }
                 }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index cde2d6b370..b4a3d800d1 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -55,7 +55,6 @@ import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.pending.jdk.JDK16;
 import org.apache.sis.pending.jdk.JDK19;
 import org.apache.sis.metadata.internal.shared.ReferencingServices;
-import org.apache.sis.metadata.sql.internal.shared.SQLUtilities;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.internal.shared.Formulas;
@@ -390,7 +389,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
              *
              *   SELECT DATUM_CODE FROM "Datum"
              *    WHERE ELLIPSOID_CODE IN (?,…)
-             *      AND (LOWER(DATUM_NAME) LIKE '?%')
+             *      AND (DATUM_NAME LIKE ?)
              */
             source = TableInfo.DATUM;
             if (isInstance(GeodeticDatum.class, object)) {
@@ -428,7 +427,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
          * so that buffer shall not contain valuable information yet.
          */
         final var buffer = new StringBuilder(350);     // Temporary buffer for 
building SQL query.
-        final Set<String> namePatterns;
+        final Set<String> namePatterns;                // Name including 
aliases.
         final String aliasSQL;
         if (ArraysExt.containsIdentity(filters, Condition.NAME)) {
             namePatterns = new LinkedHashSet<>();
@@ -441,7 +440,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
                   .append(dao.translator.toActualTableName(source.table))
                   .append("' AND ");
             // PostgreSQL does not require explicit cast when the value is a 
literal instead of "?".
-            appendFilterByName(namePatterns, "ALIAS", buffer);
+            appendLikeNames(namePatterns, "ALIAS", buffer);
             aliasSQL = dao.translator.apply(buffer.toString());
             buffer.setLength(0);
         } else {
@@ -454,7 +453,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
          *    SELECT <codeColumn> FROM <table>
          *      WHERE CAST(<typeColumn> AS VARCHAR(80)) LIKE 'type%'
          *        AND <filter.column> IN (<filter.values>)
-         *        AND (LOWER(<nameColumn>) LIKE '<name>%')
+         *        AND (<nameColumn> = ?)
          *
          * 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
@@ -476,7 +475,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
             if (namePatterns != null) {
                 if (isNext) buffer.append(" AND ");
                 isNext = false;
-                appendFilterByName(namePatterns, source.nameColumn, buffer);
+                appendLikeNames(namePatterns, source.nameColumn, buffer);
                 try (ResultSet result = stmt.executeQuery(aliasSQL)) {
                     while (result.next()) {
                         final int code = result.getInt(1);
@@ -535,19 +534,24 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
     }
 
     /**
-     * Returns a SQL pattern for the given datum name. The name is returned in 
all lower cases for allowing
-     * case-insensitive searches. Punctuations are replaced by any sequence of 
characters ({@code '%'}) and
-     * non-ASCII letters are replaced by any single character ({@code '_'}). 
The returned pattern should be
-     * flexible enough for accepting all names considered equal in {@code 
DefaultGeodeticDatum} comparisons.
-     * In case of doubt, it is okay to return a pattern accepting more names.
+     * Returns a <abbr>SQL</abbr> {@code LIKE} pattern for the given datum 
name.
+     * Characters other than letters and digits are replaced by the {@code 
'%'} wildcard.
+     * Note that this rule replaces also the {@code %}, {@code _} and {@code 
'} characters,
+     * thus avoiding the need to escape them.
+     *
+     * <p>This method does not try to address case-insensitive and 
accent-insensitive searches.
+     * This method assumes that the column uses a database collation ignoring 
cases and accents.
+     * This method is used in complement to collation, for adding more 
flexibility in the punctuations.
+     * In case of doubt, it is okay to return a pattern accepting more names, 
as the caller will check
+     * if we really have a match (including checks of ellipsoid and prime 
meridian).</p>
      *
      * @param  name    the datum name for which to return a SQL pattern.
      * @param  buffer  temporary buffer to use for creating the pattern.
-     * @return the SQL pattern for the given name.
+     * @return the <abbr>SQL</abbr> {@code LIKE} pattern for the given datum 
name.
      *
      * @see 
org.apache.sis.referencing.datum.DefaultGeodeticDatum#isHeuristicMatchForName(String)
      */
-    private String toDatumPattern(final String name, final StringBuilder 
buffer) {
+    private static String toDatumPattern(final String name, final 
StringBuilder buffer) {
         int start = 0;
         if (name.startsWith(ESRI_DATUM_PREFIX)) {
             start = ESRI_DATUM_PREFIX.length();
@@ -556,7 +560,22 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
         if (end < 0) end = name.length();
         end = CharSequences.skipTrailingWhitespaces(name, start, end);
         buffer.setLength(0);
-        SQLUtilities.toLikePattern(name, start, end, true, true, 
dao.translator.wildcardEscape, buffer);
+        while (start < end) {
+            final int c = name.codePointAt(start);
+            if (Character.isLetterOrDigit(c)) {
+                buffer.appendCodePoint(c);
+            } else {
+                final int length = buffer.length();
+                if (length > 0 && buffer.charAt(length - 1) != '%') {
+                    buffer.append('%');
+                }
+            }
+            start += Character.charCount(c);
+        }
+        final int length = buffer.length();
+        if (length == 0 || buffer.charAt(length - 1) != '%') {
+            buffer.append('%');
+        }
         return buffer.toString();
     }
 
@@ -565,7 +584,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
      * {@link #toDatumPattern(String, StringBuilder)}. This method append a 
SQL fragment like below:
      *
      * {@snippet lang="sql" :
-     *     (LOWER(<column>) LIKE '<pattern>' OR …)
+     *     (<column> LIKE '<pattern>' OR …)
      *     }
      *
      * This method assumes that {@code namePatterns} contains at least one 
element.
@@ -574,11 +593,10 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
      * @param  column        column where the search for the names.
      * @param  buffer        buffer where to add the SQL fragment.
      */
-    private static void appendFilterByName(final Set<String> namePatterns, 
final String column, final StringBuilder buffer) {
+    private static void appendLikeNames(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('\'');
+            buffer.append(separator).append(column).append(" LIKE 
'").append(pattern).append('\'');
             separator = " OR ";
         }
         buffer.append(')');
@@ -623,12 +641,9 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
         /** The object to search. */
         private final IdentifiedObject object;
 
-        /** Workaround for a Derby bug (see {@code filterFalsePositive(…)}). */
+        /** The name of the object to search. */
         private String name;
 
-        /** {@code LIKE} Pattern of the name of the object to search. */
-        private String namePattern;
-
         /** Information about the tables of the object to search. */
         private final TableInfo source;
 
@@ -697,17 +712,14 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
                     case 0: {   // Fetch codes from the name.
                         if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
                             name = getName(object);
-                            if (name != null) {     // Should never be null, 
but we are paranoiac.
-                                namePattern = dao.toLikePattern(name);
-                                dao.findCodesFromName(source, 
TableInfo.toCacheKey(object), namePattern, name, addTo);
-                            }
+                            dao.findCodesFromName(source, 
TableInfo.toCacheKey(object), name, addTo);
                         }
                         break;
                     }
                     case 1: {   // Fetch codes from the aliases.
                         if (domain != Domain.EXHAUSTIVE_VALID_DATASET) {
-                            if (namePattern != null) {
-                                dao.findCodesFromAlias(source, namePattern, 
name, addTo);
+                            if (name != null) {
+                                dao.findCodesFromAlias(source, name, addTo);
                             }
                         }
                         break;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index b0e62db918..b0a8fc0bc9 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -829,11 +829,10 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
                  * then we will search in the aliases table as a fallback.
                  */
                 final var result = new ArrayList<Integer>();
-                final String pattern = toLikePattern(code);
-                findCodesFromName(source, source.type, pattern, code, result);
+                findCodesFromName(source, source.type, code, result);
                 if (result.isEmpty()) {
                     // Search in aliases only if no match was found in primary 
names.
-                    findCodesFromAlias(source, pattern, code, result);
+                    findCodesFromAlias(source, code, result);
                 }
                 Integer resolved = null;
                 for (Integer value : result) {
@@ -871,58 +870,47 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
         return primaryKeys;
     }
 
-    /**
-     * Returns the given object name as a pattern which can be used in a 
{@code LIKE} clause.
-     * This method does not change the character case for avoiding the need to 
use {@code LOWER}
-     * in the <abbr>SQL</abbr> statement (because it may prevent the use of 
the database index).
-     */
-    final String toLikePattern(final String name) {
-        return SQLUtilities.toLikePattern(name, false, 
translator.wildcardEscape);
-    }
-
     /**
      * Finds the authority codes for the given name.
+     * The search is case-insensitive and accent-insensitive if the database 
uses a lenient collation.
      *
      * @param  source    information about the table where the code should 
appear.
      * @param  cacheKey  object class or {@link 
TableInfo#toCacheKey(IdentifiedObject)} value.
-     * @param  pattern   the name to search as a pattern that can be used with 
{@code LIKE}.
-     * @param  name      the original name. This is a temporary workaround for 
a Derby bug (see {@code filterFalsePositive(…)}).
+     * @param  name      name of the object to search. Case and accents will 
be ignored if possible.
      * @param  addTo     the collection where to add the codes that have been 
found.
      * @throws SQLException if an error occurred while querying the database.
      */
-    final void findCodesFromName(final TableInfo source, final Object 
cacheKey, final String pattern, final String name,
+    final void findCodesFromName(final TableInfo source, final Object 
cacheKey, final String name,
                                  final Collection<Integer> addTo) throws 
SQLException
     {
         AuthorityCodes codes = getCodeMap(cacheKey, source, false);
         if (codes != null) {
-            codes.findCodesFromName(pattern, name, addTo);
+            codes.findCodesFromName(name, addTo);
         }
     }
 
     /**
      * Finds the authority codes for the given alias.
+     * The search is case-insensitive and accent-insensitive if the database 
uses a lenient collation.
      *
-     * @param  source   information about the table where the code should 
appear.
-     * @param  pattern  the name to search as a pattern that can be used with 
{@code LIKE}.
-     * @param  name     the original name. This is a temporary workaround for 
a Derby bug (see {@code filterFalsePositive(…)}).
-     * @param  addTo    the collection where to add the codes that have been 
found.
+     * @param  source  information about the table where the code should 
appear.
+     * @param  name    name of the object to search. Case and accents will be 
ignored if possible.
+     * @param  addTo   the collection where to add the codes that have been 
found.
      * @throws SQLException if an error occurred while querying the database.
      */
-    final void findCodesFromAlias(final TableInfo source, final String 
pattern, final String name, final Collection<Integer> addTo)
+    final void findCodesFromAlias(final TableInfo source, final String name, 
final Collection<Integer> addTo)
             throws SQLException
     {
         final PreparedStatement stmt = prepareStatement(
                 "AliasKey",
                 "SELECT OBJECT_CODE, ALIAS"
                         + " FROM \"Alias\""
-                        + " WHERE OBJECT_TABLE_NAME=? AND ALIAS LIKE ?");
+                        + " WHERE OBJECT_TABLE_NAME=? AND ALIAS=?");
         stmt.setString(1, translator.toActualTableName(source.table));
-        stmt.setString(2, pattern);
+        stmt.setString(2, name);
         try (ResultSet result = stmt.executeQuery()) {
             while (result.next()) {
-                if (SQLUtilities.filterFalsePositive(name, 
result.getString(2))) {
-                    addTo.add(getOptionalInteger(result, 1));
-                }
+                addTo.add(getOptionalInteger(result, 1));
             }
         }
     }
@@ -1115,13 +1103,41 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
      *
      * @param  result       the result set to fetch value from.
      * @param  columnIndex  the column index (1-based).
-     * @return the Boolean at the specified column, or {@code null}.
+     * @return the Boolean at the specified column, or {@code false}.
      * @throws SQLException if an error occurred while querying the database.
      */
-    private boolean getOptionalBoolean(final ResultSet result, final int 
columnIndex) throws SQLException {
+    private boolean getBoolean(final ResultSet result, final int columnIndex) 
throws SQLException {
         return translator.useBoolean() ? result.getBoolean(columnIndex) : 
(result.getInt(columnIndex) != 0);
     }
 
+    /**
+     * Gets the value from the specified {@link ResultSet}, or {@code null} if 
none.
+     * This method is invoked for columns where the <abbr>EPSG</abbr> database 
uses "Yes", "No" or empty values.
+     * The Apache <abbr>SIS</abbr> scripts rather uses the {@code BOOLEAN} 
type, but allowing null values for the
+     * cases where the flag is allowed to be absent (empty).
+     *
+     * <h4>Exceptions</h4>
+     * If a string is not recognized as a Boolean value, this method throws a 
{@link SQLException} because a wrong
+     * {@code PARAM_SIGN_REVERSAL} value would let {@code EPSGDataAccess} 
finishes its work without apparent problem
+     * but would cause failures later when Apache <abbr>SIS</abbr> tries to 
infer an inverse operation. An exception
+     * thrown at a later time is more difficult to relate to the root cause 
than if we throw the exception here.
+     *
+     * @param  result       the result set to fetch value from.
+     * @param  columnIndex  the column index (1-based).
+     * @return the Boolean at the specified column, or {@code null}.
+     * @throws SQLException if an error occurred while querying the database.
+     */
+    private Boolean getOptionalBoolean(final ResultSet result, final int 
columnIndex) throws SQLException {
+        Boolean value;
+        if (translator.useBoolean()) {
+            value = result.getBoolean(columnIndex);
+        } else {
+            // May throw SQLException - see above comment.
+            value = SQLUtilities.parseBoolean(result.getString(columnIndex));
+        }
+        return result.wasNull() ? null : value;
+    }
+
     /**
      * Formats an error message for an unexpected null value.
      */
@@ -1557,11 +1573,8 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 final String column = isPrimaryKey ? source.codeColumn : 
source.nameColumn;
                 query.setLength(queryStart);
                 query.append(source.codeColumn);
-                if (!isPrimaryKey) {
-                    query.append(", ").append(column);      // Only for 
filterFalsePositive(…).
-                }
                 query.append(" FROM ").append(source.fromClause)
-                     .append(" WHERE ").append(column).append(isPrimaryKey ? " 
= ?" : " LIKE ?");
+                     .append(" WHERE ").append(column).append("=?");
                 try (PreparedStatement stmt = 
connection.prepareStatement(translator.apply(query.toString()))) {
                     /*
                      * Check if at least one record is found for the code or 
the name.
@@ -1570,14 +1583,12 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                     if (isPrimaryKey) {
                         stmt.setInt(1, key);
                     } else {
-                        stmt.setString(1, toLikePattern(code));
+                        stmt.setString(1, code);
                     }
                     Integer present = null;
                     try (ResultSet result = stmt.executeQuery()) {
                         while (result.next()) {
-                            if (isPrimaryKey || 
SQLUtilities.filterFalsePositive(code, result.getString(2))) {
-                                present = 
ensureSingleton(getOptionalInteger(result, 1), present, code);
-                            }
+                            present = 
ensureSingleton(getOptionalInteger(result, 1), present, code);
                         }
                     }
                     if (present != null) {
@@ -1712,7 +1723,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 final String  area       = getOptionalString (result, 3);
                 final String  scope      = getOptionalString (result, 4);
                 final String  remarks    = getOptionalString (result, 5);
-                final boolean deprecated = getOptionalBoolean(result, 6);
+                final boolean deprecated = getBoolean        (result, 6);
                 final String  type       = getString   (code, result, 7);
                 /*
                  * Do not invoke `createProperties` now, even if we have all 
required information,
@@ -2003,7 +2014,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 final String   area       = getOptionalString  (result,  8);
                 final String   scope      = getOptionalString  (result,  9);
                 final String   remarks    = getOptionalString  (result, 10);
-                final boolean  deprecated = getOptionalBoolean (result, 11);
+                final boolean  deprecated = getBoolean         (result, 11);
                 final Integer  convRSCode = getOptionalInteger (result, 15);
                 /*
                  * Do not invoke `createProperties` now, even if we have all 
required information,
@@ -2209,10 +2220,10 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                             + " WHERE CONVENTIONAL_RS_CODE = ?", code))
             {
                 while (result.next()) {
-                    final Integer epsg       = getInteger   (code, result, 1);
-                    final String  name       = getString    (code, result, 2);
-                    final String  remarks    = getOptionalString  (result, 3);
-                    final boolean deprecated = getOptionalBoolean (result, 4);
+                    final Integer epsg       = getInteger (code, result, 1);
+                    final String  name       = getString  (code, result, 2);
+                    final String  remarks    = getOptionalString(result, 3);
+                    final boolean deprecated = getBoolean       (result, 4);
                     /*
                      * Map of properties should be populated only after we 
extracted all
                      * information needed from the `ResultSet`, because it may 
be closed.
@@ -2286,7 +2297,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 final double  semiMinorAxis     = getOptionalDouble (result, 
5);
                 final String  uom_code          = getString   (code, result, 
6);
                 final String  remarks           = getOptionalString (result, 
7);
-                final boolean deprecated        = getOptionalBoolean(result, 
8);
+                final boolean deprecated        = getBoolean        (result, 
8);
                 final Unit<Length> unit         = 
owner.createUnit(uom_code).asType(Length.class);
                 final boolean useSemiMinor      = 
Double.isNaN(inverseFlattening);
                 if (useSemiMinor && Double.isNaN(semiMinorAxis)) {
@@ -2379,7 +2390,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 final double  longitude  = getDouble   (code, result, 3);
                 final String  uom_code   = getString   (code, result, 4);
                 final String  remarks    = getOptionalString (result, 5);
-                final boolean deprecated = getOptionalBoolean(result, 6);
+                final boolean deprecated = getBoolean        (result, 6);
                 final Unit<Angle> unit = 
owner.createUnit(uom_code).asType(Angle.class);
                 /*
                  * Map of properties should be populated only after we 
extracted all
@@ -2471,7 +2482,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 double   zmax        = getOptionalDouble  (result,  9);
                 Temporal tmin        = getOptionalTemporal(result, 11, 
"createExtent");
                 Temporal tmax        = getOptionalTemporal(result, 12, 
"createExtent");
-                boolean  deprecated  = getOptionalBoolean (result, 13);
+                boolean  deprecated  = getBoolean         (result, 13);
                 DefaultGeographicBoundingBox bbox = null;
                 if (!(Double.isNaN(ymin) && Double.isNaN(ymax) && 
Double.isNaN(xmin) && Double.isNaN(xmax))) {
                     /*
@@ -2580,12 +2591,12 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                         + " WHERE COORD_SYS_CODE = ?", code))
         {
             while (result.next()) {
-                final Integer epsg       = getInteger  (code, result, 1);
-                final String  name       = getString   (code, result, 2);
-                final String  type       = getString   (code, result, 3);
-                final int     dimension  = getInteger  (code, result, 4);
-                final String  remarks    = getOptionalString (result, 5);
-                final boolean deprecated = getOptionalBoolean(result, 6);
+                final Integer epsg       = getInteger (code, result, 1);
+                final String  name       = getString  (code, result, 2);
+                final String  type       = getString  (code, result, 3);
+                final int     dimension  = getInteger (code, result, 4);
+                final String  remarks    = getOptionalString(result, 5);
+                final boolean deprecated = getBoolean       (result, 6);
                 final CoordinateSystemAxis[] axes = createComponents(
                         GeodeticAuthorityFactory::createCoordinateSystemAxis,
                         "AxisOrder",
@@ -2999,6 +3010,7 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
      *
      * @param  parameter  the <abbr>EPSG</abbr> code of the parameter 
descriptor.
      * @param  options    the options where to store the {@value 
#SIGN_REVERSAL_OPTION} value.
+     * @throws SQLException if the flag uses an unrecognized string value.
      */
     private void getSignReversal(final int parameter, final Map<String, 
String> options) throws SQLException {
         try (ResultSet result = executeQueryForCodes(
@@ -3009,15 +3021,8 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
         {
             Boolean reversibility = null;
             while (result.next()) {
-                Boolean value;
-                if (translator.useBoolean()) {
-                    value = result.getBoolean(1);
-                    if (result.wasNull()) return;
-                } else {
-                    // May throw SQLException - see above comment.
-                    value = SQLUtilities.parseBoolean(result.getString(1));
-                    if (value == null) return;
-                }
+                Boolean value = getOptionalBoolean(result, 1);
+                if (value == null) return;
                 if (reversibility == null) reversibility = value;
                 else if (!reversibility.equals(value)) return;
             }
@@ -3086,10 +3091,10 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                         + " WHERE PARAMETER_CODE = ?", code))
         {
             while (result.next()) {
-                final Integer epsg        = getInteger  (code, result, 1);
-                final String  name        = getString   (code, result, 2);
-                final String  description = getOptionalString (result, 3);
-                final boolean deprecated  = getOptionalBoolean(result, 4);
+                final Integer epsg        = getInteger (code, result, 1);
+                final String  name        = getString  (code, result, 2);
+                final String  description = getOptionalString(result, 3);
+                final boolean deprecated  = getBoolean       (result, 4);
                 getParameterUnit(epsg, options, null);
                 getParameterType(epsg, options);
                 getSignReversal (epsg, options);
@@ -3368,10 +3373,10 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                         + " WHERE COORD_OP_METHOD_CODE = ?", code))
         {
             while (result.next()) {
-                final Integer epsg       = getInteger  (code, result, 1);
-                final String  name       = getString   (code, result, 2);
-                final String  remarks    = getOptionalString (result, 3);
-                final boolean deprecated = getOptionalBoolean(result, 4);
+                final Integer epsg       = getInteger (code, result, 1);
+                final String  name       = getString  (code, result, 2);
+                final String  remarks    = getOptionalString(result, 3);
+                final boolean deprecated = getBoolean       (result, 4);
                 final ParameterDescriptor<?>[] descriptors = createComponents(
                                 
GeodeticAuthorityFactory::createParameterDescriptor,
                                 "Coordinate_Operation Parameter Usage",
@@ -3475,12 +3480,12 @@ search: try (ResultSet result = 
executeMetadataQuery("Deprecation",
                 } else {
                     methodCode = getInteger(code, result, 6);
                 }
-                final String  version    = getOptionalString (result,  7);
-                final double  accuracy   = getOptionalDouble (result,  8);
-                final String  area       = getOptionalString (result,  9);
-                final String  scope      = getOptionalString (result, 10);
-                final String  remarks    = getOptionalString (result, 11);
-                final boolean deprecated = getOptionalBoolean(result, 12);
+                final String  version    = getOptionalString(result,  7);
+                final double  accuracy   = getOptionalDouble(result,  8);
+                final String  area       = getOptionalString(result,  9);
+                final String  scope      = getOptionalString(result, 10);
+                final String  remarks    = getOptionalString(result, 11);
+                final boolean deprecated = getBoolean       (result, 12);
                 /*
                  * Create the source and target CRS for the codes fetched 
above.  Those CRS are optional only for
                  * conversions (the above calls to getString(code, result, …) 
verified that those CRS are defined
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
index eef5cf7db1..f4f2002d64 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGInstaller.java
@@ -122,7 +122,18 @@ final class EPSGInstaller extends ScriptRunner {
     }
 
     /**
-     * Processes to the creation of the EPSG database using the SQL scripts 
from the given provider.
+     * Processes to the creation of the <abbr>EPSG</abbr> database using the 
<abbr>SQL</abbr> scripts
+     * from the given provider. If the given provider is {@code null}, then 
this method searches for
+     * a <abbr>SQL</abbr> default provider on the module path, in preference 
order:
+     *
+     * <ol>
+     *   <li>A provider from a publicly supported dependency such as {@code 
sis-epsg.jar} or {@code sis-embedded.jar}.
+     *       Users have to put one of those dependencies in the module path 
themselves. This action is interpreted as
+     *       an acceptance of <abbr>EPSG</abbr> terms of use, so no license 
agreement window will popup.</li>
+     *   <li>A provider offering users to automatically download the data 
through an interactive application.
+     *       Those providers are defined by {@code org.apache.sis.console} and 
{@code org.apache.sis.gui} modules.
+     *       Users must accept <abbr>EPSG</abbr> terms of use before the 
database can be installed.
+     * </ol>
      *
      * @param  scriptProvider  user-provided scripts, or {@code null} for 
automatic lookup.
      * @param  locale          the locale for information or warning messages, 
if any.
@@ -134,7 +145,14 @@ final class EPSGInstaller extends ScriptRunner {
     public boolean run(InstallationResources scriptProvider, final Locale 
locale) throws SQLException, IOException {
         long time = System.nanoTime();
         if (scriptProvider == null) {
-            scriptProvider = lookupProvider(locale);
+            for (final InstallationResources candidate : 
InstallationResources.load()) {
+                if (candidate.getAuthorities().contains(Constants.EPSG)) {
+                    scriptProvider = candidate;
+                    if 
(!candidate.getClass().isAnnotationPresent(Fallback.class)) {
+                        break;
+                    }
+                }
+            }
             if (scriptProvider == null) {
                 return false;
             }
@@ -163,41 +181,6 @@ final class EPSGInstaller extends ScriptRunner {
         return true;
     }
 
-    /**
-     * Searches for a SQL script provider on the module path before to 
fallback on the default provider.
-     * The returned provider will be, in preference order:
-     *
-     * <ol>
-     *   <li>A provider from a publicly supported dependency such as {@code 
sis-epsg.jar} or {@code sis-embedded.jar}.
-     *       Users have to put one of those dependencies in the module path 
themselves. This action is interpreted as
-     *       an acceptance of EPSG terms of use, so no license agreement 
window will popup.</li>
-     *   <li>A provider offering users to automatically download the data. 
Those providers are defined by
-     *       {@code org.apache.sis.console} and {@code org.apache.sis.gui} 
modules.
-     *       Users must accept EPSG terms of use before the database can be 
installed.
-     * </ol>
-     *
-     * @param  locale  the locale for information or warning messages, if any.
-     * @return the SQL preferred script provider, or {@code null} if none.
-     */
-    private static InstallationResources lookupProvider(final Locale locale) 
throws IOException {
-        InstallationResources fallback = null;
-        for (final InstallationResources provider : 
InstallationResources.load()) {
-            if (provider.getAuthorities().contains(Constants.EPSG)) {
-                if (!provider.getClass().isAnnotationPresent(Fallback.class)) {
-                    return provider;
-                }
-                fallback = provider;
-            }
-        }
-        /*
-         * If we did not found a provider ready to use such as `sis-epsg.jar` 
or `sis-embedded.jar`,
-         * we may fallback on a provider offering to download the data (those 
fallbacks are provided
-         * by `org.apache.sis.console` and `org.apache.sis.gui` modules). 
Those fallbacks will ask to
-         * the users if they accept the EPSG Terms of Use.
-         */
-        return fallback;
-    }
-
     /**
      * Creates a message reporting the failure to create EPSG database. This 
method is invoked when {@link EPSGFactory}
      * caught an exception. This method completes the exception message with 
the file name and line number where the
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index 92350ce9fd..11bf562de0 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -314,7 +314,7 @@ public class SQLTranslator implements UnaryOperator<String> 
{
      */
     @SuppressWarnings("fallthrough")
     final void setup(final DatabaseMetaData md) throws SQLException {
-        String schemaPattern = SQLUtilities.escape(schema, wildcardEscape);
+        String schemaPattern = SQLUtilities.escapeWildcards(schema, 
wildcardEscape);
         int tableIndex = 0;
         do {
             usePrefixedTableNames  = false;
@@ -323,7 +323,7 @@ public class SQLTranslator implements UnaryOperator<String> 
{
             switch (tableIndex++) {
                 case 0: {   // Test EPSG standard table name first.
                     usePrefixedTableNames = true;
-                    table = SQLUtilities.escape(TABLE_PREFIX, wildcardEscape);
+                    table = SQLUtilities.escapeWildcards(TABLE_PREFIX, 
wildcardEscape);
                     // Fallthrough for testing "epsg_coordoperation".
                 }
                 case 2: {
@@ -357,7 +357,7 @@ public class SQLTranslator implements UnaryOperator<String> 
{
          * naming convention (unquoted or mixed-case, prefixed by "epsg_" or 
not).
          */
         UnaryOperator<String> toNativeCase = UnaryOperator.identity();
-        schemaPattern  = SQLUtilities.escape(schema, wildcardEscape);
+        schemaPattern  = SQLUtilities.escapeWildcards(schema, wildcardEscape);
         tableRewording = new HashMap<>();
         replacements   = new HashMap<>();
         /*
@@ -461,7 +461,7 @@ check:  for (;;) {
             boolean isTableFound = false;
             brokenTargetCols.addAll(mayRenameColumns.values());
             table = toNativeCase.apply(toActualTableName(table));
-            try (ResultSet result = md.getColumns(catalog, schemaPattern, 
SQLUtilities.escape(table, wildcardEscape), "%")) {
+            try (ResultSet result = md.getColumns(catalog, schemaPattern, 
SQLUtilities.escapeWildcards(table, wildcardEscape), "%")) {
                 while (result.next()) {
                     isTableFound = true;          // Assuming that all tables 
contain at least one column.
                     final String column = 
result.getString(Reflection.COLUMN_NAME).toUpperCase(Locale.US);
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
index 99d047429c..1f60163481 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGFactoryTest.java
@@ -305,13 +305,13 @@ public final class EPSGFactoryTest extends 
TestCaseWithLogs {
         assertAxisDirectionsEqual(crs.getCoordinateSystem(), 
AxisDirection.EAST, AxisDirection.NORTH);
         assertSame(crs, factory.createProjectedCRS("27571"));
         /*
-         * Gets the CRS using 'createObject'. It will require more SQL
+         * Gets the CRS using `createObject`. It will require more SQL
          * statement internally in order to determines the object type.
          */
         assertSame(crs, factory.createObject("27571"));
         assertSame(crs, factory.createObject("NTF (Paris) / Lambert zone I"));
-        assertSame(crs, factory.createProjectedCRS("NTF Paris Lambert zone 
I"));
-        assertSame(crs, factory.createObject("NTF Paris Lambert zone I"));
+        assertSame(crs, factory.createProjectedCRS("ntf (paris) / lambert zone 
I"));
+        assertSame(crs, factory.createObject("ntf (paris) / lambert zone I"));
         loggings.assertNoUnexpectedLog();
     }
 
@@ -558,7 +558,7 @@ public final class EPSGFactoryTest extends TestCaseWithLogs 
{
         final EPSGFactory factory = dataEPSG.factory();
         /*
          * Most basic objects.
-         * Note: the numbers in 'size() >= x' checks were determined from the 
content of EPSG dataset version 7.9.
+         * Note: the numbers in `size() >= x` checks were determined from the 
content of EPSG dataset version 7.9.
          */
         try {
             final Set<String> axes = 
factory.getAuthorityCodes(CoordinateSystemAxis.class);
diff --git 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
index d0096a7b1f..4fd44e5021 100644
--- 
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
+++ 
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java
@@ -46,7 +46,6 @@ import org.apache.sis.referencing.cs.AxesConvention;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
 import org.apache.sis.referencing.internal.shared.DefinitionVerifier;
 import org.apache.sis.referencing.internal.shared.ReferencingUtilities;
-import org.apache.sis.metadata.sql.internal.shared.SQLUtilities;
 import org.apache.sis.metadata.sql.internal.shared.SQLBuilder;
 import org.apache.sis.geometry.wrapper.GeometryType;
 import org.apache.sis.system.CommonExecutor;
@@ -149,7 +148,7 @@ public class InfoStatements implements Localized, 
AutoCloseable {
     private PreparedStatement sridForUnknownTable;
 
     /**
-     * The statement for fetching a SRID from a CRS and its set of authority 
codes.
+     * The statement for fetching a <abbr>SRID</abbr> from a <abbr>CRS</abbr> 
and its set of authority codes.
      * Created when first needed.
      *
      * @see #findOrAddCRS(CoordinateReferenceSystem)
@@ -484,7 +483,7 @@ public class InfoStatements implements Localized, 
AutoCloseable {
         appendFrom(sql, schema.crsTable);
         sql.append(search).append("=?");
         if (byAuthorityCode) {
-            sql.append(" AND 
LOWER(").append(schema.crsAuthorityNameColumn).append(") LIKE ?");
+            sql.append(" AND 
").append(schema.crsAuthorityNameColumn).append("=?");
         }
         return connection.prepareStatement(sql.toString());
     }
@@ -822,23 +821,21 @@ public class InfoStatements implements Localized, 
AutoCloseable {
                     sridFromCRS = prepareSearchCRS(true);
                 }
                 sridFromCRS.setInt(1, code);
-                sridFromCRS.setString(2, SQLUtilities.toLikePattern(authority, 
true, database.wildcardEscape));
+                sridFromCRS.setString(2, authority);
                 try (ResultSet result = sridFromCRS.executeQuery()) {
                     while (result.next()) {
-                        if (SQLUtilities.filterFalsePositive(authority, 
result.getString(1))) {
-                            final int srid = result.getInt(2);
-                            if (sridFounInUse.add(srid)) try {
-                                final Object parsed = parseDefinition(result, 
3);
-                                if (Utilities.equalsApproximately(parsed, 
crs)) {
-                                    search.srid = srid;
-                                    return search;
-                                }
-                            } catch (ParseException e) {
-                                if (error == null) error = e;
-                                else error.addSuppressed(e);
+                        final int srid = result.getInt(2);
+                        if (sridFounInUse.add(srid)) try {
+                            final Object parsed = parseDefinition(result, 3);
+                            if (Utilities.equalsApproximately(parsed, crs)) {
+                                search.srid = srid;
+                                return search;
                             }
-                            done.put(search, Boolean.FALSE);    // Declare 
this "authority:code" pair as not available.
+                        } catch (ParseException e) {
+                            if (error == null) error = e;
+                            else error.addSuppressed(e);
                         }
+                        done.put(search, Boolean.FALSE);    // Declare this 
"authority:code" pair as not available.
                     }
                 }
             }
diff --git 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/Content.java
 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/Content.java
index 08ef3b8fd4..2856608183 100644
--- 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/Content.java
+++ 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/Content.java
@@ -254,7 +254,7 @@ public class Content extends ResourceDefinition {
             final String escape = 
stmt.getConnection().getMetaData().getSearchStringEscape();
             while (rs.next()) {
                 String tableName = getString(rs, 1);
-                GenericName name = factory.createLocalName(namespace, 
SQLUtilities.escape(tableName, escape));
+                GenericName name = factory.createLocalName(namespace, 
SQLUtilities.escapeWildcards(tableName, escape));
                 contents.add(new Content(dao, name, tableName, rs, listeners));
             }
         } catch (SQLException e) {
diff --git 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStoreProvider.java
 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStoreProvider.java
index 7fa5f33137..1fd3503b28 100644
--- 
a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStoreProvider.java
+++ 
b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStoreProvider.java
@@ -192,7 +192,7 @@ public class GpkgStoreProvider extends DataStoreProvider {
         if (source != null) {
             try (Connection connection = source.getConnection()) {
                 final DatabaseMetaData metadata = connection.getMetaData();
-                final String table = SQLUtilities.escape(Content.TABLE_NAME, 
metadata.getSearchStringEscape());
+                final String table = 
SQLUtilities.escapeWildcards(Content.TABLE_NAME, 
metadata.getSearchStringEscape());
                 try (ResultSet r = metadata.getColumns(null, null, table, 
"%")) {
                     boolean hasTable = false, hasType = false;
                     while (r.next()) {
diff --git 
a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java
 
b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java
index 8dabce60c1..79524923b1 100644
--- 
a/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java
+++ 
b/optional/src/org.apache.sis.referencing.database/main/org/apache/sis/resources/embedded/EmbeddedResources.java
@@ -131,13 +131,18 @@ public class EmbeddedResources extends 
InstallationResources {
      *
      * @param  authority  shall be {@code "Embedded"}.
      * @param  index      shall be 0.
-     * @return the embedded data source.
+     * @return the embedded data source, or {@code null} if not available.
      */
     @Override
     public DataSource getResource(String authority, int index) {
         verifyAuthority(authority);
         synchronized (Initializer.class) {
-            if (dataSource == null) {
+            /*
+             * Check for `LICENSE.txt` as a way to detect whether the data are 
available.
+             * It should always be the case when using a released JAR file, 
but it may not
+             * be the case when using a local build.
+             */
+            if (dataSource == null && 
EmbeddedResources.class.getResourceAsStream("LICENSE.txt") != null) {
                 final var ds = new EmbeddedDataSource();
                 ds.setDataSourceName(Initializer.DATABASE);
                 ds.setDatabaseName("classpath:" + DIRECTORY + "/Databases/" + 
Initializer.DATABASE);
diff --git 
a/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/Prepare.sql
 
b/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/Prepare.sql
index 9083d21524..080febcdbb 100644
--- 
a/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/Prepare.sql
+++ 
b/optional/src/org.apache.sis.referencing.epsg/main/org/apache/sis/referencing/factory/sql/epsg/Prepare.sql
@@ -54,3 +54,9 @@ CREATE CAST (VARCHAR AS "CRS Kind")          WITH INOUT AS 
ASSIGNMENT;
 CREATE CAST (VARCHAR AS "CS Kind")           WITH INOUT AS ASSIGNMENT;
 CREATE CAST (VARCHAR AS "Supersession Type") WITH INOUT AS ASSIGNMENT;
 CREATE CAST (VARCHAR AS "Table Name")        WITH INOUT AS ASSIGNMENT;
+
+--
+-- PostgreSQL: collation using the International Components for Unicode (ICU) 
library.
+-- 
https://www.postgresql.org/docs/current/collation.html#ICU-COLLATION-SETTINGS
+--
+CREATE COLLATION "Ignore Accent and Case" (PROVIDER = 'icu', DETERMINISTIC = 
false, LOCALE = 'en_GB-u-ka-shifted-ks-level1');
diff --git 
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Changes.md
 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Changes.md
new file mode 100644
index 0000000000..11e941e896
--- /dev/null
+++ 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Changes.md
@@ -0,0 +1,46 @@
+# Changes in EPSG scripts
+
+This page summarizes the changes applied by Apache SIS on the SQL scripts 
published by EPSG.
+For more information about the use of EPSG geodetic dataset in Apache SIS, see 
[README](./README.md).
+
+## Tables
+
+Additions:
+* Comments in a header for explaining the origin of the file.
+* In the "Coordinate Axis" table, `NOT NULL` constraint added to the 
`coord_axis_code` column.
+* `COLLATE "Ignore Accent and Case"` added in definitions of columns for 
object names or aliases.
+
+Removal of unused tables or columns:
+* The "Change" table and the `change_id` column in all tables.
+* The `information_source`, `data_source` and `revision_date` columns in all 
tables.
+* The `crs_scope`, `coord_op_scope`, `datum_scope` and `area_of_use_code` 
columns (deprecated).
+
+Change of data types:
+* Type of `realization_epoch` and `publication_date` columns changed to `DATE` 
(in the "Datum" table).
+* Type of `epsg_usage` column changed from `SERIAL` to `INTEGER NOT NULL`.
+* Type of `table_name` column in all tables changed from `VARCHAR(80)` to 
"Table Name".
+* Type of `coord_ref_sys_kind` column changed from `VARCHAR(24)` to "CRS Kind".
+* Type of `coord_sys_type` column changed from `VARCHAR(24)` to "CS Kind".
+* Type of `datum_type` column changed from `VARCHAR(24)` to "Datum Kind".
+* Type of `supersession_type` column changed from `VARCHAR(50)` to 
"Supersession Type".
+* Type of `ellipsoid_shape`, `reverse_op`, `param_sign_reversal`, `show_crs`, 
`show_operation`
+  and all `deprecated` columns changed from `SMALLINT` (or sometimes 
`VARCHAR(3)`) to `BOOLEAN`.
+* Change all `FLOAT` types to `DOUBLE PRECISION` because Apache SIS reads all 
numbers as `double` type.
+  This change avoids spurious digits in the conversions from `float` to 
`double`.
+
+Changes that do not impact data:
+* Rename `epsg_` table names to the camel case convention used by Apache SIS.
+* Use a different column order for keeping related columns close to each other.
+* Suppress trailing `NULL` (not to be confused with `NOT NULL`) as they are 
implicit.
+
+**Maintenance note:** if some values were added in any enumeration, check the 
maximal
+length of the `VARCHAR` replacements in the 
`EPSGInstaller.identifierReplacements` map.
+If some new columns have their type changed to the Boolean or double-precision 
type,
+some hard-coded values in the `DataScriptFormatter` class may need to be 
modified,
+in particular the `booleanColumns` and `doubleColumns` collections.
+
+
+## Foreigner keys
+
+* Remove the `fk_change_id` foreigner key.
+* At the end of all `ALTER TABLE` statement, append `ON UPDATE RESTRICT ON 
DELETE RESTRICT`.
diff --git 
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md
 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md
index 4c4beff5dd..d52b844c47 100644
--- 
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md
+++ 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md
@@ -37,8 +37,10 @@ Execute the scripts in the `public` schema of a PostgreSQL 
database on the local
 This page assumes that the database name is "Referencing", but any other name 
can be used
 if the argument given to `TableScriptUpdater` (later in this page) is adjusted 
accordingly.
 
-If a copy of the original SQL scripts (as downloaded from EPSG) for the 
previous version is still available,
-and if the following commands report no difference, then jump to "Automatic 
updates after the manual checks" step.
+
+### Updates the Data Definition scripts
+
+Verify that the new SQL scripts downloaded from EPSG defines the same tables 
as the previous version:
 
 ```bash
 cd _<directory containing EPSG scripts of previous version>_
@@ -46,70 +48,16 @@ diff PostgreSQL_Table_Script.sql 
$EPSG_SCRIPTS/PostgreSQL_Table_Script.sql
 diff PostgreSQL_FKey_Script.sql  $EPSG_SCRIPTS/PostgreSQL_FKey_Script.sql
 ```
 
-Otherwise, move to the directory which contains the Apache SIS scripts:
+If there are some changes, port them manually to the {@code Tables.sql} and 
{@code FKeys.sql} scripts.
+The [page listing the changes](./Changes.html) gives information about the 
changes to expert or to reproduce.
+Then, execute the `main` method of the 
`org.apache.sis.referencing.factory.sql.epsg.*Updater` classes
+located in the test directory of the `org.apache.sis.non-free:sis-epsg` Maven 
sub-project.
+Adjust version numbers as needed in the following commands:
 
 ```bash
 cd <path to a local copy of 
http://svn.apache.org/repos/asf/sis/data/non-free/EPSG/>
 export NON_FREE_DIR=$PWD
-```
-
-Overwrite `Tables.sql` and `FKeys.sql` with the new SQL scripts.
-Do not overwrite `Data.sql` yet:
-
-```bash
-cp $EPSG_SCRIPTS/PostgreSQL_Table_Script.sql Tables.sql
-cp $EPSG_SCRIPTS/PostgreSQL_FKey_Script.sql  FKeys.sql
-```
 
-
-### Manual checks and editions
-
-Open the `Tables.sql` file for edition:
-
-* Keep the header comments that existed in the overwritten file.
-* Remove the `"Change"` table and the `change_id` column in all tables. They 
are EPSG metadata unused by Apache SIS.
-* Remove the `information_source`, `data_source` and `revision_date` columns 
in all tables. They are EPSG metadata unused by Apache SIS.
-* Remove the `crs_scope`, `coord_op_scope`, `datum_scope` and 
`area_of_use_code` columns, which are deprecated.
-* Keep the same column order than in the previous `Tables.sql`.
-* Rename `epsg_` table names to the camel case convention used by Apache SIS.
-* Suppress trailing `NULL` (not to be confused with `NOT NULL`) as they are 
implicit.
-* In the statement creating the `coordinateaxis` table,
-  add the `NOT NULL` constraint to the `coord_axis_code` column.
-* In the statement creating the `epsg_datum` table,
-  change the type of the `realization_epoch` and `publication_date` columns to 
`DATE`.
-* Change the type of `ellipsoid_shape`, `reverse_op`, `param_sign_reversal`
-  `show_crs`, `show_operation` and all `deprecated` fields from `SMALLINT`
-  (or sometimes `VARCHAR(3)`) to `BOOLEAN`.
-* Change all `FLOAT` types to `DOUBLE PRECISION` because Apache SIS reads all 
numbers as `double` type.
-  This change avoids spurious digits in the conversions from `float` to 
`double`.
-* Change the type of `epsg_usage` column from `SERIAL` to `INTEGER NOT NULL`.
-* Change the type of every `table_name` columns from `VARCHAR(80)` to `"Table 
Name"`.
-* Change the type of `coord_ref_sys_kind` column from `VARCHAR(24)` to `"CRS 
Kind"`.
-* Change the type of `coord_sys_type` column from `VARCHAR(24)` to `"CS Kind"`.
-* Change the type of `datum_type` column from `VARCHAR(24)` to `"Datum Kind"`.
-* Change the type of `supersession_type` column from `VARCHAR(50)` to 
`"Supersession Type"`.
-* If new enumeration values are added, check the maximal lengths of `VARCHAR` 
replacements in `EPSGInstaller`.
-* Suppress trailing spaces and save.
-
-Then open the `FKeys.sql` file for edition:
-
-* Remove the `fk_change_id` foreigner key.
-* At the end of all `ALTER TABLE` statement, append `ON UPDATE RESTRICT ON 
DELETE RESTRICT`.
-* Suppress trailing spaces and save.
-
-Usually, the above editions result in no change compared to the previous 
scripts (ignoring white spaces),
-in which case the maintainer can just revert the changes in order to preserve 
the formatting.
-However, if some changes are found in the schema, then hard-coded values in 
the `DataScriptFormatter` class
-may need to be modified, in particular the `booleanColumns` and 
`doubleColumns` collections.
-
-
-### Automatic updates after the manual checks
-
-Execute the `main` method of the 
`org.apache.sis.referencing.factory.sql.epsg.*Updater` classes
-located in the test directory of the `org.apache.sis.non-free:sis-epsg` Maven 
sub-project.
-Adjust version numbers as needed in the following commands:
-
-```bash
 cd _<path to SIS project directory>_
 gradle clean test jar
 export 
CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar

Reply via email to