You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2025/08/14 11:59:59 UTC
(sis) branch geoapi-4.0 updated: Update the EPSG database installer: Contains also the following changes:
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 967360a5f7 Update the EPSG database installer: Contains also the following changes:
967360a5f7 is described below
commit 967360a5f7c37127c9e1cec5a00605c5246168f0
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Aug 10 13:43:00 2025 +0200
Update the EPSG database installer:
Contains also the following changes:
* Replax `Datum` / `DatumEnsemble` comparison in `ALLOW_VARIANT` mode.
* Rename `epsg_` table names to the camel case convention used by Apache SIS.
* Use a consistent naming convention for the tables that are new in EPSG 10+.
* Rename the enumerations to the final names that they have in the database.
* Finer conversion to `VARCHAR` for databases that do not support enumerations.
* Replace most of `ScriptRunner.isFooSupported` flags by `Dialect` enumeration.
* Add `StringBuilders.trimWhitespaces` utility method.
* Exclude deprecated or unused columns in EPSG scripts.
---
.../org/apache/sis/metadata/sql/Installer.java | 2 +-
.../org/apache/sis/metadata/sql/privy/Dialect.java | 78 +++-
.../sis/metadata/sql/privy/ScriptRunner.java | 274 ++++--------
.../apache/sis/metadata/sql/privy/Supports.java | 41 +-
.../main/org/apache/sis/temporal/TemporalDate.java | 2 +-
.../org/apache/sis/metadata/sql/TestDatabase.java | 2 +-
.../sis/metadata/sql/privy/ScriptRunnerTest.java | 9 +-
.../sis/metadata/sql/privy/SupportsTest.java} | 34 +-
.../sis/referencing/crs/AbstractSingleCRS.java | 32 +-
.../org/apache/sis/referencing/cs/AbstractCS.java | 2 +-
.../sis/referencing/datum/AbstractDatum.java | 2 +-
.../referencing/factory/sql/AuthorityCodes.java | 2 +-
.../referencing/factory/sql/EPSGCodeFinder.java | 2 +-
.../referencing/factory/sql/EPSGDataAccess.java | 24 +-
.../sis/referencing/factory/sql/EPSGFactory.java | 54 +--
.../sis/referencing/factory/sql/EPSGInstaller.java | 190 ++------
.../sis/referencing/factory/sql/EPSG_Finish.sql | 117 ++---
.../sis/referencing/factory/sql/EPSG_Prepare.sql | 32 +-
.../factory/sql/InstallationScriptProvider.java | 31 +-
.../sis/referencing/factory/sql/SQLTranslator.java | 137 +++---
.../sis/referencing/factory/sql/TableInfo.java | 36 +-
.../operation/AbstractCoordinateOperation.java | 2 +-
.../referencing/factory/sql/EPSGInstallerTest.java | 40 +-
.../main/org/apache/sis/pending/jdk/JDK22.java | 11 +
.../main/org/apache/sis/util/ComparisonMode.java | 62 ++-
.../main/org/apache/sis/util/StringBuilders.java | 23 +-
.../org/apache/sis/util/StringBuildersTest.java | 27 +-
.../apache/sis/storage/geopackage/GpkgStore.java | 2 +-
.../sis/referencing/factory/sql/epsg/Clear.sql | 43 ++
.../factory/sql/epsg/DataScriptFormatter.java | 495 +++++++--------------
.../referencing/factory/sql/epsg/DebugTools.sql | 17 +-
.../sis/referencing/factory/sql/epsg/README.md | 23 +-
.../referencing/factory/sql/epsg/TableValues.java | 364 +++++++++++++++
.../sis/referencing/factory/sql/epsg/Updater.java | 73 +++
34 files changed, 1296 insertions(+), 989 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 853d1a5889..82d78f034c 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
@@ -51,7 +51,7 @@ final class Installer extends ScriptRunner {
* @throws SQLException if an error occurred while executing a SQL statement.
*/
Installer(final Connection connection) throws SQLException {
- super(connection, 100);
+ super(connection, null, 100);
if (isEnumTypeSupported) {
enumTypes = null;
} else {
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
index 816741e419..f4f6e0e7e6 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Dialect.java
@@ -36,9 +36,9 @@ public enum Dialect {
* @see DatabaseMetaData#supportsANSI92EntryLevelSQL()
*/
ANSI(null, Supports.ALTER_TABLE_WITH_ADD_CONSTRAINT
- | Supports.JAVA_TIME
| Supports.READ_ONLY_UPDATE
| Supports.CONCURRENCY
+ | Supports.JAVA_TIME
| Supports.SRID),
/**
@@ -57,9 +57,9 @@ public enum Dialect {
* It also have a {@code SHUTDOWN} command which is specific to HSQLDB.
*/
HSQL("hsqldb", Supports.ALTER_TABLE_WITH_ADD_CONSTRAINT
- | Supports.JAVA_TIME
| Supports.READ_ONLY_UPDATE
- | Supports.CONCURRENCY),
+ | Supports.CONCURRENCY
+ | Supports.JAVA_TIME),
/**
* The database uses PostgreSQL syntax. This is ANSI, but provided an a separated
@@ -67,18 +67,21 @@ public enum Dialect {
*/
POSTGRESQL("postgresql", Supports.TABLE_INHERITANCE
| Supports.ALTER_TABLE_WITH_ADD_CONSTRAINT
- | Supports.JAVA_TIME
+ | Supports.GRANT_USAGE_ON_SCHEMA
+ | Supports.GRANT_SELECT_ON_TABLE
+ | Supports.COMMENT
| Supports.READ_ONLY_UPDATE
| Supports.CONCURRENCY
+ | Supports.JAVA_TIME
| Supports.SRID),
/**
* The database uses Oracle syntax. This is ANSI, but without {@code "AS"} keyword.
*/
ORACLE("oracle", Supports.ALTER_TABLE_WITH_ADD_CONSTRAINT
- | Supports.JAVA_TIME
| Supports.READ_ONLY_UPDATE
| Supports.CONCURRENCY
+ | Supports.JAVA_TIME
| Supports.SRID),
/**
@@ -133,7 +136,7 @@ public enum Dialect {
}
/**
- * Whether this dialect support table inheritance.
+ * Whether this dialect supports table inheritance.
*/
public final boolean supportsTableInheritance() {
return (flags & Supports.TABLE_INHERITANCE) != 0;
@@ -150,7 +153,7 @@ public enum Dialect {
}
/**
- * Whether this dialect support adding table constraints after creation.
+ * Whether this dialect supports adding table constraints after creation.
* This feature is not yet supported in SQLite.
*
* @see DatabaseMetaData#supportsAlterTableWithAddColumn()
@@ -160,15 +163,50 @@ public enum Dialect {
}
/**
- * Whether the JDBC driver supports conversions from objects to {@code java.time} API.
- * The JDBC 4.2 specification provides a mapping from {@link java.sql.Types} to temporal objects.
- * The specification suggests that {@link java.sql.ResultSet#getObject(int, Class)} should accept
- * those temporal types in the {@link Class} argument, but not all drivers support that.
+ * {@code true} if the database supports {@code "GRANT USAGE ON SCHEMA"} statements.
+ * Read-only permissions are typically granted to {@code PUBLIC}.
+ * Example:
*
- * @see <a href="https://jcp.org/aboutJava/communityprocess/maintenance/jsr221/JDBC4.2MR-January2014.pdf">JDBC Maintenance Release 4.2</a>
+ * {@snippet lang="sql" :
+ * GRANT USAGE ON SCHEMA metadata TO PUBLIC;
+ * }
*/
- public final boolean supportsJavaTime() {
- return (flags & Supports.JAVA_TIME) != 0;
+ public final boolean supportsGrantUsageOnSchema() {
+ return (flags & Supports.GRANT_USAGE_ON_SCHEMA) != 0;
+ }
+
+ /**
+ * {@code true} if the database supports {@code "GRANT SELECT ON TABLE"} statements.
+ * Read-only permissions are typically granted to {@code PUBLIC}.
+ * Example:
+ *
+ * {@snippet lang="sql" :
+ * GRANT SELECT ON TABLE "Coordinate Reference System" TO PUBLIC;
+ * }
+ *
+ */
+ public final boolean supportsGrantSelectOnTable() {
+ return (flags & Supports.GRANT_SELECT_ON_TABLE) != 0;
+ }
+
+ /**
+ * Whether this dialect supports all the {@code "GRANT … ON …"} features documented in this class.
+ */
+ public final boolean supportsAllGrants() {
+ return (flags & (Supports.GRANT_USAGE_ON_SCHEMA | Supports.GRANT_SELECT_ON_TABLE))
+ == (Supports.GRANT_USAGE_ON_SCHEMA | Supports.GRANT_SELECT_ON_TABLE);
+ }
+
+ /**
+ * {@code true} if the database supports the {@code COMMENT} statement.
+ * Example:
+ *
+ * {@snippet lang="sql" :
+ * COMMENT ON SCHEMA metadata IS 'ISO 19115 metadata';
+ * }
+ */
+ public final boolean supportsComment() {
+ return (flags & Supports.COMMENT) != 0;
}
/**
@@ -187,6 +225,18 @@ public enum Dialect {
return (flags & Supports.CONCURRENCY) != 0;
}
+ /**
+ * Whether the JDBC driver supports conversions from objects to {@code java.time} API.
+ * The JDBC 4.2 specification provides a mapping from {@link java.sql.Types} to temporal objects.
+ * The specification suggests that {@link java.sql.ResultSet#getObject(int, Class)} should accept
+ * those temporal types in the {@link Class} argument, but not all drivers support that.
+ *
+ * @see <a href="https://jcp.org/aboutJava/communityprocess/maintenance/jsr221/JDBC4.2MR-January2014.pdf">JDBC Maintenance Release 4.2</a>
+ */
+ public final boolean supportsJavaTime() {
+ return (flags & Supports.JAVA_TIME) != 0;
+ }
+
/**
* Whether the spatial extension supports <abbr>SRID</abbr> in {@code ST_*} functions.
*/
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/ScriptRunner.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/ScriptRunner.java
index 4883581f48..788d1836b3 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/ScriptRunner.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/ScriptRunner.java
@@ -21,7 +21,6 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
-import java.util.function.BiFunction;
import java.io.FileNotFoundException;
import java.io.EOFException;
import java.io.IOException;
@@ -56,14 +55,6 @@ import org.apache.sis.util.resources.Errors;
* @author Johann Sorel (Geomatys)
*/
public class ScriptRunner implements AutoCloseable {
- /**
- * The database user having read (not write) permissions.
- *
- * @see #isGrantOnSchemaSupported
- * @see #isGrantOnTableSupported
- */
- protected static final String PUBLIC = "PUBLIC";
-
/**
* The sequence for SQL comments. Leading lines starting by those characters will be ignored.
*/
@@ -87,16 +78,6 @@ public class ScriptRunner implements AutoCloseable {
*/
private static final char END_OF_STATEMENT = ';';
- /**
- * The characters for escaping a portion of the SQL script. This is used by PostgreSQL
- * for the definition of triggers. Those characters should appear at the beginning of
- * a line (ignoring whitespaces), because the text before it will not be parsed.
- *
- * <p>This string shall not begin with a whitespace or
- * {@linkplain Character#isUnicodeIdentifierPart(int) Unicode identifier part}.</p>
- */
- private static final String ESCAPE = "$BODY$";
-
/**
* The presumed dialect spoken by the database.
*/
@@ -108,7 +89,7 @@ public class ScriptRunner implements AutoCloseable {
*
* @see #addReplacement(String, String)
*/
- private final Map<String,String> replacements = new HashMap<>();
+ private final Map<String,String> replacements;
/**
* The quote character for identifiers actually used in the database,
@@ -136,73 +117,6 @@ public class ScriptRunner implements AutoCloseable {
*/
protected final boolean isEnumTypeSupported;
- /**
- * {@code true} if the database supports catalogs.
- */
- protected final boolean isCatalogSupported;
-
- /**
- * {@code true} if the database supports schemas.
- */
- protected final boolean isSchemaSupported;
-
- /**
- * {@code true} if the database supports {@code "GRANT USAGE ON SCHEMA"} statements.
- * Read-only permissions are typically granted to {@link #PUBLIC}.
- * Example:
- *
- * {@snippet lang="sql" :
- * GRANT USAGE ON SCHEMA metadata TO PUBLIC;
- * }
- *
- * @see #statementsToSkip
- */
- protected final boolean isGrantOnSchemaSupported;
-
- /**
- * {@code true} if the database supports {@code "GRANT SELECT ON TABLE"} statements.
- * Read-only permissions are typically granted to {@link #PUBLIC}.
- * Example:
- *
- * {@snippet lang="sql" :
- * GRANT SELECT ON TABLE epsg_coordinatereferencesystem TO PUBLIC;
- * }
- *
- * @see #statementsToSkip
- */
- protected final boolean isGrantOnTableSupported;
-
- /**
- * {@code true} if the database supports the {@code COMMENT} statement.
- * Example:
- *
- * {@snippet lang="sql" :
- * COMMENT ON SCHEMA metadata IS 'ISO 19115 metadata';
- * }
- *
- * @see #statementsToSkip
- */
- protected final boolean isCommentSupported;
-
- /**
- * {@code true} if the following instruction shall be executed
- * (assuming that the PostgreSQL {@code "plpgsql"} language is desired):
- *
- * {@code sql
- * CREATE TRUSTED PROCEDURAL LANGUAGE 'plpgsql'
- * HANDLER plpgsql_call_handler
- * VALIDATOR plpgsql_validator;
- * }
- *
- * <p>Notes per database product:</p>
- * <ul>
- * <li><b>PostgreSQL:</b> {@code true} only for database prior to version 9.
- * Starting at version 9, the language is installed by default.</li>
- * <li><b>Other databases:</b> {@code false} because not supported.</li>
- * </ul>
- */
- protected final boolean isCreateLanguageRequired;
-
/**
* The maximum number of rows allowed per {@code "INSERT"} statement.
* This is 1 if the database does not support multi-rows insertion.
@@ -223,9 +137,9 @@ public class ScriptRunner implements AutoCloseable {
*
* <ul>
* <li>{@link #isEnumTypeSupported} for {@code "CREATE TYPE …"} or {@code "CREATE CAST …"} statements.</li>
- * <li>{@link #isGrantOnSchemaSupported} for {@code "GRANT USAGE ON SCHEMA …"} statements.</li>
- * <li>{@link #isGrantOnTableSupported} for {@code "GRANT SELECT ON TABLE …"} statements.</li>
- * <li>{@link #isCommentSupported} for {@code "COMMENT ON …"} 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>
* </ul>
*/
private Matcher statementsToSkip;
@@ -260,52 +174,50 @@ public class ScriptRunner implements AutoCloseable {
*
* <p>Some {@code maxRowsPerInsert} parameter values of interest:</p>
* <ul>
- * <li>A value of 0 means to create only the schemas without inserting any data in them.</li>
+ * <li>A value of 0 means to create only the schema without inserting any data in them.</li>
* <li>A value of 1 means to use one separated {@code INSERT INTO} statement for each row, which may be slow.</li>
* <li>A value of 100 is a value which have been found empirically as giving good results.</li>
* <li>A value of {@link Integer#MAX_VALUE} means to not perform any attempt to limit the number of rows in an
* {@code INSERT INTO} statement. Note that this causes {@link StackOverflowError} in some JDBC driver.</li>
* </ul>
*
+ * The {@code schemaToCreate} argument is ignored if not supported by the database.
+ *
* @param connection the connection to the database.
+ * @param schemaToCreate schema to create and set as the default schema, or {@code null} if none.
* @param maxRowsPerInsert maximum number of rows per {@code "INSERT INTO"} statement.
* @throws SQLException if an error occurred while creating a SQL statement.
*/
- public ScriptRunner(final Connection connection, final int maxRowsPerInsert) throws SQLException {
+ public ScriptRunner(final Connection connection, final String schemaToCreate, final int maxRowsPerInsert) throws SQLException {
ArgumentChecks.ensurePositive("maxRowsPerInsert", maxRowsPerInsert);
- final DatabaseMetaData metadata = connection.getMetaData();
- this.maxRowsPerInsert = maxRowsPerInsert;
- this.dialect = Dialect.guess(metadata);
- this.identifierQuote = metadata.getIdentifierQuoteString();
- this.isSchemaSupported = metadata.supportsSchemasInTableDefinitions() &&
- metadata.supportsSchemasInDataManipulation();
- this.isCatalogSupported = metadata.supportsCatalogsInTableDefinitions() &&
- metadata.supportsCatalogsInDataManipulation();
- this.statement = connection.createStatement();
+ final DatabaseMetaData metadata;
+ this.maxRowsPerInsert = maxRowsPerInsert;
+ replacements = new HashMap<>();
+ metadata = connection.getMetaData();
+ dialect = Dialect.guess(metadata);
+ identifierQuote = metadata.getIdentifierQuoteString();
+ if (schemaToCreate != null && metadata.supportsSchemasInTableDefinitions()) {
+ try (Statement stmt = connection.createStatement()) {
+ stmt.executeUpdate("CREATE SCHEMA " + identifierQuote + schemaToCreate + identifierQuote);
+ if (dialect.supportsGrantUsageOnSchema()) {
+ stmt.executeUpdate("GRANT USAGE ON SCHEMA " + identifierQuote + schemaToCreate + identifierQuote + " TO PUBLIC");
+ }
+ }
+ connection.setSchema(schemaToCreate); // Must be set before the next call to `createStatement()` below.
+ }
+ statement = connection.createStatement();
switch (dialect) {
default: {
- isEnumTypeSupported = false;
- isGrantOnSchemaSupported = false;
- isGrantOnTableSupported = false;
- isCreateLanguageRequired = false;
- isCommentSupported = false;
+ isEnumTypeSupported = false;
break;
}
case POSTGRESQL: {
final int version = metadata.getDatabaseMajorVersion();
- isEnumTypeSupported = (version == 8) ? metadata.getDatabaseMinorVersion() >= 4 : version >= 8;
- isGrantOnSchemaSupported = true;
- isGrantOnTableSupported = true;
- isCreateLanguageRequired = (version < 9);
- isCommentSupported = true;
+ isEnumTypeSupported = (version == 8) ? metadata.getDatabaseMinorVersion() >= 4 : version >= 8;
break;
}
case HSQL: {
- isEnumTypeSupported = false;
- isGrantOnSchemaSupported = false;
- isGrantOnTableSupported = false;
- isCreateLanguageRequired = false;
- isCommentSupported = false;
+ isEnumTypeSupported = 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,
@@ -325,18 +237,18 @@ public class ScriptRunner implements AutoCloseable {
if (!isEnumTypeSupported) {
addStatementToSkip("CREATE\\s+(?:TYPE|CAST)\\s+.*");
}
- if (!isGrantOnSchemaSupported || !isGrantOnTableSupported) {
+ if (!dialect.supportsAllGrants()) {
addStatementToSkip("GRANT\\s+\\w+\\s+ON\\s+");
- if (isGrantOnSchemaSupported) {
+ if (dialect.supportsGrantUsageOnSchema()) {
regexOfStmtToSkip.append("TABLE");
- } else if (isGrantOnTableSupported) {
+ } else if (dialect.supportsGrantSelectOnTable()) {
regexOfStmtToSkip.append("SCHEMA");
} else {
regexOfStmtToSkip.append("(?:TABLE|SCHEMA)");
}
regexOfStmtToSkip.append("\\s+.*");
}
- if (!isCommentSupported) {
+ if (!dialect.supportsComment()) {
addStatementToSkip("COMMENT\\s+ON\\s+.*");
}
if (!dialect.supportsAlterTableWithAddConstraint()) {
@@ -359,9 +271,9 @@ public class ScriptRunner implements AutoCloseable {
*
* <ul>
* <li>{@code "CREATE TYPE …"} or {@code "CREATE CAST …"} if {@link #isEnumTypeSupported} is {@code false}.</li>
- * <li>{@code "GRANT USAGE ON SCHEMA …"} if {@link #isGrantOnSchemaSupported} is {@code false}.</li>
- * <li>{@code "GRANT SELECT ON TABLE …"} if {@link #isGrantOnTableSupported} is {@code false}.</li>
- * <li>{@code "COMMENT ON …"} if {@link #isCommentSupported} 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>
* </ul>
*
* This method can be invoked for ignoring some additional statements.
@@ -380,8 +292,10 @@ public class ScriptRunner implements AutoCloseable {
}
/**
- * Declares that a word in the SQL script needs to be replaced by the given word.
- * The replacement is performed only for occurrences outside identifiers or texts.
+ * Declares that a word in the <abbr>SQL</abbr> script needs to be replaced by the given word.
+ * The replacement is performed only for occurrences outside quoted identifiers or texts.
+ * For replacement of texts or identifiers, see {@link #editText(StringBuilder, int, int)}
+ * and {@link #editQuotedIdentifier(StringBuilder, int, int)} instead.
*
* <h4>Example</h4>
* This is used for mapping the <abbr>EPSG</abbr> table names from the mixed-cases convention used in
@@ -393,7 +307,7 @@ public class ScriptRunner implements AutoCloseable {
* understood by the database. For example, if a database does not support the {@code "TEXT"} data type,
* this method can be used for replacing {@code "TEXT"} by {@code "LONG VARCHAR"}.</p>
*
- * <h4>Limitation</h3>
+ * <h4>Limitation</h4>
* The {@code inScript} word to replace must be a single word with no space.
* If the text to replace contains two words (for example {@code "CREATE TABLE"}), then revert
* commit {@code bceb569558bfb7e3cf1a14aaf9261e786db06856} for bringing back this functionality.
@@ -418,16 +332,6 @@ public class ScriptRunner implements AutoCloseable {
return replacements.getOrDefault(inScript, inScript);
}
- /**
- * For every entries in the replacements map, replaces the entry value by the value returned by
- * {@code function(key, value)}.
- *
- * @param function the function that modify the replacement mapping.
- */
- protected final void modifyReplacements(final BiFunction<String,String,String> function) {
- replacements.replaceAll(function);
- }
-
/**
* Runs the given SQL script.
* Lines are read and grouped up to the terminal {@value #END_OF_STATEMENT} character, then sent to the database.
@@ -486,12 +390,14 @@ public class ScriptRunner implements AutoCloseable {
public final int run(final String filename, final BufferedReader in) throws IOException, SQLException {
currentFile = filename;
currentLine = 0;
- int statementCount = 0; // For informative purpose only.
- int posOpeningQuote = -1; // -1 if we are not inside a text.
- boolean isInsideIdentifier = false;
+ int statementCount = 0; // For informative purpose only.
+ int posOpeningTextQuote = -1; // -1 if we are not inside a text.
+ int posOpeningIdentifierQuote = -1;
final var buffer = new StringBuilder();
+ final boolean hasReplacements = !replacements.isEmpty();
String line;
while ((line = in.readLine()) != null) {
+ currentLine++;
/*
* Ignore empty lines and comment lines, but only if they appear at the begining of the SQL statement.
*/
@@ -506,44 +412,23 @@ public class ScriptRunner implements AutoCloseable {
} else {
buffer.append('\n');
}
- /*
- * If we find the "$BODY$" string, copy verbatism (without any attempt to parse the lines) until
- * the next occurrence of "$BODY$". This simple algorithm does not allow more than one block of
- * "$BODY$ ... $BODY$" on the same statement and presumes that the text before "$BODY$" contains
- * nothing that need to be parsed.
- */
- int pos = line.indexOf(ESCAPE);
- if (pos >= 0) {
- pos += ESCAPE.length();
- while ((pos = line.indexOf(ESCAPE, pos)) < 0) {
- buffer.append(line).append('\n');
- line = in.readLine();
- if (line == null) {
- throw new EOFException();
- }
- pos = 0;
- }
- pos += ESCAPE.length();
- buffer.append(line, 0, pos);
- line = line.substring(pos);
- }
/*
* Copy the current line in the buffer. Then, the loop will search for words or characters to replace
* (for example replacements of IDENTIFIER_QUOTE character by the database-specific quote character).
* Replacements (if any) will be performed in-place in the buffer. Concequently the buffer length may
* vary during the loop execution.
*/
- pos = buffer.length();
+ int pos = buffer.length();
int length = buffer.append(line).length();
parseLine: while (pos < length) {
int c = buffer.codePointAt(pos);
int n = Character.charCount(c);
- if (posOpeningQuote < 0 && !isInsideIdentifier) {
+ if ((posOpeningTextQuote & posOpeningIdentifierQuote) < 0) { // True if both positions are -1.
int start = pos;
while (Character.isUnicodeIdentifierStart(c)) {
/*
- * 'start' is the position of the first character of a Unicode identifier. Following loop
- * sets 'pos' to the end (exclusive) of that Unicode identifier. Variable 'c' will be set
+ * `start` is the position of the first character of a Unicode identifier. Following loop
+ * sets `pos` to the end (exclusive) of that Unicode identifier. Variable `c` will be set
* to the character after the Unicode identifier, provided that we have not reached EOL.
*/
while ((pos += n) < length) {
@@ -553,17 +438,19 @@ parseLine: while (pos < length) {
}
/*
* Perform in-place replacement if the Unicode identifier is one of the keys listed
- * in the 'replacements' map. This operation may change the buffer length. The 'pos'
+ * in the `replacements` map. This operation may change the buffer length. The `pos`
* must be updated if needed for staying at position after the Unicode identifier.
*/
- final String word = buffer.substring(start, pos);
- final String replace = replacements.get(word);
- if (replace != null) {
- length = buffer.replace(start, pos, replace).length();
- pos = start + replace.length();
+ if (hasReplacements) {
+ final String word = buffer.substring(start, pos);
+ final String replace = replacements.get(word);
+ if (replace != null) {
+ length = buffer.replace(start, pos, replace).length();
+ pos = start + replace.length();
+ }
}
/*
- * Skip whitespaces and set the 'c' variable to the next character, which may be either
+ * Skip whitespaces and set the `c` variable to the next character, which may be either
* another Unicode start (to be processed by the enclosing loop) or another character
* (to be processed by the switch statement after the enclosing loop).
*/
@@ -583,10 +470,17 @@ parseLine: while (pos < length) {
* replace the standard quote character by the database-specific one.
*/
case IDENTIFIER_QUOTE: {
- if (posOpeningQuote < 0) {
- isInsideIdentifier = !isInsideIdentifier;
+ if (posOpeningTextQuote < 0) {
length = buffer.replace(pos, pos + n, identifierQuote).length();
n = identifierQuote.length();
+ if (posOpeningIdentifierQuote < 0) {
+ posOpeningIdentifierQuote = pos;
+ } else {
+ editQuotedIdentifier(buffer, posOpeningIdentifierQuote, pos += n);
+ pos -= length - (length = buffer.length());
+ posOpeningIdentifierQuote = -1;
+ continue; // Because we already skipped the " character.
+ }
}
break;
}
@@ -595,13 +489,13 @@ parseLine: while (pos < length) {
* found the opening or closing character, ignoring the '' escape sequence.
*/
case QUOTE: {
- if (!isInsideIdentifier) {
- if (posOpeningQuote < 0) {
- posOpeningQuote = pos;
+ if (posOpeningIdentifierQuote < 0) {
+ if (posOpeningTextQuote < 0) {
+ posOpeningTextQuote = pos;
} else if ((pos += n) >= length || buffer.codePointAt(pos) != QUOTE) {
- editText(buffer, posOpeningQuote, pos);
+ editText(buffer, posOpeningTextQuote, pos);
pos -= length - (length = buffer.length());
- posOpeningQuote = -1;
+ posOpeningTextQuote = -1;
continue; // Because we already skipped the ' character.
} // else found a double ' character, which means to escape it.
}
@@ -612,7 +506,7 @@ parseLine: while (pos < length) {
* since SQL statement in JDBC are not expected to contain it.
*/
case END_OF_STATEMENT: {
- if (posOpeningQuote < 0 && !isInsideIdentifier) {
+ if ((posOpeningTextQuote & posOpeningIdentifierQuote) < 0) { // True if both are -1.
if (CharSequences.skipLeadingWhitespaces(buffer, pos + n, length) >= length) {
buffer.setLength(pos);
}
@@ -635,7 +529,8 @@ parseLine: while (pos < length) {
}
/**
- * Invoked for each text found in a SQL statement. The text, <em>including its quote characters</em>,
+ * Invoked for each single-quoted text found in a <abbr>SQL</abbr> statement.
+ * The text, <em>including its single quote characters</em>,
* is the {@code sql} substring from index {@code lower} inclusive to {@code upper} exclusive.
* Subclasses can override this method if they wish to modify the text content.
* Modifications are applied directly in the given {@code sql} buffer.
@@ -647,6 +542,20 @@ parseLine: while (pos < length) {
protected void editText(final StringBuilder sql, final int lower, final int upper) {
}
+ /**
+ * Invoked for each double-quoted identifier found in a <abbr>SQL</abbr> statement.
+ * The identifier, <em>including its double quote characters</em>,
+ * is the {@code sql} substring from index {@code lower} inclusive to {@code upper} exclusive.
+ * Subclasses can override this method if they wish to modify the identifier.
+ * Modifications are applied directly in the given {@code sql} buffer.
+ *
+ * @param sql the whole SQL statement.
+ * @param lower index of the opening quote character (usually {@code "}) of the identifier in {@code sql}.
+ * @param upper index after the closing quote character (usually {@code "}) of the identifier in {@code sql}.
+ */
+ protected void editQuotedIdentifier(final StringBuilder sql, final int lower, final int upper) {
+ }
+
/**
* Returns {@code true} if the given fragment seems outside identifier quotes or text quotes.
* The given fragment must be the beginning or the end of an SQL statement, or be bounded by
@@ -763,7 +672,7 @@ parseLine: while (pos < length) {
int nrows = maxRowsPerInsert;
int begin = endOfLine + 1;
while ((endOfLine = subSQL.indexOf('\n', ++endOfLine)) >= 0) {
- if (--nrows == 0) { // Extract lines until we have reached the 'maxRowsPerInsert' amount.
+ if (--nrows == 0) { // Extract lines until we have reached the `maxRowsPerInsert` amount.
int end = endOfLine;
if (subSQL.charAt(end - 1) == ',') {
end--;
@@ -810,8 +719,7 @@ parseLine: while (pos < length) {
public String status(final Locale locale) {
String position = null;
if (currentFile != null) {
- position = Errors.forLocale(locale).getString(Errors.Keys.ErrorInFileAtLine_2, currentFile,
- (currentLine != 0) ? currentLine : '?');
+ position = Errors.forLocale(locale).getString(Errors.Keys.ErrorInFileAtLine_2, currentFile, currentLine);
}
if (currentSQL != null) {
final var buffer = new StringBuilder();
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Supports.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Supports.java
index d03cbe4f97..4be4e20293 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Supports.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Supports.java
@@ -27,7 +27,7 @@ final class Supports {
/**
* Whether this dialect support table inheritance.
*/
- static final int TABLE_INHERITANCE = 1;
+ public static final int TABLE_INHERITANCE = 0x001;
/**
* Whether child tables inherit the index of their parent tables.
@@ -35,7 +35,7 @@ final class Supports {
*
* @see <a href="https://issues.apache.org/jira/browse/SIS-358">SIS-358</a>
*/
- static final int INDEX_INHERITANCE = 2;
+ public static final int INDEX_INHERITANCE = 0x002;
/**
* Whether this dialect support adding table constraints after creation.
@@ -43,33 +43,48 @@ final class Supports {
*
* @see DatabaseMetaData#supportsAlterTableWithAddColumn()
*/
- static final int ALTER_TABLE_WITH_ADD_CONSTRAINT = 4;
+ public static final int ALTER_TABLE_WITH_ADD_CONSTRAINT = 0x004;
/**
- * Whether the JDBC driver supports conversions from objects to {@code java.time} API.
- * The JDBC 4.2 specification provides a mapping from {@link java.sql.Types} to temporal objects.
- * The specification suggests that {@link java.sql.ResultSet#getObject(int, Class)} should accept
- * those temporal types in the {@link Class} argument, but not all drivers support that.
- *
- * @see <a href="https://jcp.org/aboutJava/communityprocess/maintenance/jsr221/JDBC4.2MR-January2014.pdf">JDBC Maintenance Release 4.2</a>
+ * Whether the database supports {@code "GRANT USAGE ON SCHEMA …"}.
*/
- static final int JAVA_TIME = 8;
+ public static final int GRANT_USAGE_ON_SCHEMA = 0x008;
+
+ /**
+ * Whether the database supports {@code "GRANT SELECT ON TABLE …"}.
+ */
+ public static final int GRANT_SELECT_ON_TABLE = 0x010;
+
+ /**
+ * Whether the database supports {@code "COMMENT ON …"}.
+ */
+ public static final int COMMENT = 0x020;
/**
* Whether the JDBC driver supports configuring readOnly mode on connection instances.
* This feature is not supported in SQLite.
*/
- static final int READ_ONLY_UPDATE = 16;
+ public static final int READ_ONLY_UPDATE = 0x040;
/**
* Whether the JDBC driver supports concurrent transactions.
*/
- static final int CONCURRENCY = 32;
+ public static final int CONCURRENCY = 0x080;
+
+ /**
+ * Whether the JDBC driver supports conversions from objects to {@code java.time} API.
+ * The JDBC 4.2 specification provides a mapping from {@link java.sql.Types} to temporal objects.
+ * The specification suggests that {@link java.sql.ResultSet#getObject(int, Class)} should accept
+ * those temporal types in the {@link Class} argument, but not all drivers support that.
+ *
+ * @see <a href="https://jcp.org/aboutJava/communityprocess/maintenance/jsr221/JDBC4.2MR-January2014.pdf">JDBC Maintenance Release 4.2</a>
+ */
+ public static final int JAVA_TIME = 0x100;
/**
* Whether the spatial extension supports <abbr>SRID</abbr> in {@code ST_*} functions.
*/
- static final int SRID = 64;
+ public static final int SRID = 0x200;
/**
* Do not allow instantiation of this class.
diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
index 86aa1a6259..e54b06c085 100644
--- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
+++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalDate.java
@@ -240,7 +240,7 @@ public final class TemporalDate extends Date implements LenientComparable {
*/
@Override
public boolean equals(final Object other, final ComparisonMode mode) {
- if (other instanceof TemporalDate && mode.ordinal() >= ComparisonMode.ALLOW_VARIANT.ordinal()) {
+ if (other instanceof TemporalDate && mode.allowsVariant()) {
/*
* The number of milliseconds may differ when comparing two local dates,
* depending on which timezone was assumed when `Date` has been constructed.
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
index ceb470cc21..44354c5649 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/TestDatabase.java
@@ -310,7 +310,7 @@ public class TestDatabase implements AutoCloseable {
* @throws ClassCastException if an element of the list is not a {@code String} or a {@code Supplier<InputStream>}.
*/
public final void executeSQL(final List<?> scripts) throws IOException, SQLException {
- try (Connection c = source.getConnection(); ScriptRunner r = new ScriptRunner(c, 1000)) {
+ try (Connection c = source.getConnection(); ScriptRunner r = new ScriptRunner(c, null, 1000)) {
for (final Object sql : scripts) {
if (sql instanceof String s) {
r.run(s);
diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/ScriptRunnerTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/ScriptRunnerTest.java
index 3507d656ae..4244019edc 100644
--- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/ScriptRunnerTest.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/ScriptRunnerTest.java
@@ -50,7 +50,7 @@ public final class ScriptRunnerTest extends TestCase {
try (TestDatabase db = TestDatabase.create("ScriptRunner");
Connection c = db.source.getConnection())
{
- final ScriptRunner sr = new ScriptRunner(c, 3);
+ final ScriptRunner sr = new ScriptRunner(c, null, 3);
testSupportedFlags(sr);
testRegularExpressions(sr);
}
@@ -63,12 +63,7 @@ public final class ScriptRunnerTest extends TestCase {
*/
@TestStep
public static void testSupportedFlags(final ScriptRunner sr) {
- assertFalse(sr.isCatalogSupported);
- assertTrue (sr.isSchemaSupported);
- assertFalse(sr.isGrantOnSchemaSupported);
- assertFalse(sr.isGrantOnTableSupported);
assertFalse(sr.isEnumTypeSupported);
- assertFalse(sr.isCommentSupported);
}
/**
@@ -83,7 +78,7 @@ public final class ScriptRunnerTest extends TestCase {
assertFalse(sr.isSupported("CREATE CAST (VARCHAR AS CI_DateTypeCode) WITH INOUT AS ASSIGNMENT"));
assertTrue (sr.isSupported("CREATE TABLE CI_Citation (…)"));
assertFalse(sr.isSupported("GRANT USAGE ON SCHEMA metadata TO PUBLIC"));
- assertFalse(sr.isSupported("GRANT SELECT ON TABLE epsg_coordinatereferencesystem TO PUBLIC"));
+ assertFalse(sr.isSupported("GRANT SELECT ON TABLE \"Coordinate Reference System\" TO PUBLIC"));
assertFalse(sr.isSupported("COMMENT ON SCHEMA metadata IS 'ISO 19115 metadata'"));
}
}
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/SupportsTest.java
similarity index 50%
copy from endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java
copy to endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/SupportsTest.java
index adc7887cf7..18b2a33d45 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java
+++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/sql/privy/SupportsTest.java
@@ -14,31 +14,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.pending.jdk;
+package org.apache.sis.metadata.sql.privy;
-import java.io.Console;
+import java.lang.reflect.Field;
+
+// Test dependencies
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.*;
+import org.apache.sis.test.TestCase;
/**
- * Place holder for some functionalities defined in a JDK more recent than Java 11.
+ * Verifies the constants declared in {@link Supports}.
*
* @author Martin Desruisseaux (Geomatys)
*/
-public final class JDK22 {
+public final class SupportsTest extends TestCase {
/**
- * Do not allow instantiation of this class.
+ * Creates a new test case.
*/
- private JDK22() {
+ public SupportsTest() {
}
/**
- * Placeholder for {@code Console.isTerminal()}.
+ * Verifies that there is no collision between the constant values.
+ *
+ * @throws IllegalAccessException if a field is not public.
*/
- public static boolean isTerminal(Console c) {
- try {
- return (Boolean) Console.class.getMethod("isTerminal").invoke(c);
- } catch (ReflectiveOperationException e) {
- return true;
+ @Test
+ public void ensureNoCollision() throws IllegalAccessException {
+ int allFlags = 0;
+ for (Field field : Supports.class.getFields()) {
+ final int flag = field.getInt(null);
+ assertEquals(1, Integer.bitCount(flag));
+ assertNotEquals(allFlags, allFlags |= flag);
}
+ assertNotEquals(0, allFlags);
}
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
index 686ef5b11a..ac88426d4a 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/AbstractSingleCRS.java
@@ -31,6 +31,7 @@ import org.apache.sis.util.resources.Errors;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.referencing.cs.AbstractCS;
import org.apache.sis.referencing.internal.Resources;
+import org.apache.sis.metadata.privy.Identifiers;
import org.apache.sis.metadata.privy.ImplementationHelper;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -208,6 +209,22 @@ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements SingleCR
return ensemble;
}
+ /**
+ * Returns whether the given datum may be considered as equivalent to the given datum ensemble.
+ * Used for comparisons with {@link ComparisonMode#APPROXIMATE} for interoperability between
+ * the legacy and the new definition of EPSG:4326.
+ */
+ private static boolean isHeuristicMatchForName(final DatumEnsemble<?> ensemble, final Datum datum, final ComparisonMode mode) {
+ if (ensemble == null || datum == null) {
+ return false;
+ }
+ final Boolean match = Identifiers.hasCommonIdentifier(ensemble.getIdentifiers(), datum.getIdentifiers());
+ if (match != null) {
+ return match;
+ }
+ return IdentifiedObjects.isHeuristicMatchForName(datum, ensemble.getName().getCode());
+ }
+
/**
* Compares this coordinate reference system with the specified object for equality.
*
@@ -218,7 +235,7 @@ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements SingleCR
* @hidden because nothing new to said.
*/
@Override
- public boolean equals(final Object object, final ComparisonMode mode) {
+ public boolean equals(final Object object, ComparisonMode mode) {
if (super.equals(object, mode)) {
switch (mode) {
case STRICT: {
@@ -227,8 +244,17 @@ class AbstractSingleCRS<D extends Datum> extends AbstractCRS implements SingleCR
}
default: {
final var that = (SingleCRS) object;
- return Utilities.deepEquals(getDatum(), that.getDatum(), mode) &&
- Utilities.deepEquals(getDatumEnsemble(), that.getDatumEnsemble(), mode);
+ final var d1 = this.getDatum();
+ final var d2 = that.getDatum();
+ if (mode == ComparisonMode.DEBUG) {
+ mode = ComparisonMode.ALLOW_VARIANT; // For avoiding too early `AssertionError`.
+ }
+ if (Utilities.deepEquals(d1, d2, mode)) {
+ return mode.allowsVariant() || Utilities.deepEquals(getDatumEnsemble(), that.getDatumEnsemble(), mode);
+ } else if (mode.allowsVariant()) {
+ return isHeuristicMatchForName(this.getDatumEnsemble(), d2, mode) ||
+ isHeuristicMatchForName(that.getDatumEnsemble(), d1, mode);
+ }
}
}
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
index ffd308007d..feba4754fe 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/AbstractCS.java
@@ -586,7 +586,7 @@ next: for (final CoordinateSystemAxis axis : axes) {
if (dimension != that.getDimension()) {
return false;
}
- if (mode != ComparisonMode.ALLOW_VARIANT) {
+ if (!mode.allowsVariant()) {
for (int i=0; i<dimension; i++) {
if (!Utilities.deepEquals(getAxis(i), that.getAxis(i), mode)) {
return false;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
index c473e4e1d3..17be60c6da 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/AbstractDatum.java
@@ -451,7 +451,7 @@ public class AbstractDatum extends AbstractIdentifiedObject implements Datum {
* and parameters. We extend this rule to datum as well.
*/
final var that = (Datum) object;
- if (!compareDynamicReferenceFrames(that, mode)) {
+ if (!(mode.allowsVariant() || compareDynamicReferenceFrames(that, mode))) {
return false;
}
final Boolean match = Identifiers.hasCommonIdentifier(getIdentifiers(), that.getIdentifiers());
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 5a7eac4e08..27b01d3b7d 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
@@ -134,7 +134,7 @@ final class AuthorityCodes extends AbstractMap<String,String> implements Seriali
final int columnNameStart = buffer.append("SELECT ").length();
final int columnNameEnd = buffer.append(table.codeColumn).length();
buffer.append(" FROM ").append(table.table);
- final Class<?> tableType = table.where(type, buffer);
+ final Class<?> tableType = table.where(factory, type, buffer);
final int conditionStart = buffer.length();
if (table.showColumn != null) {
buffer.append(table.showColumn).append("<>0 AND ");
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 47975c6b37..cab3292126 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
@@ -418,7 +418,7 @@ crs: if (isInstance(CoordinateReferenceSystem.class, object)) {
* It may be absent (typically, only datums or reference frames have that condition).
*/
buffer.append("SELECT ").append(table.codeColumn).append(" FROM ").append(table.table);
- table.where(object, buffer); // Unconditionally append a "WHERE" clause.
+ table.where(dao, object, buffer); // Unconditionally append a "WHERE" clause.
boolean isNext = false;
for (final Condition filter : filters) {
isNext |= filter.appendToWhere(buffer, isNext);
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 59997d0496..49eca3b051 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
@@ -1596,7 +1596,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
*/
final FactoryCall<CRSFactory, CoordinateReferenceSystem> constructor;
/*
- * The following switch statement should have a case for all "epsg_crs_kind" values enumerated
+ * The following switch statement should have a case for all "CRS Kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases.
*/
switch (type.toLowerCase(Locale.US)) {
@@ -1918,7 +1918,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
*/
final FactoryCall<DatumFactory, ? extends Datum> constructor;
/*
- * The following switch statement should have a case for all "epsg_datum_kind" values enumerated
+ * The following switch statement should have a case for all "Datum Kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases.
*/
switch (type.toLowerCase(Locale.US)) {
@@ -2009,9 +2009,9 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
{
double max = Double.NaN;
try (ResultSet result = executeQueryForCodes(
- "DatumEnsemble",
+ "Datum Ensemble",
"SELECT ENSEMBLE_ACCURACY"
- + " FROM \"DatumEnsemble\""
+ + " FROM \"Datum Ensemble\""
+ " WHERE DATUM_ENSEMBLE_CODE = ?", code))
{
// Should have exactly one value. The loop is a paranoiac safety.
@@ -2025,9 +2025,9 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
final var accuracy = PositionalAccuracyConstant.ensemble(max);
final List<Datum> members = createComponents(
GeodeticAuthorityFactory::createDatum,
- "DatumEnsembleMember",
+ "Datum Ensemble Member",
"SELECT DATUM_CODE"
- + " FROM \"DatumEnsembleMember\""
+ + " FROM \"Datum Ensemble Member\""
+ " WHERE DATUM_ENSEMBLE_CODE = ?"
+ " ORDER BY DATUM_SEQUENCE", code);
return (factory, metadata) -> DefaultDatumEnsemble.castOrCopy(factory.createDatumEnsemble(metadata, members, accuracy));
@@ -2104,12 +2104,12 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
var returnValue = (IdentifiedObject) localCache.get(cacheKey);
if (returnValue == null) {
try (ResultSet result = executeQueryForCodes(
- "ConventionalRS",
+ "Conventional RS",
"SELECT"+ /* column 1 */ " CONVENTIONAL_RS_CODE,"
+ /* column 2 */ " CONVENTIONAL_RS_NAME,"
+ /* column 3 */ " REMARKS,"
+ /* column 4 */ " DEPRECATED"
- + " FROM \"ConventionalRS\""
+ + " FROM \"Conventional RS\""
+ " WHERE CONVENTIONAL_RS_CODE = ?", code))
{
while (result.next()) {
@@ -2123,7 +2123,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
*/
@SuppressWarnings("LocalVariableHidesMemberVariable")
final Map<String,Object> properties = createProperties(
- "ConventionalRS", epsg, name, null, null, null, remarks, deprecated);
+ "Conventional RS", epsg, name, null, null, null, remarks, deprecated);
returnValue = ensureSingleton(new AbstractIdentifiedObject(properties), returnValue, code);
if (result.isClosed()) break; // See createProperties(…) for explanation.
}
@@ -2506,7 +2506,7 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
final Map<String,Object> properties = createProperties(
"Coordinate System", epsg, name, null, null, null, remarks, deprecated);
/*
- * The following switch statement should have a case for all "epsg_cs_kind" values enumerated
+ * The following switch statement should have a case for all "CS Kind" values enumerated
* in the "EPSG_Prepare.sql" file, except that the values in this Java code are in lower cases.
*/
final CSFactory csFactory = owner.csFactory;
@@ -2723,9 +2723,9 @@ search: try (ResultSet result = executeMetadataQuery("Deprecation",
var returnValue = (RealizationMethod) localCache.get(cacheKey);
if (returnValue == null && code != null) {
try (ResultSet result = executeQueryForCodes(
- "DatumRealizationMethod",
+ "Datum Realization Method",
"SELECT REALIZATION_METHOD_NAME"
- + " FROM \"DatumRealizationMethod\""
+ + " FROM \"Datum Realization Method\""
+ " WHERE REALIZATION_METHOD_CODE = ?", code))
{
while (result.next()) {
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
index b01ecc5512..4c8dd3ac1b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGFactory.java
@@ -19,7 +19,6 @@ package org.apache.sis.referencing.factory.sql;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -146,8 +145,7 @@ public class EPSGFactory extends ConcurrentAuthorityFactory<EPSGDataAccess> impl
/**
* The name of the schema that contains the EPSG tables, or {@code null} or an empty string.
* <ul>
- * <li>The {@code ""} value retrieves the EPSG tables without a schema.
- * In such case, table names are prefixed by {@value SQLTranslator#TABLE_PREFIX}.</li>
+ * <li>The {@code ""} value retrieves the EPSG tables without a schema.</li>
* <li>The {@code null} value means that the schema name should not be used to narrow the search.
* In such case, {@link SQLTranslator} will tries to automatically detect the schema.</li>
* </ul>
@@ -354,8 +352,8 @@ public class EPSGFactory extends ConcurrentAuthorityFactory<EPSGDataAccess> impl
* If no provider is specified, then this method searches on the module path (with {@link java.util.ServiceLoader})
* for user-provided implementations of {@code InstallationScriptProvider}.
* If no user-specified provider is found, then this method will search for
- * {@code "EPSG_*Tables.sql"}, {@code "EPSG_*Data.sql"} and {@code "EPSG_*FKeys.sql"} files in the
- * {@code $SIS_DATA/Databases/ExternalSources} directory where {@code *} stands for any characters
+ * {@code "*Tables*.sql"}, {@code "*Data*.sql"} and {@code "*FKeys*.sql"} files in the
+ * {@code $SIS_DATA/Databases/ExternalSources/EPSG} directory where {@code *} stands for any characters
* provided that there is no ambiguity.</li>
* </ul>
*
@@ -378,36 +376,38 @@ public class EPSGFactory extends ConcurrentAuthorityFactory<EPSGDataAccess> impl
String message = null;
Exception failure = null;
boolean success = false;
- try (EPSGInstaller installer = new EPSGInstaller(Objects.requireNonNull(connection))) {
- final boolean ac = connection.getAutoCommit();
- if (ac) {
+ try {
+ if (catalog != null) {
+ connection.setCatalog(catalog);
+ }
+ final boolean autoCommit = connection.getAutoCommit();
+ if (autoCommit) {
connection.setAutoCommit(false);
}
- try {
+ try (EPSGInstaller installer = new EPSGInstaller(connection, schema)) {
try {
- if (!"".equals(schema)) { // Schema may be null.
- installer.setSchema(schema != null ? schema : Constants.EPSG);
- if (catalog != null && !catalog.isEmpty()) {
- installer.prependNamespace(catalog);
- }
- }
success = installer.run(scriptProvider, locale);
- } finally {
- if (ac) {
- if (success) {
- connection.commit();
- } else {
- connection.rollback();
- }
- connection.setAutoCommit(true);
+ } catch (IOException | SQLException e) {
+ message = installer.failure(locale);
+ failure = e;
+ }
+ } finally {
+ if (autoCommit) {
+ if (success) {
+ connection.commit();
+ } else {
+ connection.rollback();
}
+ connection.setAutoCommit(true);
}
- } catch (IOException | SQLException e) {
- message = installer.failure(locale);
- failure = e;
}
} catch (SQLException e) {
- failure = e;
+ if (failure != null) {
+ failure.addSuppressed(e);
+ } else {
+ failure = e;
+ }
+ success = false;
}
if (!success) {
if (message == null) {
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 124eb96682..28afba7e4b 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
@@ -21,14 +21,11 @@ import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.BufferedReader;
import java.sql.Connection;
-import java.sql.DatabaseMetaData;
import java.sql.SQLException;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
-import org.apache.sis.util.ArraysExt;
-import org.apache.sis.util.StringBuilders;
import org.apache.sis.util.privy.Constants;
-import static org.apache.sis.util.privy.Constants.EPSG;
import org.apache.sis.util.resources.Messages;
import org.apache.sis.util.logging.PerformanceLevel;
import org.apache.sis.metadata.sql.privy.ScriptRunner;
@@ -47,142 +44,44 @@ import org.apache.sis.setup.InstallationResources;
*/
final class EPSGInstaller extends ScriptRunner {
/**
- * The pattern for an {@code "UPDATE … SET … REPLACE"} instruction.
- * Example:
- *
- * {@snippet lang="sql" :
- * UPDATE epsg_datum
- * SET datum_name = replace(datum_name, CHAR(182), CHAR(10));
- * }
- *
- * Note: this regular expression uses a capturing group.
+ * The quoted identifiers to replace, or an empty map if none.
+ * Used for replacing enumeration types when not supported by the target database.
*/
- static final String REPLACE_STATEMENT =
- "UPDATE\\s+[\\w\\.\" ]+\\s+SET\\s+(\\w+)\\s*=\\s*replace\\s*\\(\\s*\\1\\W+.*";
+ private final Map<String, String> identifierReplacements;
/**
- * {@code true} if the Pilcrow character (¶ - decimal code 182) should be replaced by Line Feed
- * (LF - decimal code 10). This is a possible workaround when the database does not support the
- * {@code REPLACE(column, CHAR(182), CHAR(10))} SQL statement, but accepts LF.
+ * Whether to apply the replacements in the {@link #identifierReplacements} map.
*/
- private final boolean replacePilcrow;
+ private boolean applyReplacements;
/**
* Creates a new runner which will execute the statements using the given connection.
- * The encoding is {@code "ISO-8859-1"}, which is the encoding used for the files provided by EPSG.
+ * This constructor creates immediately a schema of the given name in the database.
*
* @param connection the connection to the database.
+ * @param schema the schema, or {@code null} for any, or empty for none.
* @throws SQLException if an error occurred while executing a SQL statement.
*/
- public EPSGInstaller(final Connection connection) throws SQLException {
- super(connection, 100);
- final DatabaseMetaData metadata = connection.getMetaData();
- final String[] functions = metadata.getStringFunctions().split("\\s*,\\s*");
- if (!ArraysExt.containsIgnoreCase(functions, "REPLACE")) {
- addStatementToSkip(REPLACE_STATEMENT);
+ public EPSGInstaller(final Connection connection, final String schema) throws SQLException {
+ super(connection, schema == null ? Constants.EPSG : schema.isEmpty() ? null : schema, 100);
+ if (isEnumTypeSupported) {
+ identifierReplacements = Map.of();
+ } else {
+ identifierReplacements = Map.of(
+ "Datum Kind", "VARCHAR(16)", // Original: VARCHAR(24) for column "datum_type".
+ "CRS Kind", "VARCHAR(13)", // Original: VARCHAR(24) for column "coord_ref_sys_kind".
+ "CS Kind", "VARCHAR(15)", // Original: VARCHAR(24) for column "coord_sys_type".
+ "Supersession Type", "VARCHAR(12)", // Original: VARCHAR(50) for column "supersession_type".
+ "Table Name", ENUM_REPLACEMENT); // Original: VARCHAR(80) for columns "object_table_name".
}
- /*
- * The SQL scripts provided by EPSG contains some lines with only a "COMMIT" statement.
- * This statement is not understood by all databases, and interferes with our calls to
- * setAutoCommit(false) ... commit() / rollback().
- */
- addStatementToSkip("COMMIT");
- replacePilcrow = false; // Never supported for now.
}
/**
- * Creates immediately a schema of the given name in the database.
- * The {@code "epsg_"} prefix in table names or the {@code "epsg"}
- * schema name will be replaced by the given schema name.
- *
- * <p>This method should be invoked only once. It does nothing if the database does not supports schema.</p>
- *
- * @param schema the schema (usually {@code "epsg"}).
- * @throws SQLException if the schema cannot be created.
- * @throws IOException if an I/O operation was required and failed.
+ * The <abbr>SQL</abbr> type to use as a replacement for enumerated values in databases that do not
+ * support enumerations. The maximal length declared in this constant should be the greatest length
+ * declared in {@code VARCHAR(…)} substitutions done when {@link #isEnumTypeSupported} is false.
*/
- public void setSchema(final String schema) throws SQLException, IOException {
- if (isSchemaSupported) {
- /*
- * Creates the schema on the database.
- * Note that we do not quote the schema name, which is a somewhat arbitrary choice.
- */
- final var sb = new StringBuilder(40);
- execute(sb.append("CREATE SCHEMA ").append(identifierQuote).append(schema).append(identifierQuote));
- if (isGrantOnSchemaSupported) {
- sb.setLength(0);
- execute(sb.append("GRANT USAGE ON SCHEMA ")
- .append(identifierQuote).append(schema).append(identifierQuote).append(" TO ").append(PUBLIC));
- }
- /*
- * Mapping from the table names used in the SQL scripts to the names used in `EPSGDataAccess`.
- * Those names are the ones that were used in the original EPSG dataset in MS-Access database.
- * We use those original names because they are easier to read than the names in SQL scripts.
- *
- * TODO: move to org.apache.sis.referencing.factory.sql.epsg.DataScriptFormatter.
- */
- addReplacement(SQLTranslator.TABLE_PREFIX + "alias", "Alias");
- addReplacement(SQLTranslator.TABLE_PREFIX + "area", "Area"); // Deprecated (removed in EPSG 10)
- addReplacement(SQLTranslator.TABLE_PREFIX + "change", "Change");
- addReplacement(SQLTranslator.TABLE_PREFIX + "conventionalrs", "ConventionalRS");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordinateaxis", "Coordinate Axis");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordinateaxisname", "Coordinate Axis Name");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperation", "Coordinate_Operation");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperationmethod", "Coordinate_Operation Method");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperationparam", "Coordinate_Operation Parameter");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperationparamusage", "Coordinate_Operation Parameter Usage");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperationparamvalue", "Coordinate_Operation Parameter Value");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordoperationpath", "Coordinate_Operation Path");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordinatereferencesystem", "Coordinate Reference System");
- addReplacement(SQLTranslator.TABLE_PREFIX + "coordinatesystem", "Coordinate System");
- addReplacement(SQLTranslator.TABLE_PREFIX + "datum", "Datum");
- addReplacement(SQLTranslator.TABLE_PREFIX + "datumensemble", "DatumEnsemble");
- addReplacement(SQLTranslator.TABLE_PREFIX + "datumensemblemember", "DatumEnsembleMember");
- addReplacement(SQLTranslator.TABLE_PREFIX + "datumrealizationmethod", "DatumRealizationMethod");
- addReplacement(SQLTranslator.TABLE_PREFIX + "definingoperation", "DefiningOperation");
- addReplacement(SQLTranslator.TABLE_PREFIX + "deprecation", "Deprecation");
- addReplacement(SQLTranslator.TABLE_PREFIX + "ellipsoid", "Ellipsoid");
- addReplacement(SQLTranslator.TABLE_PREFIX + "extent", "Extent");
- addReplacement(SQLTranslator.TABLE_PREFIX + "namingsystem", "Naming System");
- addReplacement(SQLTranslator.TABLE_PREFIX + "primemeridian", "Prime Meridian");
- addReplacement(SQLTranslator.TABLE_PREFIX + "scope", "Scope");
- addReplacement(SQLTranslator.TABLE_PREFIX + "supersession", "Supersession");
- addReplacement(SQLTranslator.TABLE_PREFIX + "unitofmeasure", "Unit of Measure");
- addReplacement(SQLTranslator.TABLE_PREFIX + "usage", "Usage");
- addReplacement(SQLTranslator.TABLE_PREFIX + "versionhistory", "Version History");
- if (isEnumTypeSupported) {
- addReplacement(SQLTranslator.TABLE_PREFIX + "datum_kind", "Datum Kind");
- addReplacement(SQLTranslator.TABLE_PREFIX + "crs_kind", "CRS Kind");
- addReplacement(SQLTranslator.TABLE_PREFIX + "cs_kind", "CS Kind");
- addReplacement(SQLTranslator.TABLE_PREFIX + "table_name", "Table Name");
- }
- prependNamespace(schema);
- }
- if (!isEnumTypeSupported) {
- addReplacement(SQLTranslator.TABLE_PREFIX + "datum_kind", TableInfo.ENUM_REPLACEMENT);
- addReplacement(SQLTranslator.TABLE_PREFIX + "crs_kind", TableInfo.ENUM_REPLACEMENT);
- addReplacement(SQLTranslator.TABLE_PREFIX + "cs_kind", TableInfo.ENUM_REPLACEMENT);
- addReplacement(SQLTranslator.TABLE_PREFIX + "table_name", TableInfo.ENUM_REPLACEMENT);
- }
- }
-
- /**
- * Prepends the given schema or catalog to all table names.
- */
- final void prependNamespace(final String schema) {
- modifyReplacements((key, value) -> {
- if (key.startsWith(SQLTranslator.TABLE_PREFIX)) {
- final var buffer = new StringBuilder(value.length() + schema.length() + 5);
- buffer.append(identifierQuote).append(schema).append(identifierQuote).append('.');
- final boolean isQuoted = value.endsWith(identifierQuote);
- if (!isQuoted) buffer.append(identifierQuote);
- buffer.append(value);
- if (!isQuoted) buffer.append(identifierQuote);
- value = buffer.toString();
- }
- return value;
- });
- }
+ static final String ENUM_REPLACEMENT = "VARCHAR(36)";
/**
* Invoked for each text found in a SQL statement. This method replaces {@code ''} by {@code Null}.
@@ -190,7 +89,7 @@ final class EPSGInstaller extends ScriptRunner {
* same as "information is an empty string". This replacement is okay in this particular case
* since there is no field in the EPSG database for which we really want an empty string.
*
- * @param sql the whole SQL statement.
+ * @param sql the whole <abbr>SQL</abbr> statement.
* @param lower index of the first character of the text in {@code sql}.
* @param upper index after the last character of the text in {@code sql}.
*/
@@ -201,11 +100,8 @@ final class EPSGInstaller extends ScriptRunner {
default: {
return;
}
- /*
- * Replace '' by Null for every table.
- */
case 2: {
- replacement = "Null";
+ replacement = "Null"; // Replace '' by Null for every table.
break;
}
}
@@ -213,17 +109,21 @@ final class EPSGInstaller extends ScriptRunner {
}
/**
- * Modifies the SQL statement before to execute it, or omit unsupported statements.
+ * Replaces an enumeration type by a standard type if enumerations are not supported.
*
- * @throws SQLException if an error occurred while executing the SQL statement.
- * @throws IOException if an I/O operation was required and failed.
+ * @param sql the whole <abbr>SQL</abbr> statement.
+ * @param lower index of the first character of the identifier in {@code sql}.
+ * @param upper index after the last character of the identifier in {@code sql}.
*/
@Override
- protected int execute(final StringBuilder sql) throws SQLException, IOException {
- if (replacePilcrow) {
- StringBuilders.replace(sql, "¶", "\n");
+ protected void editQuotedIdentifier(final StringBuilder sql, final int lower, final int upper) {
+ if (applyReplacements) {
+ final int n = identifierQuote.length();
+ final String r = identifierReplacements.get(sql.substring(lower + n, upper - n));
+ if (r != null) {
+ sql.replace(lower, upper, r); // This replacement removes the quotes.
+ }
}
- return super.execute(sql);
}
/**
@@ -247,13 +147,15 @@ final class EPSGInstaller extends ScriptRunner {
InstallationScriptProvider.log(Messages.forLocale(locale).createLogRecord(
Level.INFO,
Messages.Keys.CreatingSchema_2,
- EPSG,
+ Constants.EPSG,
SQLUtilities.getSimplifiedURL(getConnection().getMetaData())));
- final String[] scripts = scriptProvider.getResourceNames(EPSG);
+ final String[] scripts = scriptProvider.getResourceNames(Constants.EPSG);
int numRows = 0;
for (int i=0; i<scripts.length; i++) {
- try (BufferedReader in = scriptProvider.openScript(EPSG, i)) {
- numRows += run(scripts[i], in);
+ final String script = scripts[i];
+ applyReplacements = !(identifierReplacements.isEmpty() || InstallationScriptProvider.PREPARE.equals(script));
+ try (BufferedReader in = scriptProvider.openScript(Constants.EPSG, i)) {
+ numRows += run(script, in);
}
}
time = System.nanoTime() - time;
@@ -273,7 +175,7 @@ final class EPSGInstaller extends ScriptRunner {
* <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 using the SQL scripts in the {@code $SIS_DATA/Databases/ExternalSources} directory.
+ * <li>A provider using the SQL scripts in the {@code $SIS_DATA/Databases/ExternalSources/EPSG} directory.
* Users have to put those scripts in that directory manually. 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
@@ -287,7 +189,7 @@ final class EPSGInstaller extends ScriptRunner {
private static InstallationResources lookupProvider(final Locale locale) throws IOException {
InstallationResources fallback = null;
for (final InstallationResources provider : InstallationResources.load()) {
- if (provider.getAuthorities().contains(EPSG)) {
+ if (provider.getAuthorities().contains(Constants.EPSG)) {
if (!provider.getClass().isAnnotationPresent(Fallback.class)) {
return provider;
}
@@ -299,7 +201,7 @@ final class EPSGInstaller extends ScriptRunner {
* 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 user if (s)he accepts EPSG terms of use. But before to use those fallbacks, check if the
- * data have not been downloaded manually in the "$SIS_DATA/Databases/ExternalSources" directory.
+ * data have not been downloaded manually in the "$SIS_DATA/Databases/ExternalSources/EPSG" directory.
*/
final var manual = new InstallationScriptProvider.Default(locale);
return manual.getAuthorities().isEmpty() ? fallback : manual;
@@ -311,7 +213,7 @@ final class EPSGInstaller extends ScriptRunner {
* error occurred, if such information is available.
*/
final String failure(final Locale locale) {
- String message = Messages.forLocale(locale).getString(Messages.Keys.CanNotCreateSchema_1, EPSG);
+ String message = Messages.forLocale(locale).getString(Messages.Keys.CanNotCreateSchema_1, Constants.EPSG);
String status = status(locale);
if (status != null) {
message = message + ' ' + status;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
index 2c28d81c73..b649bf65ce 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
@@ -12,10 +12,10 @@
--
-- "Scale factor at natural origin" (EPSG:8805) shall be dimensionless (EPSG:9201), not in "m" units (EPSG:9001).
-UPDATE epsg_coordoperationparamvalue SET uom_code = 9201 WHERE parameter_code = 8805 AND uom_code = 9001;
+UPDATE "Coordinate_Operation Parameter Value" SET uom_code = 9201 WHERE parameter_code = 8805 AND uom_code = 9001;
-- Value 44°87′ is illegal for DMS units (EPSG:9110). A value equivalent to the erroneous DMS value is 45°27′.
-UPDATE epsg_coordoperationparamvalue SET parameter_value = 45.27 WHERE parameter_value = 44.87 AND uom_code = 9110;
+UPDATE "Coordinate_Operation Parameter Value" SET parameter_value = 45.27 WHERE parameter_value = 44.87 AND uom_code = 9110;
@@ -29,77 +29,84 @@ UPDATE epsg_coordoperationparamvalue SET parameter_value = 45.27 WHERE parameter
-- Indexes for queries used by EPSGDataAccess.createFoo(epsgcode) methods. --
-- Indexed fields are numeric values used mainly in equality comparisons. --
-----------------------------------------------------------------------------
-CREATE INDEX ix_alias_object_code ON epsg_alias (object_code);
-CREATE INDEX ix_crs_datum_code ON epsg_coordinatereferencesystem (datum_code);
-CREATE INDEX ix_crs_projection_code ON epsg_coordinatereferencesystem (projection_conv_code);
-CREATE INDEX ix_coordinate_axis_code ON epsg_coordinateaxis (coord_axis_code);
-CREATE INDEX ix_coordinate_axis_sys_code ON epsg_coordinateaxis (coord_sys_code);
-CREATE INDEX ix_coordinate_operation_crs ON epsg_coordoperation (source_crs_code, target_crs_code);
-CREATE INDEX ix_coordinate_operation_method_code ON epsg_coordoperation (coord_op_method_code);
-CREATE INDEX ix_parameter_usage_method_code ON epsg_coordoperationparamusage (coord_op_method_code);
-CREATE INDEX ix_parameter_values ON epsg_coordoperationparamvalue (coord_op_code, coord_op_method_code);
-CREATE INDEX ix_parameter_value_code ON epsg_coordoperationparamvalue (parameter_code);
-CREATE INDEX ix_path_concat_operation_code ON epsg_coordoperationpath (concat_operation_code);
-CREATE INDEX ix_supersession_object_code ON epsg_supersession (object_code);
+CREATE INDEX ix_alias_object_code ON "Alias" (object_code);
+CREATE INDEX ix_crs_datum_code ON "Coordinate Reference System" (datum_code);
+CREATE INDEX ix_crs_projection_code ON "Coordinate Reference System" (projection_conv_code);
+CREATE INDEX ix_coordinate_axis_code ON "Coordinate Axis" (coord_axis_code);
+CREATE INDEX ix_coordinate_axis_sys_code ON "Coordinate Axis" (coord_sys_code);
+CREATE INDEX ix_coordinate_operation_crs ON "Coordinate_Operation" (source_crs_code, target_crs_code);
+CREATE INDEX ix_coordinate_operation_method_code ON "Coordinate_Operation" (coord_op_method_code);
+CREATE INDEX ix_parameter_usage_method_code ON "Coordinate_Operation Parameter Usage" (coord_op_method_code);
+CREATE INDEX ix_parameter_values ON "Coordinate_Operation Parameter Value" (coord_op_code, coord_op_method_code);
+CREATE INDEX ix_parameter_value_code ON "Coordinate_Operation Parameter Value" (parameter_code);
+CREATE INDEX ix_path_concat_operation_code ON "Coordinate_Operation Path" (concat_operation_code);
+CREATE INDEX ix_supersession_object_code ON "Supersession" (object_code);
-----------------------------------------------------------------------------
-- Indexes for queries used by EPSGDataAccess.createFoo(epsgcode) methods. --
-- Indexed fields are numeric values used in ORDER BY clauses. --
-----------------------------------------------------------------------------
-CREATE INDEX ix_coordinate_axis_order ON epsg_coordinateaxis (coord_axis_order);
-CREATE INDEX ix_coordinate_operation_accuracy ON epsg_coordoperation (coord_op_accuracy);
-CREATE INDEX ix_parameter_order ON epsg_coordoperationparamusage (sort_order);
-CREATE INDEX ix_path_concat_operation_step ON epsg_coordoperationpath (op_path_step);
-CREATE INDEX ix_supersession_object_year ON epsg_supersession (supersession_year);
-CREATE INDEX ix_version_history_date ON epsg_versionhistory (version_date);
+CREATE INDEX ix_coordinate_axis_order ON "Coordinate Axis" (coord_axis_order);
+CREATE INDEX ix_coordinate_operation_accuracy ON "Coordinate_Operation" (coord_op_accuracy);
+CREATE INDEX ix_parameter_order ON "Coordinate_Operation Parameter Usage" (sort_order);
+CREATE INDEX ix_path_concat_operation_step ON "Coordinate_Operation Path" (op_path_step);
+CREATE INDEX ix_datum_member_order ON "Datum Ensemble Member" (datum_sequence);
+CREATE INDEX ix_supersession_object_year ON "Supersession" (supersession_year);
+CREATE INDEX ix_version_history_date ON "Version History" (version_date);
-----------------------------------------------------------------------------
-- Indexes on the object names for finding an EPSG code from a name. --
-----------------------------------------------------------------------------
-CREATE INDEX ix_name_crs ON epsg_coordinatereferencesystem (coord_ref_sys_name);
-CREATE INDEX ix_name_cs ON epsg_coordinatesystem (coord_sys_name);
-CREATE INDEX ix_name_axis ON epsg_coordinateaxisname (coord_axis_name);
-CREATE INDEX ix_name_datum ON epsg_datum (datum_name);
-CREATE INDEX ix_name_ellipsoid ON epsg_ellipsoid (ellipsoid_name);
-CREATE INDEX ix_name_prime_meridian ON epsg_primemeridian (prime_meridian_name);
-CREATE INDEX ix_name_coord_op ON epsg_coordoperation (coord_op_name);
-CREATE INDEX ix_name_method ON epsg_coordoperationmethod (coord_op_method_name);
-CREATE INDEX ix_name_parameter ON epsg_coordoperationparam (parameter_name);
-CREATE INDEX ix_name_unit ON epsg_unitofmeasure (unit_of_meas_name);
-CREATE INDEX ix_alias ON epsg_alias (object_table_name, alias);
+CREATE INDEX ix_name_crs ON "Coordinate Reference System" (coord_ref_sys_name);
+CREATE INDEX ix_name_cs ON "Coordinate System" (coord_sys_name);
+CREATE INDEX ix_name_axis ON "Coordinate Axis Name" (coord_axis_name);
+CREATE INDEX ix_name_datum ON "Datum" (datum_name);
+CREATE INDEX ix_name_ellipsoid ON "Ellipsoid" (ellipsoid_name);
+CREATE INDEX ix_name_prime_meridian ON "Prime Meridian" (prime_meridian_name);
+CREATE INDEX ix_name_coord_op ON "Coordinate_Operation" (coord_op_name);
+CREATE INDEX ix_name_method ON "Coordinate_Operation Method" (coord_op_method_name);
+CREATE INDEX ix_name_parameter ON "Coordinate_Operation Parameter" (parameter_name);
+CREATE INDEX ix_name_unit ON "Unit of Measure" (unit_of_meas_name);
+CREATE INDEX ix_alias ON "Alias" (object_table_name, alias);
-----------------------------------------------------------------------------
-- Indexes used by EPSGDataAccess.Finder for reverse operation. --
-----------------------------------------------------------------------------
-CREATE INDEX ix_major_ellipsoid ON epsg_ellipsoid (semi_major_axis);
-CREATE INDEX ix_geogcrs_crs ON epsg_coordinatereferencesystem (base_crs_code);
-CREATE INDEX ix_ellipsoid_datum ON epsg_datum (ellipsoid_code);
+CREATE INDEX ix_major_ellipsoid ON "Ellipsoid" (semi_major_axis);
+CREATE INDEX ix_geogcrs_crs ON "Coordinate Reference System" (base_crs_code);
+CREATE INDEX ix_ellipsoid_datum ON "Datum" (ellipsoid_code);
-----------------------------------------------------------------------------
-- Grants read permission to all tables in the EPSG schema. --
-----------------------------------------------------------------------------
-GRANT SELECT ON TABLE epsg_alias TO PUBLIC;
-GRANT SELECT ON TABLE epsg_area TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordinateaxis TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordinateaxisname TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperation TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperationmethod TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperationparam TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperationparamusage TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperationparamvalue TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordoperationpath TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordinatereferencesystem TO PUBLIC;
-GRANT SELECT ON TABLE epsg_coordinatesystem TO PUBLIC;
-GRANT SELECT ON TABLE epsg_datum TO PUBLIC;
-GRANT SELECT ON TABLE epsg_ellipsoid TO PUBLIC;
-GRANT SELECT ON TABLE epsg_namingsystem TO PUBLIC;
-GRANT SELECT ON TABLE epsg_primemeridian TO PUBLIC;
-GRANT SELECT ON TABLE epsg_supersession TO PUBLIC;
-GRANT SELECT ON TABLE epsg_unitofmeasure TO PUBLIC;
-GRANT SELECT ON TABLE epsg_versionhistory TO PUBLIC;
-GRANT SELECT ON TABLE epsg_change TO PUBLIC;
-GRANT SELECT ON TABLE epsg_deprecation TO PUBLIC;
+GRANT SELECT ON TABLE "Alias" TO PUBLIC;
+GRANT SELECT ON TABLE "Conventional RS" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate Axis" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate Axis Name" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate Reference System" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate System" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation Method" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation Parameter" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation Parameter Usage" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation Parameter Value" TO PUBLIC;
+GRANT SELECT ON TABLE "Coordinate_Operation Path" TO PUBLIC;
+GRANT SELECT ON TABLE "Datum" TO PUBLIC;
+GRANT SELECT ON TABLE "Datum Ensemble" TO PUBLIC;
+GRANT SELECT ON TABLE "Datum Ensemble Member" TO PUBLIC;
+GRANT SELECT ON TABLE "Datum Realization Method" TO PUBLIC;
+GRANT SELECT ON TABLE "Defining Operation" TO PUBLIC;
+GRANT SELECT ON TABLE "Deprecation" TO PUBLIC;
+GRANT SELECT ON TABLE "Ellipsoid" TO PUBLIC;
+GRANT SELECT ON TABLE "Extent" TO PUBLIC;
+GRANT SELECT ON TABLE "Naming System" TO PUBLIC;
+GRANT SELECT ON TABLE "Prime Meridian" TO PUBLIC;
+GRANT SELECT ON TABLE "Scope" TO PUBLIC;
+GRANT SELECT ON TABLE "Supersession" TO PUBLIC;
+GRANT SELECT ON TABLE "Unit of Measure" TO PUBLIC;
+GRANT SELECT ON TABLE "Usage" TO PUBLIC;
+GRANT SELECT ON TABLE "Version History" TO PUBLIC;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Prepare.sql b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Prepare.sql
index b84c8ad6d4..39e59a71d5 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Prepare.sql
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Prepare.sql
@@ -10,31 +10,31 @@
-- If enumerated values are not supported by the database, Apache SIS will automatically replace their usage
-- by the VARCHAR type.
--
-CREATE TYPE epsg_datum_kind AS ENUM ('geodetic', 'vertical', 'temporal', 'engineering', 'dynamic geodetic', 'ensemble');
-CREATE TYPE epsg_crs_kind AS ENUM ('geocentric', 'geographic 2D', 'geographic 3D', 'projected', 'vertical', 'temporal', 'compound', 'engineering', 'derived');
-CREATE TYPE epsg_cs_kind AS ENUM ('ellipsoidal', 'spherical', 'Cartesian', 'vertical', 'gravity-related', 'time', 'linear', 'polar', 'cylindrical', 'affine', 'ordinal');
-CREATE TYPE epsg_supersession_type AS ENUM ('Supersession');
-CREATE TYPE epsg_table_name AS ENUM
+CREATE TYPE "Datum Kind" AS ENUM ('geodetic', 'vertical', 'temporal', 'engineering', 'dynamic geodetic', 'ensemble');
+CREATE TYPE "CRS Kind" AS ENUM ('geocentric', 'geographic 2D', 'geographic 3D', 'projected', 'vertical', 'temporal', 'compound', 'engineering', 'derived');
+CREATE TYPE "CS Kind" AS ENUM ('ellipsoidal', 'spherical', 'Cartesian', 'vertical', 'gravity-related', 'time', 'linear', 'polar', 'cylindrical', 'affine', 'ordinal');
+CREATE TYPE "Supersession Type" AS ENUM ('Supersession');
+CREATE TYPE "Table Name" AS ENUM
('Alias',
'Area', -- Deprecated (removed in EPSG 10).
- 'AxisName', -- Deprecated (removed in EPSG 10).
'Change',
- 'ConventionalRS',
+ 'Conventional RS',
'Coordinate Axis',
'Coordinate Axis Name',
'Coordinate_Operation',
'Coordinate_Operation Method',
'Coordinate_Operation Parameter',
+ 'Coordinate_Operation Parameter Type', -- Only in online registry.
'Coordinate_Operation Parameter Usage',
'Coordinate_Operation Parameter Value',
'Coordinate_Operation Path',
'Coordinate Reference System',
'Coordinate System',
'Datum',
- 'DatumEnsemble',
- 'DatumEnsembleMember',
- 'DatumRealizationMethod',
- 'DefiningOperation',
+ 'Datum Ensemble',
+ 'Datum Ensemble Member',
+ 'Datum Realization Method',
+ 'Defining Operation',
'Deprecation',
'Ellipsoid',
'Extent',
@@ -49,8 +49,8 @@ CREATE TYPE epsg_table_name AS ENUM
--
-- Those casts allow to use enumerated values as if they were VARCHAR elements.
--
-CREATE CAST (VARCHAR AS epsg_datum_kind) WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS epsg_crs_kind) WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS epsg_cs_kind) WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS epsg_supersession_type) WITH INOUT AS ASSIGNMENT;
-CREATE CAST (VARCHAR AS epsg_table_name) WITH INOUT AS ASSIGNMENT;
+CREATE CAST (VARCHAR AS "Datum Kind") WITH INOUT AS ASSIGNMENT;
+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;
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
index 9e78b8cd3d..096efdad13 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/InstallationScriptProvider.java
@@ -41,6 +41,7 @@ import org.apache.sis.system.Fallback;
import org.apache.sis.system.DataDirectory;
import org.apache.sis.util.privy.CollectionsExt;
import org.apache.sis.util.privy.Constants;
+import org.apache.sis.pending.jdk.JDK22;
/**
@@ -107,7 +108,7 @@ public abstract class InstallationScriptProvider extends InstallationResources {
* </tr><tr>
* <td>{@code EPSG}</td>
* <td class="sep"><code>
- * {{@linkplain #PREPARE}, "EPSG_Tables.sql", "EPSG_Data.sql", "EPSG_FKeys.sql", {@linkplain #FINISH}}
+ * {{@linkplain #PREPARE}, "Tables.sql", "Data.sql", "FKeys.sql", {@linkplain #FINISH}}
* </code></td>
* </tr>
* </table>
@@ -273,14 +274,14 @@ public abstract class InstallationScriptProvider extends InstallationResources {
/**
- * The default implementation which uses the scripts in the {@code $SIS_DATA/Databases/ExternalSources}
+ * The default implementation which uses the scripts in the {@code $SIS_DATA/Databases/ExternalSources/EPSG}
* directory, if present. This class expects the files to have those exact names where {@code *} stands
* for any characters provided that there is no ambiguity:
*
* <ul>
- * <li>{@code EPSG_*Tables.sql}</li>
- * <li>{@code EPSG_*Data.sql}</li>
- * <li>{@code EPSG_*FKeys.sql}</li>
+ * <li>{@code *Tables*.sql}</li>
+ * <li>{@code *Data*.sql}</li>
+ * <li>{@code *FKeys*.sql}</li>
* </ul>
*
* @author Martin Desruisseaux (Geomatys)
@@ -309,8 +310,8 @@ public abstract class InstallationScriptProvider extends InstallationResources {
final String[] found = new String[resources.length - (FIRST_FILE + 1)]; // +1 is for omitting `FINISH`.
@SuppressWarnings("LocalVariableHidesMemberVariable")
Path directory = DataDirectory.DATABASES.getDirectory();
- if (directory != null && Files.isDirectory(directory = directory.resolve("ExternalSources"))) {
- try (DirectoryStream<Path> content = Files.newDirectoryStream(directory, "EPSG_*.sql")) {
+ if (directory != null && Files.isDirectory(directory = JDK22.resolve(directory, "ExternalSources", Constants.EPSG))) {
+ try (DirectoryStream<Path> content = Files.newDirectoryStream(directory, "*.sql")) {
for (final Path path : content) {
final String name = path.getFileName().toString();
for (int i=0; i<found.length; i++) {
@@ -319,6 +320,7 @@ public abstract class InstallationScriptProvider extends InstallationResources {
if (found[i] == null) {
found[i] = name;
} else {
+ directory = null;
log(Errors.forLocale(locale).createLogRecord(Level.WARNING,
Errors.Keys.DuplicatedFileReference_1, part));
}
@@ -330,27 +332,18 @@ public abstract class InstallationScriptProvider extends InstallationResources {
directory = null; // Does not exist or is not a directory.
}
this.directory = directory;
- /*
- * Store the actual file names including the target database. Example:
- *
- * - PostgreSQL_Table_Script.sql
- * - PostgreSQL_Data_Script.sql
- * - PostgreSQL_FKey_Script.sql
- *
- * If a file was not found, use the "EPSG_Foo_Script.sql" name pattern for giving some hints
- * to users about which file was expected. This filename will appear in the exception message.
- */
for (int i=0; i<found.length; i++) {
String file = found[i];
if (file == null) {
- file = "EPSG_" + resources[FIRST_FILE + i] + "_Script.sql";
+ // File not found. Create a default name which will appear in the exception to be thrown.
+ file = resources[FIRST_FILE + i] + ".sql";
}
resources[FIRST_FILE + i] = file;
}
}
/**
- * Returns {@code "EPSG"} if the scripts exist in the {@code ExternalSources} subdirectory,
+ * Returns {@code "EPSG"} if the scripts exist in the {@code ExternalSources/EPSG} subdirectory,
* or an empty set otherwise.
*
* @return {@code "EPSG"} if the SQL scripts for installing the EPSG dataset are available,
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 ca2979436d..ea11bcfd25 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
@@ -55,58 +55,68 @@ import org.apache.sis.referencing.internal.Resources;
* <li>{@code SELECT * FROM epsg_CoordinateReferenceSystem} (same as above with {@code "epsg_"} prefix added)</li>
* </ul>
*
- * Above possibilities differ in the letter cases, spaces and {@code "epsg_"} prefix (non-exhaustive).
- * Those differences exist between the <abbr>EPSG</abbr> databases distributed in MS-Access format or
- * as <abbr>SQL</abbr> scripts. More differences may exist for handling the differences between version
- * 9 and 10 of the <abbr>EPSG</abbr> database schema.
+ * Some differences are the lower or camel case, the spaces between words and the {@code "epsg_"} prefix.
+ * Those differences exist because the <abbr>EPSG</abbr> database is distributed in two family of formats:
+ * as an MS-Access file and as <abbr>SQL</abbr> scripts, and those two families use different table names.
+ * In addition to format-dependent differences, there is also changes in the database schema between some
+ * versions of the <abbr>EPSG</abbr> dataset.
*
- * <p>Apache <abbr>SIS</abbr> generally uses the naming convention found in the MS-Access database,
- * because it provides more readable <abbr>SQL</abbr> statements. <abbr>SIS</abbr> also assumes an
- * <abbr>EPSG</abbr> database schema version 10 or latter. If {@link EPSGFactory} is connected to
- * a database which uses a different table naming convention, or to <abbr>EPSG</abbr> version 9,
- * then this {@code SQLTranslator} class will translate the <abbr>SQL</abbr> statements on-the-fly.
- * The following table gives the mapping between the two naming conventions:</p>
+ * <h2>Table naming convention</h2>
+ * For readability reasons,
+ * Apache <abbr>SIS</abbr> generally uses the naming convention found in the MS-Access database,
+ * except for the cases shown in the "Name in Apache <abbr>SIS</abbr>" column of the table below.
+ * The <abbr>SQL</abbr> statements given to the {@link #apply(String)} method use the latter.
+ * The following table gives the mapping between the naming conventions:
*
* <table class="sis">
- * <caption>Table and column names</caption>
- * <tr><th>Element</th> <th>Name in MS-Access database</th> <th>Name in <abbr>SQL</abbr> scripts</th></tr>
- * <tr><td>Table</td> <td>{@code Alias}</td> <td>{@code epsg_alias}</td></tr>
- * <tr><td>Legacy table</td> <td>{@code Area}</td> <td>{@code epsg_area}</td></tr>
- * <tr><td>Table</td> <td>{@code Change}</td> <td>{@code epsg_change}</td></tr>
- * <tr><td>Table</td> <td>{@code ConventionalRS}</td> <td>{@code epsg_conventionalrs}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate Axis}</td> <td>{@code epsg_coordinateaxis}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate Axis Name}</td> <td>{@code epsg_coordinateaxisname}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation}</td> <td>{@code epsg_coordoperation}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation Method}</td> <td>{@code epsg_coordoperationmethod}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation Parameter}</td> <td>{@code epsg_coordoperationparam}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation Parameter Usage}</td> <td>{@code epsg_coordoperationparamusage}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation Parameter Value}</td> <td>{@code epsg_coordoperationparamvalue}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate_Operation Path}</td> <td>{@code epsg_coordoperationpath}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate Reference System}</td> <td>{@code epsg_coordinatereferencesystem}</td></tr>
- * <tr><td>Table</td> <td>{@code Coordinate System}</td> <td>{@code epsg_coordinatesystem}</td></tr>
- * <tr><td>Table</td> <td>{@code Datum}</td> <td>{@code epsg_datum}</td></tr>
- * <tr><td>Table</td> <td>{@code DatumEnsemble}</td> <td>{@code epsg_datumensemble}</td></tr>
- * <tr><td>Table</td> <td>{@code DatumEnsembleMember}</td> <td>{@code epsg_datumensemblemember}</td></tr>
- * <tr><td>Table</td> <td>{@code DatumRealizationMethod}</td> <td>{@code epsg_datumrealizationmethod}</td></tr>
- * <tr><td>Table</td> <td>{@code DefiningOperation}</td> <td>{@code epsg_definingoperation}</td></tr>
- * <tr><td>Table</td> <td>{@code Deprecation}</td> <td>{@code epsg_deprecation}</td></tr>
- * <tr><td>Table</td> <td>{@code Ellipsoid}</td> <td>{@code epsg_ellipsoid}</td></tr>
- * <tr><td>Table</td> <td>{@code Extent}</td> <td>{@code epsg_extent}</td></tr>
- * <tr><td>Table</td> <td>{@code Naming System}</td> <td>{@code epsg_namingsystem}</td></tr>
- * <tr><td>Table</td> <td>{@code Prime Meridian}</td> <td>{@code epsg_primemeridian}</td></tr>
- * <tr><td>Table</td> <td>{@code Scope}</td> <td>{@code epsg_scope}</td></tr>
- * <tr><td>Table</td> <td>{@code Supersession}</td> <td>{@code epsg_supersession}</td></tr>
- * <tr><td>Table</td> <td>{@code Unit of Measure}</td> <td>{@code epsg_unitofmeasure}</td></tr>
- * <tr><td>Table</td> <td>{@code Version History}</td> <td>{@code epsg_versionhistory}</td></tr>
- * <tr><td>Table</td> <td>{@code Usage}</td> <td>{@code epsg_usage}</td></tr>
- * <tr><td>Column</td> <td>{@code ORDER}</td> <td>{@code coord_axis_order}</td></tr>
+ * <caption>Mapping of table names</caption>
+ * <tr><th>Name in <abbr>SQL</abbr> scripts</th> <th>Name in MS-Access database</th> <th>Name in Apache <abbr>SIS</abbr></th></tr>
+ * <tr><td>{@code epsg_alias}</td> <td>{@code Alias}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_area}</td> <td>{@code Area}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_change}</td> <td>{@code Change}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_conventionalrs}</td> <td>{@code ConventionalRS}</td> <td>{@code Conventional RS}</td></tr>
+ * <tr><td>{@code epsg_coordinateaxis}</td> <td>{@code Coordinate Axis}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordinateaxisname}</td> <td>{@code Coordinate Axis Name}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperation}</td> <td>{@code Coordinate_Operation}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperationmethod}</td> <td>{@code Coordinate_Operation Method}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperationparam}</td> <td>{@code Coordinate_Operation Parameter}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperationparamusage}</td> <td>{@code Coordinate_Operation Parameter Usage}</td><td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperationparamvalue}</td> <td>{@code Coordinate_Operation Parameter Value}</td><td>″</td></tr>
+ * <tr><td>{@code epsg_coordoperationpath}</td> <td>{@code Coordinate_Operation Path}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordinatereferencesystem}</td><td>{@code Coordinate Reference System}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_coordinatesystem}</td> <td>{@code Coordinate System}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_datum}</td> <td>{@code Datum}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_datumensemble}</td> <td>{@code DatumEnsemble}</td> <td>{@code Datum Ensemble}</td></tr>
+ * <tr><td>{@code epsg_datumensemblemember}</td> <td>{@code DatumEnsembleMember}</td> <td>{@code Datum Ensemble Member}</td></tr>
+ * <tr><td>{@code epsg_datumrealizationmethod}</td> <td>{@code DatumRealizationMethod}</td> <td>{@code Datum Realization Method}</td></tr>
+ * <tr><td>{@code epsg_definingoperation}</td> <td>{@code DefiningOperation}</td> <td>{@code Defining Operation}</td></tr>
+ * <tr><td>{@code epsg_deprecation}</td> <td>{@code Deprecation}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_ellipsoid}</td> <td>{@code Ellipsoid}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_extent}</td> <td>{@code Extent}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_namingsystem}</td> <td>{@code Naming System}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_primemeridian}</td> <td>{@code Prime Meridian}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_scope}</td> <td>{@code Scope}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_supersession}</td> <td>{@code Supersession}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_unitofmeasure}</td> <td>{@code Unit of Measure}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_versionhistory}</td> <td>{@code Version History}</td> <td>″</td></tr>
+ * <tr><td>{@code epsg_usage}</td> <td>{@code Usage}</td> <td>″</td></tr>
* </table>
*
- * Apache <abbr>SIS</abbr> automatically detects which convention is used, regardless the database engine.
+ * Columns have the same name in all formats, with only one exception:
+ * for avoiding confusion with the <abbr>SQL</abbr> keyword of the same name,
+ * the {@code ORDER} column in MS-Access has been renamed {@code coord_axis_order} in <abbr>SQL</abbr> scripts.
+ * Apache <abbr>SIS</abbr> uses the latter.
+ *
+ * <p>Apache <abbr>SIS</abbr> automatically detects which name convention is used, regardless the database engine.
* For example, it is legal to use the mixed-case variant in a PostgreSQL database
* even if <abbr>EPSG</abbr> distributes the PostgreSQL scripts in lower-case.
* The {@code "epsg_"} prefix is redundant with database schema and can be omitted.
- * {@code SQLTranslator} automatically detects which database schema contains the <abbr>EPSG</abbr> tables.
+ * {@code SQLTranslator} automatically detects which database schema contains the <abbr>EPSG</abbr> tables.</p>
+ *
+ * <h2>Versions of <abbr>EPSG</abbr> schema</h2>
+ * Apache <abbr>SIS</abbr> assumes an <abbr>EPSG</abbr> database schema version 10 or latter.
+ * If {@link EPSGFactory} is connected to the <abbr>EPSG</abbr> version 9 database,
+ * then this {@code SQLTranslator} class will modify the <abbr>SQL</abbr> statements on-the-fly.
*
* <h2>Thread safety</h2>
* All {@code SQLTranslator} instances given to the {@link EPSGFactory} constructor
@@ -220,6 +230,8 @@ public class SQLTranslator implements UnaryOperator<String> {
* Non-null if the {@value #ENUMERATION_COLUMN} column in {@code "Alias"} table uses enumeration instead
* than character varying. In such case, this field contains the enumeration type. If {@code null}, then
* then column type is {@code VARCHAR} and the cast can be omitted.
+ *
+ * @see #useEnumerations()
*/
private String tableNameEnum;
@@ -232,7 +244,7 @@ public class SQLTranslator implements UnaryOperator<String> {
private boolean useBoolean;
/**
- * Whether the sentinel table "Coordinate_Operation" or a variant has been found.
+ * Whether the sentinel table {@code "Coordinate_Operation"} or a variant has been found.
* If {@code false}, then {@link EPSGInstaller} needs to be run.
* Note that the {@linkplain #schema} may be {@code null}.
*
@@ -389,20 +401,30 @@ check: for (;;) {
break;
}
case 1: {
+ table = "Coordinate_Operation";
+ missingColumns.put("AREA_OF_USE_CODE", "INTEGER"); // Defined in version 9, deprecated in 10+.
+ missingColumns.put("COORD_OP_SCOPE", "CHAR(1)"); // Idem.
+ break;
+ }
+ case 2: {
table = "Coordinate Reference System";
mayRenameColumns.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE"); // EPSG version 10 → version 9.
+ missingColumns .put("AREA_OF_USE_CODE", "INTEGER"); // Defined in version 9, deprecated in 10+.
+ missingColumns .put("CRS_SCOPE", "CHAR(1)"); // Idem.
break;
}
- case 2: {
+ case 3: {
table = "Datum";
mayRenameColumns.put("PUBLICATION_DATE", "REALIZATION_EPOCH"); // EPSG version 10 → version 9.
missingColumns .put("ANCHOR_EPOCH", "DOUBLE PRECISION");
missingColumns .put("FRAME_REFERENCE_EPOCH", "DOUBLE PRECISION");
missingColumns .put("REALIZATION_METHOD_CODE", "INTEGER");
missingColumns .put("CONVENTIONAL_RS_CODE", "INTEGER");
+ missingColumns .put("AREA_OF_USE_CODE", "INTEGER"); // Defined in version 9, deprecated in 10+.
+ missingColumns .put("DATUM_SCOPE", "CHAR(1)"); // Idem.
break;
}
- case 3: {
+ case 4: {
table = "Extent"; // "Area" in 9, "Extent" in 10.
mayRenameColumns.put("EXTENT_CODE", "AREA_CODE"); // EPSG version 10 → version 9.
mayRenameColumns.put("EXTENT_NAME", "AREA_NAME");
@@ -414,23 +436,23 @@ check: for (;;) {
missingColumns .put("VERTICAL_EXTENT_MIN", "DOUBLE PRECISION");
missingColumns .put("VERTICAL_EXTENT_MAX", "DOUBLE PRECISION");
missingColumns .put("VERTICAL_EXTENT_CRS_CODE", "INTEGER");
- missingColumns .put("TEMPORAL_EXTENT_BEGIN", "VARCHAR(254)");
- missingColumns .put("TEMPORAL_EXTENT_END", "VARCHAR(254)");
+ missingColumns .put("TEMPORAL_EXTENT_BEGIN", "CHAR(1)");
+ missingColumns .put("TEMPORAL_EXTENT_END", "CHAR(1)");
mayReuse = true;
break;
}
- case 4: {
+ case 5: {
if (mayRenameColumns.isEmpty()) continue; // "Extent" table has been found.
isArea = true;
table = "Area";
break;
}
- case 5: {
+ case 6: {
isUsage = true;
table = "Usage";
break;
}
- case 6: {
+ case 7: {
if (isUsageTableFound) break check; // The check of `ENUMERATION_COLUMN` is already done.
table = "Alias"; // For checking the type of the `ENUMERATION_COLUMN`.
break;
@@ -560,6 +582,15 @@ check: for (;;) {
return useBoolean;
}
+ /**
+ * Returns {@code true} if the database uses enumeration values where applicable.
+ * This method use the {@value #ENUMERATION_COLUMN} column as a sentinel value for
+ * detecting whether enumerations are used for the whole <abbr>EPSG</abbr> database.
+ */
+ final boolean useEnumerations() {
+ return tableNameEnum != null;
+ }
+
/**
* Converts a mixed-case table name to the convention used in the database.
* The names of the tables for the two conventions are listed in a table in the Javadoc of this class.
@@ -664,7 +695,7 @@ check: for (;;) {
int w = buffer.lastIndexOf(ENUMERATION_COLUMN + "=?");
if (w >= 0) {
w += ENUMERATION_COLUMN.length() + 1;
- buffer.replace(w, w+1, "CAST(? AS " + tableNameEnum + ')');
+ buffer.replace(w, w+1, "CAST(? AS \"" + tableNameEnum + "\")");
}
}
return buffer.toString();
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
index b2afeba4e5..55c0a8653b 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/TableInfo.java
@@ -69,8 +69,8 @@ final class TableInfo {
* <h4>Ambiguity</h4>
* As of ISO 19111:2019, we have no standard way to identify the geocentric case from a {@link Class} argument
* because the standard does not provide the {@code GeocentricCRS} interface. This implementation fallbacks on
- * the SIS-specific geocentric CRS class, with a {@link #where(IdentifiedObject, StringBuilder)} method which
- * will substitute implementation-neutral objects by the Apache SIS class.
+ * the SIS-specific geocentric CRS class, with a {@link #where(EPSGDataAccess, IdentifiedObject, StringBuilder)}
+ * method which will substitute implementation-neutral objects by the Apache SIS class.
*/
static final TableInfo[] EPSG = {
CRS = new TableInfo(CoordinateReferenceSystem.class,
@@ -187,17 +187,10 @@ final class TableInfo {
final String nameColumn;
/**
- * Column type for the type (usually with the {@code "_TYPE"} suffix), or {@code null}.
- * {@link EPSGDataAccess} and {@link AuthorityCodes} assume that values in this column
- * are not longer than the maximal length specified in {@value #ENUM_REPLACEMENT}.
+ * Column name for the type (usually with the {@code "_TYPE"} suffix), or {@code null}.
*/
private final String typeColumn;
- /**
- * The SQL type to use as a replacement for enumerated values on databases that do not support enumerations.
- */
- static final String ENUM_REPLACEMENT = "VARCHAR(80)";
-
/**
* Sub-interfaces of {@link #type} to handle, or {@code null} if none.
*/
@@ -257,7 +250,7 @@ final class TableInfo {
/**
* Returns the class of objects created from the given table. The given table name should be one of
- * the values enumerated in the {@code epsg_table_name} types of the {@code EPSG_Prepare.sql} file.
+ * the values enumerated in the {@code "Table Name"} types of the {@code EPSG_Prepare.sql} file.
* The name may be prefixed by {@code "epsg_"} and may contain abbreviations of the full name.
* For example, {@code "epsg_coordoperation"} is considered as a match for {@code "Coordinate_Operation"}.
*
@@ -280,13 +273,14 @@ final class TableInfo {
/**
* Appends a {@code WHERE} clause together with a condition for searching the specified object.
- * This method delegates to {@link #where(Class, StringBuilder)} with the type of the given object,
- * except that some object properties may be inspected for resolving ambiguities.
+ * This method delegates to {@link #where(EPSGDataAccess, Class, StringBuilder)} with the type
+ * of the given object, except that some object properties may be inspected for resolving ambiguities.
*
- * @param object the object to search in the database.
- * @param buffer where to append the {@code WHERE} clause.
+ * @param factory the factory which is writing a <abbr>SQL</abbr> statement.
+ * @param object the object to search in the database.
+ * @param buffer where to append the {@code WHERE} clause.
*/
- final void where(final IdentifiedObject object, final StringBuilder buffer) {
+ final void where(final EPSGDataAccess factory, final IdentifiedObject object, final StringBuilder buffer) {
Class<?> userType = object.getClass();
if (object instanceof GeodeticCRS) {
final CoordinateSystem cs = ((GeodeticCRS) object).getCoordinateSystem();
@@ -296,7 +290,7 @@ final class TableInfo {
userType = DefaultGeocentricCRS.class;
}
}
- where(userType, buffer);
+ where(factory, userType, buffer);
}
/**
@@ -310,18 +304,20 @@ final class TableInfo {
*
* The caller shall add at least one condition after this method call.
*
+ * @param factory the factory which is writing a <abbr>SQL</abbr> statement.
* @param userType the type specified by the user.
* @param buffer where to append the {@code WHERE} clause.
* @return the subtype, or {@link #type} if no subtype was found.
*/
- final Class<?> where(final Class<?> userType, final StringBuilder buffer) {
+ final Class<?> where(final EPSGDataAccess factory, final Class<?> userType, final StringBuilder buffer) {
buffer.append(" WHERE ");
if (typeColumn != null) {
for (int i=0; i<subTypes.length; i++) {
final Class<?> candidate = subTypes[i];
if (candidate.isAssignableFrom(userType)) {
- if (ENUM_REPLACEMENT != null) {
- buffer.append("CAST(").append(typeColumn).append(" AS ").append(ENUM_REPLACEMENT).append(')');
+ if (factory.translator.useEnumerations()) {
+ buffer.append("CAST(").append(typeColumn).append(" AS ")
+ .append(EPSGInstaller.ENUM_REPLACEMENT).append(')');
} else {
buffer.append(typeColumn);
}
diff --git a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
index 27d6ecbb75..463ab03524 100644
--- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
+++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/operation/AbstractCoordinateOperation.java
@@ -897,7 +897,7 @@ check: for (int isTarget=0; ; isTarget++) { // 0 == source check; 1
if (deepEquals(crs1, crs2, mode)) {
MathTransform tr1 = this.getMathTransform();
MathTransform tr2 = that.getMathTransform();
- if (mode == ComparisonMode.ALLOW_VARIANT) try {
+ if (mode.allowsVariant()) try {
final MathTransform before = MathTransforms.linear(
CoordinateSystems.swapAndScaleAxes(crs1.getCoordinateSystem(),
crs2.getCoordinateSystem()));
diff --git a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
index 6926b7096b..893adf3297 100644
--- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
+++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/factory/sql/EPSGInstallerTest.java
@@ -19,7 +19,6 @@ package org.apache.sis.referencing.factory.sql;
import java.util.Set;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.regex.Pattern;
import java.io.IOException;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -53,9 +52,10 @@ import org.apache.sis.metadata.sql.TestDatabase;
* so we want to opportunistically verify the result immediately after database creation
* by using the {@code EPSGFactory} for creating a few CRS.
*
- * <p>This test requires that {@code $SIS_DATA/Databases/ExternalSources} directory contains
- * the {@code EPSG_Tables.sql}, {@code EPSG_Data.sql} and {@code EPSG_FKeys.sql} files.
- * Those files can be <a href="https://epsg.org/">downloaded from the source</a> or from
+ * <p>This test requires that the {@code $SIS_DATA/Databases/ExternalSources/EPSG} directory
+ * contains the {@code Tables.sql}, {@code Data.sql} and {@code FKeys.sql} files.
+ * An easy setup is to set the above-cited {@code EPSG} directory as a symbolic link
+ * to the {@code EPSG} directory of the
* <a href="https://sis.apache.org/source.html#non-free">SIS non-free directory</a>.</p>
*
* <p>Every databases created by this test suite exist only in memory.
@@ -71,34 +71,6 @@ public final class EPSGInstallerTest extends TestCaseWithLogs {
super(Loggers.CRS_FACTORY);
}
- /**
- * Tests the {@link EPSGInstaller#REPLACE_STATEMENT} pattern.
- */
- @Test
- public void testReplacePattern() {
- // Statement as in the EPSG scripts since EPSG version 7.06.
- assertTrue(Pattern.matches(EPSGInstaller.REPLACE_STATEMENT,
- "UPDATE epsg_datum\n" +
- "SET datum_name = replace(datum_name, CHR(182), CHR(10))"));
-
- // Statement as in the EPSG scripts prior to EPSG version 7.06.
- assertTrue(Pattern.matches(EPSGInstaller.REPLACE_STATEMENT,
- "UPDATE epsg_datum\n" +
- "SET datum_name = replace(datum_name, CHAR(182), CHAR(10))"));
-
- // Modified statement with mixed-case table name in a schema.
- assertTrue(Pattern.matches(EPSGInstaller.REPLACE_STATEMENT,
- "UPDATE epsg.\"Alias\"\n" +
- "SET object_table_name = replace(object_table_name, CHR(182), CHR(10))"));
-
- // Like above, but the table name contains a space.
- assertTrue(Pattern.matches(EPSGInstaller.REPLACE_STATEMENT,
- "UPDATE epsg.\"Coordinate Axis\"\n" +
- "SET coord_axis_orientation = replace(coord_axis_orientation, CHR(182), CHR(10))"));
-
- loggings.assertNoUnexpectedLog();
- }
-
/**
* Returns the SQL scripts needed for testing the database creation,
* or skip the JUnit test if those scripts are not found.
@@ -109,7 +81,7 @@ public final class EPSGInstallerTest extends TestCaseWithLogs {
private static InstallationScriptProvider getScripts() throws IOException {
final var scripts = new InstallationScriptProvider.Default(null);
assumeTrue(scripts.getAuthorities().contains(Constants.EPSG),
- "EPSG scripts not found in the \"Databases/ExternalSources\" directory.");
+ "Scripts not found in the \"Databases/ExternalSources/EPSG\" directory.");
return scripts;
}
@@ -257,7 +229,7 @@ public final class EPSGInstallerTest extends TestCaseWithLogs {
}
/**
- * Verifies some parameter values in the database. We perform this check on a parameter which are known
+ * Verifies some parameter values in the database. We perform this check on parameters which are known
* to have small values, in order to make sure that the values have not been truncated to zero.
*/
private static void verifyParameterValues(final DataSource ds) throws SQLException {
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java
index adc7887cf7..e7f2c31c5f 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/pending/jdk/JDK22.java
@@ -17,6 +17,7 @@
package org.apache.sis.pending.jdk;
import java.io.Console;
+import java.nio.file.Path;
/**
@@ -31,6 +32,16 @@ public final class JDK22 {
private JDK22() {
}
+ /**
+ * Placeholder for {@code Path.resolve(String, String...)}.
+ */
+ public static Path resolve(Path path, String... more) {
+ for (String p : more) {
+ path = path.resolve(p);
+ }
+ return path;
+ }
+
/**
* Placeholder for {@code Console.isTerminal()}.
*/
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
index 49860713e3..ed337dad42 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/ComparisonMode.java
@@ -41,7 +41,7 @@ package org.apache.sis.util;
* {@link #IGNORE_METADATA} level but not necessarily at the {@link #STRICT} level.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.5
*
* @see LenientComparable#equals(Object, ComparisonMode)
* @see Utilities#deepEquals(Object, Object, ComparisonMode)
@@ -115,13 +115,15 @@ public enum ComparisonMode {
* A <q>Mercator (variant B)</q> projection with a <i>standard parallel</i> value of 60° produces the
* same results as a <q>Mercator (variant A)</q> projection with a <i>scale factor</i> value of 0.5.
*
+ * @see #isIgnoringMetadata()
* @see org.apache.sis.util.Utilities#equalsIgnoreMetadata(Object, Object)
*/
IGNORE_METADATA,
/**
- * Only the attributes relevant to the object functionality are compared, with some tolerance
- * threshold on numerical values.
+ * Only the attributes relevant to the object functionality are compared, with some tolerance* threshold on numerical values.
+ * The threshold is implementation dependent, but the current <abbr>SIS</abbr> implementation generally aims for a precision
+ * of 1 centimeter in the linear and angular parameter values for a planet of the size of Earth.
*
* <h4>Application to coordinate operations</h4>
* If two {@link org.opengis.referencing.operation.MathTransform} objects are considered equal according this mode,
@@ -131,6 +133,8 @@ public enum ComparisonMode {
* How small is “small” is implementation dependent — the threshold cannot be specified in the current
* implementation, because of the non-linear nature of map projections.
*
+ * @see #isApproximate()
+ *
* @since 1.0
*/
APPROXIMATE,
@@ -138,17 +142,30 @@ public enum ComparisonMode {
/**
* Most but not all attributes relevant to the object functionality are compared.
* This comparison mode is equivalent to {@link #APPROXIMATE}, except that it
- * ignores some attributes that may differ between objects not equal but related.
+ * ignores some aspects that may differ between objects not equal but related.
+ * Below is a list of examples where the objects being compared would be
+ * considered different according the other modes such as {@link #APPROXIMATE},
+ * but are considered equivalent according this {@code ALLOW_VARIANT} mode.
*
- * <p>The main purpose of this method is to verify if two Coordinate Reference Systems (CRS)
- * are approximately equal ignoring axis order.</p>
+ * <ul class="verbose">
+ * <li>Two Coordinate Reference Systems (<abbr>CRS</abbr>) with the same axes but in different order.
+ * <b>Example:</b> two geographic <abbr>CRS</abbr>s with the same attributes but with
+ * (<var>latitude</var>, <var>longitude</var>) axes in one case and
+ * (<var>longitude</var>, <var>latitude</var>) axes in the other case.</li>
+ * <li>Two Coordinate Reference Systems where one is associated to a datum and the other to a datum ensemble,
+ * but the datum and the ensemble share a common authority code or have an
+ * {@link org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName equivalent name}.
+ * <b>Example:</b> {@code EPSG:9:4326} and {@code EPSG:10:4326}, which are both the same (at least conceptually)
+ * geographic <abbr>CRS</abbr> associated to the authority code 4326 in the <abbr>EPSG</abbr> geodetic dataset,
+ * but as defined in version 9 and 10 respectively of the <abbr>EPSG</abbr> database.
+ * They are the <abbr>CRS</abbr> definitions before and after the introduction of datum ensemble in the schema.</li>
+ * <li>Two Reference Frames which share a common authority code or have an
+ * {@link org.apache.sis.referencing.IdentifiedObjects#isHeuristicMatchForName equivalent name},
+ * despite one frame being static and the other frame being dynamic.
+ * <b>Example:</b> the "WGS 1972" reference frame as defined in versions 9 and 10 of <abbr>EPSG</abbr> database.</li>
+ * </ul>
*
- * <h4>Example</h4>
- * Consider two geographic coordinate reference systems with the same attributes except axis order,
- * where one CRS uses (<var>latitude</var>, <var>longitude</var>) axes
- * and the other CRS uses (<var>longitude</var>, <var>latitude</var>) axes.
- * All comparison modes (even {@code APPROXIMATE}) will consider those two CRS as different,
- * except this {@code ALLOW_VARIANT} mode which will consider one CRS to be a variant of the other.
+ * @see #allowsVariant()
*
* @since 0.7
*/
@@ -172,8 +189,8 @@ public enum ComparisonMode {
/**
* Returns {@code true} if this comparison ignores metadata.
- * This method currently returns {@code true} for {@code IGNORE_METADATA}, {@code APPROXIMATE}
- * or {@code DEBUG} only, but this list may be extended in future SIS versions.
+ * This method currently returns {@code true} for {@link #IGNORE_METADATA}, {@link #APPROXIMATE},
+ * {@link #ALLOW_VARIANT} or {@link #DEBUG}, but this list may be extended in future <abbr>SIS</abbr> versions.
*
* @return whether this comparison ignore metadata.
*
@@ -185,8 +202,8 @@ public enum ComparisonMode {
/**
* Returns {@code true} if this comparison uses a tolerance threshold.
- * This method currently returns {@code true} for {@code APPROXIMATE} or {@code DEBUG} only,
- * but this list may be extended in future SIS versions.
+ * This method currently returns {@code true} for {@link #APPROXIMATE}, {@link #ALLOW_VARIANT}
+ * or {@link #DEBUG}, but this list may be extended in future <abbr>SIS</abbr> versions.
*
* @return whether this comparison uses a tolerance threshold.
*
@@ -196,6 +213,19 @@ public enum ComparisonMode {
return ordinal() >= APPROXIMATE.ordinal();
}
+ /**
+ * Returns {@code true} if this comparison allow variants.
+ * This method currently returns {@code true} for {@link #ALLOW_VARIANT}
+ * or {@link #DEBUG}, but this list may be extended in future <abbr>SIS</abbr> versions.
+ *
+ * @return whether this comparison allows variants.
+ *
+ * @since 1.5
+ */
+ public boolean allowsVariant() {
+ return ordinal() >= ALLOW_VARIANT.ordinal();
+ }
+
/**
* If the two given objects are equal according one of the modes enumerated in this class,
* then returns that mode. Otherwise returns {@code null}.
diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/StringBuilders.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/StringBuilders.java
index d9d71431b1..5d38dfecc8 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/StringBuilders.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/StringBuilders.java
@@ -34,7 +34,7 @@ import org.apache.sis.util.resources.Errors;
* the <i>Basic Multilingual Plane</i> (BMP).
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.5
*
* @see CharSequences
*
@@ -56,6 +56,27 @@ public final class StringBuilders extends Static {
private StringBuilders() {
}
+ /**
+ * Removes leading and trailing whitespace characters in a subregion of the specified buffer.
+ * Space characters are identified by the {@link Character#isWhitespace(int)} method.
+ *
+ * @param buffer the buffer where to remove leading and trailing white spaces.
+ * @param lower index of the first character of the subregion where to remove leading spaces.
+ * @param upper index after the last character of the subregion where to remove trailing spaces.
+ * @return number of characters removed.
+ *
+ * @see CharSequences#trimWhitespaces(CharSequence, int, int)
+ *
+ * @since 1.5
+ */
+ public static int trimWhitespaces(final StringBuilder buffer, final int lower, final int upper) {
+ ArgumentChecks.ensureNonNull("buffer", buffer);
+ int i = CharSequences.skipTrailingWhitespaces(buffer, lower, upper);
+ final int length = buffer.length();
+ buffer.delete(i, upper).delete(lower, CharSequences.skipLeadingWhitespaces(buffer, lower, i));
+ return length - buffer.length();
+ }
+
/**
* Replaces every occurrences of the given character in the given buffer.
*
diff --git a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/StringBuildersTest.java b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/StringBuildersTest.java
index 604019c26d..dd6ca8eff9 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/StringBuildersTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/StringBuildersTest.java
@@ -37,12 +37,25 @@ public final class StringBuildersTest extends TestCase {
public StringBuildersTest() {
}
+ /**
+ * Tests the {@link StringBuilders#trimWhitespaces(StringBuilder, int, int)} method.
+ */
+ @Test
+ public void testTrimWhitespaces() {
+ final var buffer = new StringBuilder(" Foo bar ");
+ int expected = 4;
+ do {
+ assertEquals(expected, StringBuilders.trimWhitespaces(buffer, 0, buffer.length()));
+ assertEquals("Foo bar", buffer.toString());
+ } while ((expected ^= 4) == 0);
+ }
+
/**
* Tests the {@link StringBuilders#replace(StringBuilder, String, String)} method.
*/
@Test
public void testReplace() {
- final StringBuilder buffer = new StringBuilder("One two three two one");
+ final var buffer = new StringBuilder("One two three two one");
replace(buffer, "two", "zero");
assertEquals("One zero three zero one", buffer.toString());
replace(buffer, "zero", "ten");
@@ -54,7 +67,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testReplaceChar() {
- final StringBuilder buffer = new StringBuilder("ABCDEFBCD");
+ final var buffer = new StringBuilder("ABCDEFBCD");
replace(buffer, 'B', '*');
assertEquals("A*CDEF*CD", buffer.toString());
}
@@ -64,7 +77,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testReplaceChars() {
- final StringBuilder buffer = new StringBuilder("ABCD1234EFGH");
+ final var buffer = new StringBuilder("ABCD1234EFGH");
replace(buffer, 4, 8, new char[] {'5','6','7','8'});
assertEquals("ABCD5678EFGH", buffer.toString());
replace(buffer, 4, 6, new char[] {'1','2','3','4'});
@@ -78,7 +91,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testRemove() {
- final StringBuilder buffer = new StringBuilder("EPSG.6.7");
+ final var buffer = new StringBuilder("EPSG.6.7");
remove(buffer, ".");
assertEquals("EPSG67", buffer.toString());
}
@@ -88,7 +101,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testRepeat() {
- final StringBuilder buffer = new StringBuilder("AB12");
+ final var buffer = new StringBuilder("AB12");
repeat(buffer, 2, 'C', 0);
assertEquals("AB12", buffer.toString());
repeat(buffer, 2, 'C', 1);
@@ -106,7 +119,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testTrimFractionalPart() {
- final StringBuilder buffer = new StringBuilder("4.10");
+ final var buffer = new StringBuilder("4.10");
trimFractionalPart(buffer);
assertEquals("4.10", buffer.toString());
buffer.setCharAt(2, '0'); // Replace the '1' by '0'.
@@ -121,7 +134,7 @@ public final class StringBuildersTest extends TestCase {
*/
@Test
public void testToASCII() {
- final StringBuilder s = new StringBuilder(
+ final var s = new StringBuilder(
"mètres" + Characters.PARAGRAPH_SEPARATOR +
" ‘single’, “double”, \"ascii' 30°20′10″.");
toASCII(s);
diff --git a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
index dc3f7b2eaa..af3950e91b 100644
--- a/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
+++ b/incubator/src/org.apache.sis.storage.geopackage/main/org/apache/sis/storage/geopackage/GpkgStore.java
@@ -226,7 +226,7 @@ public class GpkgStore extends SQLStore implements WritableAggregate {
assert Thread.holdsLock(this) && userContents.isEmpty();
if (create) try {
cnx.setAutoCommit(false);
- try (ScriptRunner runner = new ScriptRunner(cnx, 100)) {
+ try (ScriptRunner runner = new ScriptRunner(cnx, null, 100)) {
runner.run("PRAGMA main.application_id = " + GpkgStoreProvider.APPLICATION_ID + ';'
+ "PRAGMA main.user_version = " + GpkgStoreProvider.VERSION + ';');
runner.run("Core.sql", GpkgStore.class.getResourceAsStream("Core.sql"));
diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Clear.sql b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Clear.sql
new file mode 100644
index 0000000000..00e3f0e276
--- /dev/null
+++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Clear.sql
@@ -0,0 +1,43 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+
+
+--
+-- Delete all EPSG tables. Useful for repeated tests of data insertions.
+--
+
+DROP TABLE "Alias" CASCADE;
+DROP TABLE "Change" CASCADE;
+DROP TABLE "Conventional RS" CASCADE;
+DROP TABLE "Coordinate Axis" CASCADE;
+DROP TABLE "Coordinate Axis Name" CASCADE;
+DROP TABLE "Coordinate Reference System" CASCADE;
+DROP TABLE "Coordinate System" CASCADE;
+DROP TABLE "Coordinate_Operation" CASCADE;
+DROP TABLE "Coordinate_Operation Method" CASCADE;
+DROP TABLE "Coordinate_Operation Parameter" CASCADE;
+DROP TABLE "Coordinate_Operation Parameter Usage" CASCADE;
+DROP TABLE "Coordinate_Operation Parameter Value" CASCADE;
+DROP TABLE "Coordinate_Operation Path" CASCADE;
+DROP TABLE "Datum" CASCADE;
+DROP TABLE "Datum Ensemble" CASCADE;
+DROP TABLE "Datum Ensemble Member" CASCADE;
+DROP TABLE "Datum Realization Method" CASCADE;
+DROP TABLE "Defining Operation" CASCADE;
+DROP TABLE "Deprecation" CASCADE;
+DROP TABLE "Ellipsoid" CASCADE;
+DROP TABLE "Extent" CASCADE;
+DROP TABLE "Naming System" CASCADE;
+DROP TABLE "Prime Meridian" CASCADE;
+DROP TABLE "Scope" CASCADE;
+DROP TABLE "Supersession" CASCADE;
+DROP TABLE "Unit of Measure" CASCADE;
+DROP TABLE "Usage" CASCADE;
+DROP TABLE "Version History" CASCADE;
+DROP TYPE "Datum Kind" CASCADE;
+DROP TYPE "CRS Kind" CASCADE;
+DROP TYPE "CS Kind" CASCADE;
+DROP TYPE "Supersession Type" CASCADE;
+DROP TYPE "Table Name" CASCADE;
diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DataScriptFormatter.java b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DataScriptFormatter.java
index cd8dd320f4..8795ce9762 100644
--- a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DataScriptFormatter.java
+++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DataScriptFormatter.java
@@ -17,72 +17,34 @@
package org.apache.sis.referencing.factory.sql.epsg;
import java.util.Map;
-import java.util.HashMap;
-import java.util.function.UnaryOperator;
-import java.io.LineNumberReader;
-import java.io.InputStreamReader;
-import java.io.Writer;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Files;
-import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.Workaround;
import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.StringBuilders;
import org.apache.sis.util.privy.URLs;
import org.apache.sis.metadata.sql.privy.SQLBuilder;
import org.apache.sis.metadata.sql.privy.ScriptRunner;
-// Test dependencies
-import org.apache.sis.metadata.sql.TestDatabase;
-
/**
- * Rewrites the {@code INSERT TO ...} statements in a SQL script in a more compact form.
- * This class is used only for updating the <abbr>SQL</abbr> scripts used by Apache SIS
+ * Rewrites the {@code INSERT TO ...} statements of a <abbr>SQL</abbr> script in a more compact form.
+ * This class is used only for updating the <abbr>SQL</abbr> scripts used by Apache <abbr>SIS</abbr>
* for the <abbr>EPSG</abbr> dataset when a newer release of that dataset is available.
* The steps to follow are documented in the {@code README.md} file.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
-public final class DataScriptFormatter extends ScriptRunner {
- /**
- * Compacts the {@code *Data_Script.sql} file provided by <abbr>EPSG</abbr>.
- * This method expects two arguments:
- *
- * <ol>
- * <li>The file of the <abbr>SQL</abbr> script to convert, which must exist.</li>
- * <li>The file where to write the compacted script, which will be overwritten without warning if it exists.</li>
- * </ol>
- *
- * The values of those arguments are typically:
- *
- * <ol>
- * <li>{@code $EPSG_SCRIPTS/PostgreSQL_Data_Script.sql}</li>
- * <li>{@code sis-epsg/src/main/resources/org/apache/sis/referencing/factory/sql/epsg/Data.sql}</li>
- * </ol>
- *
- * @param arguments the source files and the destination file.
- * @throws Exception if an error occurred while reading of writing the file.
- */
- @SuppressWarnings("UseOfSystemOutOrSystemErr")
- public static void main(String[] arguments) throws Exception {
- if (arguments.length != 2) {
- System.err.println("Expected two arguments: source SQL file and target SQL file.");
- return;
- }
- try (TestDatabase db = TestDatabase.create("dummy");
- Connection c = db.source.getConnection())
- {
- final var formatter = new DataScriptFormatter(c);
- formatter.run(Path.of(arguments[0]), Path.of(arguments[1]));
- }
- }
-
+final class DataScriptFormatter extends ScriptRunner {
/**
* The {@value} keywords.
*/
@@ -94,45 +56,68 @@ public final class DataScriptFormatter extends ScriptRunner {
private static final String VALUES = "VALUES";
/**
- * The output stream, or {@code null} if closed or not yet created.
+ * Mapping from the table names in <abbr>SQL</abbr> scripts to the names used in {@code EPSGDataAccess}.
+ * Those names are derived from the names used in the dataset distributed in MS-Access format.
+ * We use those original names because they are easier to read than the names in SQL scripts.
*/
- private Writer out;
+ private final Map<String,String> toOriginalTableNames;
/**
- * The {@code INSERT INTO "Table"} statement currently executing.
- * Used in order to detect when the script start inserting values in a different table.
+ * Names of the columns to search for computing {@link TableValues#booleanColumnIndices}.
*/
- private String insertStatement;
+ private final Set<String> booleanColumns;
/**
- * Indices (in reversal order) of columns to change from type SMALLINT to type BOOLEAN.
- * Index 0 is the last column, index 1 is the column before the last, <i>etc</i>.
- * We use the reverse order because most Boolean columns in the EPSG dataset are last.
+ * Names of the columns to search for computing {@link TableValues#doubleColumnIndices}.
+ * We do not reformat the east/west/north/south bounds, the {@code greenwich_longitude},
+ * the {@code semi_major_axis}, the {@code semi_minor_axis} and the {@code inv_flattening}
+ * columns because their values are close to integers,
+ */
+ private final Set<String> doubleColumns;
+
+ /**
+ * Names of the columns to exclude. Some are columns deprecated since <abbr>EPSG</abbr> version 10+,
+ * but still present for compatibility reasons with only null values in every rows. Other columns are
+ * <abbr>EPSG</abbr> metadata not used by <abbr>SIS</abbr> (e.g. change records).
+ *
+ * <p>The following columns are replaced by the {@code "Usage"} intersection table,
+ * but <abbr>EPSG</abbr> keeps the old columns filled with null values.</p>
+ * <ul>
+ * <li>{@code area_of_use_code}: appeared in {@code "Coordinate Reference System"}, {@code "Coordinate_Operation"} and {@code "Datum"}.</li>
+ * <li>{@code crs_scope}: appeared in {@code "Coordinate Reference System"}.</li>
+ * <li>{@code coord_op_scope}: appeared in {@code "Coordinate_Operation"}.</li>
+ * <li>{@code datum_scope}: appeared in {@code "Datum"}.</li>
+ * </ul>
+ *
+ * <p>The following columns are deprecated for other reasons:</p>
+ * <ul>
+ * <li>{@code realization_epoch}: is replaced by {@code publication_date}.</li>
+ * </ul>
+ *
+ * <p>The following columns contains <abbr>EPSG</abbr> metadata not used by Apache<abbr>SIS</abbr>.
+ * They are lineage and could be included in a future version if we can map them to ISO 19115.</p>
+ * <ul>
+ * <li>{@code information_source}, {@code data_source} and {@code revision_date}: appeared in almost all tables.</li>
+ * <li>{@code change_id}: appeared in almost all tables. Required by the removal of the {@code "Change"} table.</li>
+ * </ul>
*/
- private int[] booleanColumnIndices;
+ private final Set<String> excludedColumns;
/**
- * The {@link #booleanColumnIndices} value for each table.
+ * The values for each table. The array length is the maximal number of tables that we expect in an EPSG schema.
+ * An {@link ArrayIndexOutOfBoundsException} when reading this array would mean that the schema has changed.
*/
- private final Map<String,int[]> booleanColumnIndicesForTables;
+ private final TableValues[] valuesPerTable;
/**
- * Indices (in reversal order) of columns to reformat as a double value.
- * The EPSG database contains values like {@code -0.000000000000000000000003689471323},
- * while is either not parsed by Derby because too long (Derby 10.14 documents a limit
- * of 30 characters for floating-point constants) or parsed as 0. To workaround such
- * limitation, we reformat above value as {@code -3.689471323E-24}.
+ * Number of valid values in {@link #valuesPerTable}.
*/
- private int[] doubleColumnIndices;
+ private int tableCount;
/**
- * The {@link #doubleColumnIndices} value of each table for which we want to reformat the value.
- * We do not reformat the {@code change_id} columns since they are more like character strings.
- * We do not reformat east/west/north/south bounds or {@code greenwich_longitude} since their
- * values are close to integers, or {@code semi_major_axis}, {@code semi_minor_axis} and
- * {@code inv_flattening} for similar reasons.
+ * Statements other than {@code INSERT INTO}.
*/
- private final Map<String,int[]> doubleColumnIndicesForTables;
+ private final List<String> otherStatements;
/**
* Creates a new instance.
@@ -140,37 +125,49 @@ public final class DataScriptFormatter extends ScriptRunner {
* @param c a dummy connection. Will be used for fetching metadata.
* @throws SQLException if an error occurred while fetching metadata.
*/
- private DataScriptFormatter(final Connection c) throws SQLException {
- super(c, Integer.MAX_VALUE);
- final int[] lastColumn = new int[] {0 };
- final int[] twoLastColumns = new int[] {0,1};
- final var m = new HashMap<String,int[]>();
- m.put("epsg_area", lastColumn );
- m.put("epsg_coordinateaxisname", lastColumn );
- m.put("epsg_coordinatereferencesystem", twoLastColumns);
- m.put("epsg_coordinatesystem", lastColumn );
- m.put("epsg_coordoperation", twoLastColumns);
- m.put("epsg_coordoperationmethod", new int[] {0,8});
- m.put("epsg_coordoperationparam", lastColumn );
- m.put("epsg_coordoperationparamusage", lastColumn );
- m.put("epsg_datum", lastColumn );
- m.put("epsg_ellipsoid", new int[] {0,6});
- m.put("epsg_namingsystem", lastColumn );
- m.put("epsg_primemeridian", lastColumn );
- m.put("epsg_unitofmeasure", lastColumn );
- booleanColumnIndicesForTables = m;
- doubleColumnIndicesForTables = Map.of("epsg_coordoperationparamvalue", new int[] {2});
- }
+ DataScriptFormatter(final Connection c) throws SQLException {
+ super(c, null, Integer.MAX_VALUE);
+ booleanColumns = Set.of("deprecated", "show_crs", "show_operation", "reverse_op", "param_sign_reversal", "ellipsoid_shape");
+ doubleColumns = Set.of("parameter_value");
+ excludedColumns = Set.of("area_of_use_code", "crs_scope", "coord_op_scope", "datum_scope", // Replaced by "Usage" table.
+ "information_source", "data_source", "revision_date", "change_id", // Unused EPSG metadata.
+ "realization_epoch");
- /**
- * Returns {@code true} if the given line should be omitted from the script.
- *
- * @param line the line, without trailing {@code ';'}.
- * @return {@code true} if the line should be omitted.
- */
- private static boolean omit(final String line) {
- // We omit the following line because we changed the type from VARCHAR to DATE.
- return line.startsWith("UPDATE epsg_datum SET realization_epoch = replace(realization_epoch, CHR(182), CHR(10))");
+ toOriginalTableNames = Map.ofEntries(
+ Map.entry("epsg_alias", "Alias"),
+ Map.entry("epsg_area", "Area"), // Deprecated (removed in EPSG 10)
+ Map.entry("epsg_change", "Change"),
+ Map.entry("epsg_conventionalrs", "Conventional RS"),
+ Map.entry("epsg_coordinateaxis", "Coordinate Axis"),
+ Map.entry("epsg_coordinateaxisname", "Coordinate Axis Name"),
+ Map.entry("epsg_coordoperation", "Coordinate_Operation"),
+ Map.entry("epsg_coordoperationmethod", "Coordinate_Operation Method"),
+ Map.entry("epsg_coordoperationparam", "Coordinate_Operation Parameter"),
+ Map.entry("epsg_coordoperationparamtype", "Coordinate_Operation Parameter Type"), // Only in the online registry.
+ Map.entry("epsg_coordoperationparamusage", "Coordinate_Operation Parameter Usage"),
+ Map.entry("epsg_coordoperationparamvalue", "Coordinate_Operation Parameter Value"),
+ Map.entry("epsg_coordoperationpath", "Coordinate_Operation Path"),
+ Map.entry("epsg_coordinatereferencesystem", "Coordinate Reference System"),
+ Map.entry("epsg_coordinatesystem", "Coordinate System"),
+ Map.entry("epsg_datum", "Datum"),
+ Map.entry("epsg_datumensemble", "Datum Ensemble"),
+ Map.entry("epsg_datumensemblemember", "Datum Ensemble Member"),
+ Map.entry("epsg_datumrealizationmethod", "Datum Realization Method"),
+ Map.entry("epsg_definingoperation", "Defining Operation"),
+ Map.entry("epsg_deprecation", "Deprecation"),
+ Map.entry("epsg_ellipsoid", "Ellipsoid"),
+ Map.entry("epsg_extent", "Extent"),
+ Map.entry("epsg_namingsystem", "Naming System"),
+ Map.entry("epsg_primemeridian", "Prime Meridian"),
+ Map.entry("epsg_scope", "Scope"),
+ Map.entry("epsg_supersession", "Supersession"),
+ Map.entry("epsg_unitofmeasure", "Unit of Measure"),
+ Map.entry("epsg_usage", "Usage"),
+ Map.entry("epsg_versionhistory", "Version History"));
+
+ toOriginalTableNames.forEach((oldTable, newTable) -> addReplacement(oldTable, '"' + newTable + '"'));
+ valuesPerTable = new TableValues[toOriginalTableNames.size()];
+ otherStatements = new ArrayList<>();
}
/**
@@ -181,60 +178,85 @@ public final class DataScriptFormatter extends ScriptRunner {
* @throws IOException if an I/O operation failed.
* @throws SQLException should never happen.
*/
- private void run(final Path inputFile, final Path outputFile) throws SQLException, IOException {
- if (inputFile.equals(outputFile)) {
+ final void run(final Path inputFile, final Path outputFile) throws SQLException, IOException {
+ if (Files.isSameFile(inputFile, outputFile)) {
throw new IllegalArgumentException("Input and output files are the same.");
}
- out = Files.newBufferedWriter(outputFile);
- try (LineNumberReader in = new LineNumberReader(new InputStreamReader(Files.newInputStream(inputFile), StandardCharsets.UTF_8))) {
+ try (BufferedReader in = Files.newBufferedReader(inputFile)) {
+ run(inputFile.getFileName().toString(), in);
+ }
+ try (BufferedWriter out = Files.newBufferedWriter(outputFile)) {
out.write("---\n" +
"--- Copyright International Association of Oil and Gas Producers (IOGP)\n" +
"--- See " + URLs.EPSG_LICENSE + " (a copy is in ./LICENSE.txt).\n" +
"---\n" +
- "--- This file has been reformatted (without any change in the data) for the needs of Apache SIS project.\n" +
+ "--- This file has been reformatted for the needs of Apache SIS project.\n" +
"--- See org.apache.sis.referencing.factory.sql.epsg.DataScriptFormatter.\n" +
"---\n" +
"\n");
- run(inputFile.getFileName().toString(), in);
- } finally {
- out.close();
- out = null;
+
+ // Sort in order of table name for consistency with previous versions (smaller SVN diff).
+ Arrays.sort(valuesPerTable, (t1, t2) -> t1.insertStatement.compareTo(t2.insertStatement));
+ for (int i=0; i<tableCount; i++) {
+ valuesPerTable[i].write(out);
+ }
+ for (String other : otherStatements) {
+ out.write(other);
+ out.write(";\n"); // Really want Unix EOL, not the platform-specific one.
+ }
}
}
/**
+ * Replaces the content of a text such as {@code 'some text'}.
+ * If the text content is a table name, the old table name is replaced by the new name.
+ *
+ * <h4>Corrections</h4>
* EPSG scripts version 8.9 seems to have 2 errors where the {@code OBJECT_TABLE_NAME} column contains
* {@code "AxisName"} instead of {@code "Coordinate Axis Name"}. Furthermore, the version number noted
* in the history table is a copy-and-paste error.
+ *
+ * @param sql the whole SQL statement.
+ * @param lower index of the opening quote character ({@code '}) of the text in {@code sql}.
+ * @param upper index after the closing quote character ({@code '}) of the text in {@code sql}.
*/
@Override
@Workaround(library="EPSG", version="8.9")
protected void editText(final StringBuilder sql, int lower, int upper) {
+ final String text = sql.substring(++lower, --upper);
+ final String tableName = toOriginalTableNames.get(text);
+ if (tableName != null) {
+ sql.replace(lower, upper, tableName);
+ return;
+ }
final String table; // Name of the table where to replace a value.
final String before; // String that must exist before the value to replace, or null if none.
final String oldValue; // The old value to replace.
final String newValue; // The new value.
switch (upper - lower) { // Optimization for reducing the number of comparisons.
- default: return;
- case 10: {
- table = "epsg_deprecation";
+ default: {
+ StringBuilders.trimWhitespaces(sql, lower, upper);
+ return;
+ }
+ case 8: {
+ table = "Deprecation";
before = null;
oldValue = "AxisName";
newValue = "Coordinate Axis Name";
break;
}
- case 38: {
- table = "epsg_versionhistory";
+ case 36: {
+ table = "Version History";
before = "'8.9'";
oldValue = "Version 8.8 full release of Dataset.";
newValue = "Version 8.9 full release of Dataset.";
break;
}
}
- if (CharSequences.regionMatches(sql, ++lower, oldValue)) {
+ if (oldValue.equalsIgnoreCase(text)) {
final int s = CharSequences.skipLeadingWhitespaces(sql, 0, lower);
- if (CharSequences.regionMatches(sql, s, SQLBuilder.INSERT + table + " VALUES")) {
- if (--upper - lower != oldValue.length()) {
+ if (CharSequences.regionMatches(sql, s, SQLBuilder.INSERT + '"' + table + '"')) {
+ if (upper - lower != oldValue.length()) {
throw new AssertionError("Unexpected length");
}
if (before != null) {
@@ -244,12 +266,13 @@ public final class DataScriptFormatter extends ScriptRunner {
sql.replace(lower, upper, newValue);
}
}
+ StringBuilders.trimWhitespaces(sql, lower, upper);
}
/**
* Makes sure that {@link #execute(StringBuilder)} is invoked for every line. Whether the SQL statement
* is supported or not is irrelevant for this method since we do not know yet what will be the database
- * engine; we just copy the SQL statements in a file without executing them.
+ * engine. We just copy the <abbr>SQL</abbr> statements in a file without executing them.
*
* @return {@code true}.
*/
@@ -271,217 +294,41 @@ public final class DataScriptFormatter extends ScriptRunner {
protected int execute(final StringBuilder sql) throws IOException, SQLException {
removeLF(sql);
String line = CharSequences.trimWhitespaces(sql).toString();
- if (line.startsWith("UPDATE ")) {
- /*
- * Some EPSG tables have a "table_name" field which will contain the names of other EPSG tables.
- * In the EPSG scripts, the values are initially the table names used in the MS-Access database.
- * Then the MS-Access table names are replaced by statements like below:
- *
- * UPDATE epsg_alias SET object_table_name = 'epsg_coordinateaxis' WHERE object_table_name = 'Coordinate Axis';
- * UPDATE epsg_deprecation SET object_table_name = 'epsg_alias' WHERE object_table_name = 'Alias';
- * etc.
- *
- * For Apache SIS, we keep the original table names as defined in MS-Access database,
- * for consistency with the table names that we actually use in our EPSG schema.
- */
- if (line.contains("object_table_name")) {
- return 0;
- }
- /*
- * Following statements do not make sense anymore on enumerated or boolean values:
- *
- * UPDATE epsg_coordinatereferencesystem SET coord_ref_sys_kind = replace(coord_ref_sys_kind, CHR(182), CHR(10));
- * UPDATE epsg_coordinatesystem SET coord_sys_type = replace(coord_sys_type, CHR(182), CHR(10));
- * UPDATE epsg_datum SET datum_type = replace(datum_type, CHR(182), CHR(10));
- * UPDATE epsg_coordoperationparamusage SET param_sign_reversal = replace(param_sign_reversal, CHR(182), CHR(10))
- */
- if (line.contains("replace")) {
- if (line.contains("param_sign_reversal") || line.contains("coord_ref_sys_kind")
- || line.contains("coord_sys_type") || line.contains("datum_type"))
- {
- return 0;
- }
- }
- }
- if (insertStatement != null) {
- if (line.startsWith(insertStatement)) {
+ /*
+ * Search for a table in reverse order on the assumption that the most recently added tables
+ * are the most likely to be reused in the next statement.
+ */
+ for (int i = tableCount; --i >= 0;) {
+ final TableValues values = valuesPerTable[i];
+ if (line.startsWith(values.insertStatement)) {
// The previous instruction was already an INSERT INTO the same table.
- out.append(",\n"); // Really want Unix EOL, not the platform-specific one.
- writeValues(editInsertValues(line));
+ values.add(line);
return 1;
}
- // Previous instruction was the last INSERT INTO for a given table.
- // We now have a new instruction. Append the pending cariage return.
- out.append(";\n");
}
+ /*
+ * We are beginning insertions in a new table.
+ */
if (line.startsWith(INSERT_INTO)) {
int valuesStart = line.indexOf(VALUES, INSERT_INTO.length());
if (valuesStart < 0) {
throw new SQLException("This simple program wants VALUES on the same line as INSERT INTO.");
}
- final String table = CharSequences.trimWhitespaces(line, INSERT_INTO.length(), valuesStart).toString();
- booleanColumnIndices = booleanColumnIndicesForTables.getOrDefault(table, ArraysExt.EMPTY_INT);
- doubleColumnIndices = doubleColumnIndicesForTables.getOrDefault(table, ArraysExt.EMPTY_INT);
- /*
- * We are beginning insertions in a new table.
- */
valuesStart += VALUES.length(); // Move to the end of "VALUES".
- insertStatement = CharSequences.trimWhitespaces(line, 0, valuesStart).toString();
- out.append(insertStatement).append('\n');
- writeValues(editInsertValues(line));
+ final CharSequence insertStatement = CharSequences.trimWhitespaces(line, 0, valuesStart);
+ final var values = new TableValues(insertStatement.toString(), booleanColumns, doubleColumns, excludedColumns);
+ valuesPerTable[tableCount++] = values;
+ values.add(line);
return 1;
}
- insertStatement = null;
- if (!omit(line)) {
- out.append(line).append(";\n");
- }
+ /*
+ * May be an {@code UPDATE} statement. They existed in older versions of EPSG database.
+ * But with the most recent EPSG versions, the `otherStatements` list should stay empty.
+ */
+ otherStatements.add(line);
return 0;
}
- /**
- * Writes the values after an {@code INSERT INTO "Table" VALUES} expression.
- * This method tries to remove extra spaces before ( and after ) for producing a more compact file.
- */
- private void writeValues(final String values) throws IOException {
- if (values.startsWith("(") && values.endsWith(")")) {
- out.append('(').append(CharSequences.trimWhitespaces(values, 1, values.length() - 1)).append(')');
- } else {
- out.append(values);
- }
- }
-
- /**
- * Modifies the given {@code INSERT INTO table VALUE (…)} line before to write it.
- * The given line is only the {@code VALUE (…)} part for a single entry.
- * The modifications applied on entry values can be a change of integer types to
- * boolean types, and rewrite of some floating point values.
- */
- private String editInsertValues(String line) throws SQLException {
- line = CharSequences.trimWhitespaces(line, insertStatement.length(), line.length()).toString();
- line = editColumns(booleanColumnIndices, line, DataScriptFormatter::replaceIntegerByBoolean);
- line = editColumns(doubleColumnIndices, line, DataScriptFormatter::reformatFloatingPoints);
- line = removeUselessExponents(line);
- return line;
- }
-
- /**
- * Modifies the content of columns identified by the given indices.
- *
- * @param indices indices (in reversal order) of columns to edit. Index 0 is the last column,
- * index 1 is the column before the last, <i>etc</i>.
- * @param line the line to modify.
- * @param converter the transformation to apply columns identified by the given indices.
- * A {@code null} return value means that the conversion cannot be performed.
- * @return the modified line.
- */
- private static String editColumns(final int[] indices, final String line, final UnaryOperator<String> converter)
- throws SQLException
- {
- final var buffer = new StringBuilder(line);
- int end = CharSequences.skipTrailingWhitespaces(buffer, 0, buffer.length());
- if (buffer.codePointBefore(end) == ')') end--;
- for (int n=0, columnIndex=0; n < indices.length; columnIndex++) {
- int start = end;
- for (int c; (c = buffer.codePointBefore(start)) != ',';) {
- start -= Character.charCount(c);
- if (c == '\'') {
- while (true) {
- c = buffer.codePointBefore(start);
- start -= Character.charCount(c);
- if (c == '\'') {
- if (buffer.codePointBefore(start) != '\'') {
- break;
- }
- start--;
- }
- }
- }
- }
- if (columnIndex == indices[n]) {
- final String value = CharSequences.trimWhitespaces(buffer, start, end).toString();
- final String c = converter.apply(value);
- if (value == null) {
- throw new SQLException("Unexpected value \"" + value + "\" at position " + start + " in:\n" + line);
- }
- buffer.replace(start, end, c);
- n++;
- }
- end = CharSequences.skipTrailingWhitespaces(buffer, 0, start - 1);
- }
- return buffer.toString();
- }
-
- /**
- * Replaces the last {@code SMALLINT} types by {@code BOOLEAN}.
- * This is for consistency with the table type documented in the class javadoc.
- */
- private static String replaceIntegerByBoolean(String value) {
- if (value.equals("0") || value.equalsIgnoreCase("'No'")) {
- value = "false";
- } else if (value.equals("1") || value.equalsIgnoreCase("'Yes'")) {
- value = "true";
- } else if (value.equalsIgnoreCase("Null") || value.equals("''")) {
- value = "Null";
- } else {
- value = null;
- }
- return value;
- }
-
- /**
- * Reformats the given floating point number. This is used for replacing for example
- * {@code -0.000000000000000000000003689471323} by {@code -3.689471323E-24}.
- */
- private static String reformatFloatingPoints(String value) {
- if (!value.equalsIgnoreCase("Null")) {
- value = Double.toString(Double.parseDouble(value));
- value = CharSequences.trimFractionalPart(value).toString();
- }
- return value;
- }
-
- /**
- * For private usage by the following method only.
- */
- private final Pattern uselessExponentPattern =
- Pattern.compile("([\\(\\,]\\-?\\d+\\.\\d+)E[\\+\\-]?0+([\\,\\)])");
-
- /**
- * Removes the useless "E0" exponents after floating point numbers.
- */
- private String removeUselessExponents(String line) {
- StringBuilder cleaned = null;
- final Matcher matcher = uselessExponentPattern.matcher(line);
- while (true) {
- int lastIndex = 0;
- while (matcher.find()) {
- // Make sure this is not a quoted text.
- boolean quoted = false;
- for (int i=matcher.start(); (i=line.lastIndexOf('\'', i-1)) >= 0;) {
- if (i == 0 || line.charAt(i-1) != '\\') {
- quoted = !quoted;
- }
- }
- if (!quoted) {
- // Found a number outside quotes. Replace.
- if (cleaned == null) {
- cleaned = new StringBuilder();
- }
- cleaned.append(line, lastIndex, matcher.end(1));
- lastIndex = matcher.end();
- cleaned.append(line, matcher.start(2), lastIndex);
- }
- }
- if (lastIndex == 0) {
- return line;
- }
- cleaned.append(line, lastIndex, line.length());
- line = cleaned.toString();
- matcher.reset(line);
- cleaned.setLength(0);
- }
- }
-
/**
* Reformats a multi-line text as a single line text. For each occurrence of line feed
* (the {@code '\n'} character) found in the given buffer, this method performs the following steps:
@@ -499,16 +346,16 @@ public final class DataScriptFormatter extends ScriptRunner {
* <p><b>Note:</b> current version does not use codepoint API
* on the assumption that it is not needed for EPSG's SQL files.</p>
*
- * @param buffer the string in which to perform the removal.
+ * @param sql the string in which to perform the removal.
*/
- static void removeLF(final StringBuilder buffer) {
- int i = buffer.length();
- while ((i = buffer.lastIndexOf("\n", i)) >= 0) {
- final int length = buffer.length();
+ static void removeLF(final StringBuilder sql) {
+ int i = sql.length();
+ while ((i = sql.lastIndexOf("\n", i)) >= 0) {
+ final int length = sql.length();
int nld = 0;
int upper = i;
while (++upper < length) {
- final char c = buffer.charAt(upper);
+ final char c = sql.charAt(upper);
if (!Character.isWhitespace(c)) {
if (Character.isLetterOrDigit(c)) {
nld++;
@@ -517,7 +364,7 @@ public final class DataScriptFormatter extends ScriptRunner {
}
}
while (i != 0) {
- final char c = buffer.charAt(--i);
+ final char c = sql.charAt(--i);
if (!Character.isWhitespace(c)) {
if (Character.isLetterOrDigit(c)) {
nld++;
@@ -529,9 +376,9 @@ public final class DataScriptFormatter extends ScriptRunner {
if (nld == 2) {
upper--;
}
- buffer.delete(i, upper);
+ sql.delete(i, upper);
if (nld == 2) {
- buffer.setCharAt(i, ' ');
+ sql.setCharAt(i, ' ');
}
}
}
diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
index b9aa24ba56..20346befa9 100644
--- a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
+++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
@@ -6,13 +6,10 @@
--
-- Creates views for manual inspections of EPSG dataset. This script can be executed after EPSG database
--- installation on PostgreSQL. This file is never read by Apache SIS; it is provided only as a debugging
+-- installation on PostgreSQL. This file is never read by Apache SIS. It is provided only as a debugging
-- tool when the developer is looking for some information about the EPSG dataset.
--
-SET client_encoding = 'UTF8';
-SET search_path = 'EPSG';
-
--
-- The dimensions (number of axes) of all Coordinate Reference Systems found in the EPSG database.
@@ -24,13 +21,13 @@ CREATE VIEW "CRS dimension" AS
RIGHT JOIN "Coordinate Reference System" AS CRS ON CA.COORD_SYS_CODE = CRS.COORD_SYS_CODE
GROUP BY COORD_REF_SYS_CODE, COORD_REF_SYS_NAME ORDER BY COORD_REF_SYS_CODE;
-COMMENT ON VIEW "CRS dimension" IS 'Dimensions of all Coordinate Reference Systems.';
+COMMENT ON VIEW "CRS dimension" IS 'Number of dimensions of all Coordinate Reference Systems.';
--
-- The source and target dimensions of all Operation Methods found in the EPSG database.
--
-CREATE VIEW "OperationMethod dimension" AS
+CREATE VIEW "Operation Method dimension" AS
SELECT COM.COORD_OP_METHOD_CODE, COORD_OP_METHOD_NAME, COORD_OP_TYPE, IS_CONVERSION,
SOURCE_MIN_DIM, SOURCE_MAX_DIM, TARGET_MIN_DIM, TARGET_MAX_DIM
FROM
@@ -53,20 +50,20 @@ CREATE VIEW "OperationMethod dimension" AS
RIGHT JOIN "Coordinate_Operation Method" AS COM ON COM.COORD_OP_METHOD_CODE = DIM.COORD_OP_METHOD_CODE
ORDER BY COM.COORD_OP_METHOD_CODE;
-COMMENT ON VIEW "OperationMethod dimension" IS 'Dimensions of most Operation Methods.';
+COMMENT ON VIEW "Operation Method dimension" IS 'Number of dimensions of most Operation Methods.';
--
-- Summary of Operation Method dimensions, grouped by types (projection or not).
--
-CREATE VIEW "OperationMethodType dimension" AS
+CREATE VIEW "Operation Method Type dimension" AS
SELECT COORD_OP_TYPE, IS_CONVERSION,
MIN(OD.SOURCE_MIN_DIM) AS SOURCE_MIN_DIM,
MAX(OD.SOURCE_MAX_DIM) AS SOURCE_MAX_DIM,
MIN(OD.TARGET_MIN_DIM) AS TARGET_MIN_DIM,
MAX(OD.TARGET_MAX_DIM) AS TARGET_MAX_DIM
- FROM "OperationMethod dimension" AS OD
+ FROM "Operation Method dimension" AS OD
GROUP BY COORD_OP_TYPE, IS_CONVERSION
ORDER BY IS_CONVERSION;
-COMMENT ON VIEW "OperationMethodType dimension" IS 'Dimensions of Operation Methods types.';
+COMMENT ON VIEW "Operation Method Type dimension" IS 'Number of dimensions of Operation Method types.';
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 905941e658..0605037233 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
@@ -57,13 +57,14 @@ cp $EPSG_SCRIPTS/PostgreSQL_FKey_Script.sql FKeys.sql
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 `change` table,
- remove the `UNIQUE` constraint on the `change_id` column
- and add a `CONSTRAINT pk_change PRIMARY KEY (change_id)` line instead.
* 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`
@@ -72,15 +73,17 @@ Open the `Tables.sql` file for edition:
* 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 `epsg_table_name`.
-* Change the type of `coord_ref_sys_kind` column from `VARCHAR(24)` to `epsg_crs_kind`.
-* Change the type of `coord_sys_type` column from `VARCHAR(24)` to `epsg_cs_kind`.
-* Change the type of `datum_type` column from `VARCHAR(24)` to `epsg_datum_kind`.
-* Change the type of `supersession_type` column from `VARCHAR(50)` to `epsg_supersession_type`.
+* 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.
@@ -89,7 +92,7 @@ in which case the maintainer can just revert the changes in order to preserve th
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 `booleanColumnIndicesForTables` and `doubleColumnIndicesForTables` collections.
-Execute the `main` method of the `org.apache.sis.referencing.factory.sql.epsg.DataScriptFormatter` class
+Execute the `main` method of the `org.apache.sis.referencing.factory.sql.epsg.Updater` class
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:
@@ -107,7 +110,7 @@ export CLASSPATH=$PWD/endorsed/build/classes/java/test/org.apache.sis.metadata:$
export CLASSPATH=$PWD/optional/build/classes/java/test/org.apache.sis.referencing.epsg:$CLASSPATH
# From any directory
-java org.apache.sis.referencing.factory.sql.epsg.DataScriptFormatter $EPSG_SCRIPTS/PostgreSQL_Data_Script.sql $NON_FREE_DIR/Data.sql
+java org.apache.sis.referencing.factory.sql.epsg.Updater $EPSG_SCRIPTS/PostgreSQL_Data_Script.sql $NON_FREE_DIR/Data.sql
```
Run the tests. It it convenient to run `org.apache.sis.referencing.factory.sql.EPSGInstallerTest`
diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/TableValues.java b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/TableValues.java
new file mode 100644
index 0000000000..815b29541b
--- /dev/null
+++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/TableValues.java
@@ -0,0 +1,364 @@
+/*
+ * 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.referencing.factory.sql.epsg;
+
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.sql.SQLException;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.StringBuilders;
+
+
+/**
+ * The values associated to an {@code INSERT INTO "Table"} statement.
+ *
+ * @author Martin Desruisseaux (IRD, Geomatys)
+ */
+final class TableValues implements Comparator<String> {
+ /**
+ * The {@code INSERT INTO "Table"} statement currently executing, with all columns.
+ * Used in order to detect when the script starts to insert values in a different table.
+ */
+ final String insertStatement;
+
+ /**
+ * All columns found in {@link #insertStatement}.
+ * Excluded columns will be set to {@code null} after all lines have been added.
+ */
+ private final String[] columns;
+
+ /**
+ * Indices (in reverse order) of columns to change from type SMALLINT to type BOOLEAN.
+ * Index 0 is the last column, index 1 is the column before the last, <i>etc</i>.
+ * We use the reverse order because most Boolean columns in the EPSG dataset are last.
+ */
+ private final int[] booleanColumnIndices;
+
+ /**
+ * Indices (in reverse order) of columns to reformat as {@code double} values.
+ * The EPSG database contains values like {@code -0.000000000000000000000003689471323},
+ * which are either not parsed by Derby because too long (Derby 10.14 documents a limit
+ * of 30 characters for floating-point constants) or parsed as 0. To workaround such
+ * limitation, we reformat the above value as {@code -3.689471323E-24}.
+ */
+ private final int[] doubleColumnIndices;
+
+ /**
+ * Indices (in reverse order) of columns to exclude. Some are columns deprecated since <abbr>EPSG</abbr> version 10+,
+ * but still present for compatibility reasons with only null values in every rows. Some other are <abbr>EPSG</abbr>
+ * metadata not used by <abbr>SIS</abbr> (e.g. change records).
+ */
+ private final int[] excludedColumnIndices;
+
+ /**
+ * Whether the whole table is excluded. We exclude <abbr>EPSG</abbr> metadata
+ * that are not used by Apache <abbr>SIS</abbr>, such as the {@code "Change"} table.
+ */
+ private final boolean excludedTable;
+
+ /**
+ * The reformatted values to insert in the table.
+ */
+ private final List<String> data;
+
+ /**
+ * Creates an initially empty set of values.
+ *
+ * @param insertStatement the {@code INSERT INTO} statement.
+ * @param booleanColumns names of the columns to change from type SMALLINT to type BOOLEAN.
+ * @param doubleColumns names of the columns to reformat as {@code double} values.
+ * @param excluded0Columns names of the columns to exclude.
+ */
+ TableValues(final String insertStatement,
+ final Set<String> booleanColumns,
+ final Set<String> doubleColumns,
+ final Set<String> excludedColumns)
+ {
+ this.insertStatement = insertStatement;
+ columns = (String[]) CharSequences.split(
+ insertStatement.substring(
+ insertStatement.indexOf('(') + 1,
+ insertStatement.lastIndexOf(')')), ',');
+
+ booleanColumnIndices = indices(booleanColumns);
+ doubleColumnIndices = indices(doubleColumns);
+ excludedColumnIndices = indices(excludedColumns);
+ excludedTable = insertStatement.contains("\"Change\"");
+ data = new ArrayList<>(1000);
+ }
+
+ /**
+ * Returns the indices where the columns are found, in reverse order.
+ * Indices are returned in reverse order (0 is the last column) for convenience,
+ * as it is slightly easier to modify a line in reverse order (modified parts of
+ * the text do not change the indexes of the next parts to look at).
+ */
+ private int[] indices(final Set<String> search) {
+ final int[] indices = new int[columns.length];
+ int n = 0;
+ for (int i = columns.length; --i >= 0;) {
+ if (search.contains(columns[i])) {
+ indices[n++] = (columns.length - 1) - i;
+ }
+ }
+ return Arrays.copyOfRange(indices, 0, n);
+ }
+
+ /**
+ * Used for sorting rows in increasing order of identifiers.
+ * The identifiers are usually integers, but sometime use a {@code majpor.minor} pattern.
+ */
+ @Override
+ public int compare(final String line1, final String line2) {
+ int s1 = endOfInteger(line1, 0);
+ int s2 = endOfInteger(line2, 0);
+ if (s1 == 0) return (s2 == 0) ? line1.compareTo(line2) : +1;
+ if (s2 == 0) return -1; // Sort lines without identifier last.
+ int c = Integer.parseInt(line1, 0, s1, 10)
+ - Integer.parseInt(line2, 0, s2, 10);
+ if (c == 0) {
+ final boolean p1 = line1.charAt(s1) == '.';
+ final boolean p2 = line2.charAt(s2) == '.';
+ if (p1 & p2) {
+ // We do not bother to check if a number exists. It should always be the case.
+ c = Integer.parseInt(line1, ++s1, endOfInteger(line1, s1), 10)
+ - Integer.parseInt(line2, ++s2, endOfInteger(line2, s2), 10);
+ } else {
+ if (p1) return +1;
+ if (p2) return -1;
+ }
+ }
+ return c;
+ }
+
+ /**
+ * Returns the index after the last digit, or {@code offset} if none.
+ *
+ * @param line the line to scan.
+ * @param offset first index to scan.
+ * @return index after the last digit.
+ */
+ private static int endOfInteger(final String line, int offset) {
+ final int length = line.length();
+ while (offset < length) {
+ final char c = line.charAt(offset);
+ if (c < '0' || c > '9') break;
+ offset++;
+ }
+ return offset;
+ }
+
+ /**
+ * Writes the {@code INSERT INTO} statement followed by all values.
+ * This is invoked for writing the final result after all lines have been added.
+ */
+ final void write(final BufferedWriter out) throws IOException {
+ if (data.isEmpty()) {
+ // The whole table has been excluded. Example: "Change".
+ return;
+ }
+ data.sort(this);
+ for (int i : excludedColumnIndices) {
+ columns[columns.length - 1 - i] = null;
+ }
+ out.write(insertStatement, 0, insertStatement.indexOf('('));
+ String separator = "(";
+ for (String column : columns) {
+ if (column != null) {
+ out.write(separator);
+ out.write(column);
+ separator = ", ";
+ }
+ }
+ separator = insertStatement.substring(insertStatement.lastIndexOf(')')) + '\n';
+ for (String value : data) {
+ out.write(separator);
+ out.write('(');
+ out.write(value);
+ out.write(')');
+ separator = ",\n"; // Really want Unix EOL, not the platform-specific one.
+ }
+ out.write(";\n");
+ }
+
+ /**
+ * Modifies the given {@code INSERT INTO table VALUE (…)} line, then adds it to the list of lines to write.
+ * The given line is only the {@code VALUE (…)} part for a single entry.
+ * The modifications applied on entry values can be a change of integer types to
+ * Boolean types and a reformatting of some floating point values.
+ */
+ final void add(String line) throws SQLException {
+ if (excludedTable) {
+ return;
+ }
+ line = removeUselessExponents(line);
+
+ int start, end;
+ end = line.length();
+ start = CharSequences.skipLeadingWhitespaces(line, insertStatement.length(), end);
+ end = CharSequences.skipTrailingWhitespaces(line, start, end);
+ if (line.charAt(start++) != '(' || line.charAt(--end) != ')') {
+ throw new SQLException("Illegal `VALUES` statement:" + System.lineSeparator() + line);
+ }
+ start = CharSequences.skipLeadingWhitespaces (line, start, end);
+ end = CharSequences.skipTrailingWhitespaces(line, start, end);
+ final var buffer = new StringBuilder(end - start).append(line, start, end);
+
+ end = buffer.length();
+ int excludeIndex = 0;
+ int booleanIndex = 0;
+ int doubleIndex = 0;
+ for (int columnIndex=0; end > 0; columnIndex++) { // Index in reverse order.
+ /*
+ * Search in reverse order for the indices where each column starts and ends.
+ * If the value is a character sequence, the range includes the ' quotes.
+ * Escaped quotes ('') are supported.
+ */
+ start = end;
+ for (int c; start > 0 && (c = buffer.codePointBefore(start)) != ',';) {
+ start -= Character.charCount(c);
+ if (c == '\'') {
+ for (;;) {
+ c = buffer.codePointBefore(start);
+ start -= Character.charCount(c);
+ if (c == '\'') {
+ if (buffer.codePointBefore(start) != '\'') {
+ break;
+ }
+ start--;
+ }
+ }
+ }
+ }
+ /*
+ * Check if the column should be excluded. This code assumes that the column to exclude
+ * is never the first column, because the first column is usually the primary key.
+ * This assumption allows to assume that a coma always exists before the column.
+ */
+ if (excludeIndex < excludedColumnIndices.length && columnIndex == excludedColumnIndices[excludeIndex]) {
+ excludeIndex++;
+ buffer.delete(--start, end); // Above loop sets `start` to the index immediately after the coma.
+ end = CharSequences.skipTrailingWhitespaces(buffer, 0, start);
+ continue;
+ }
+ /*
+ * Eventually reformat the column value. We replace empty strings by Null because
+ * we replaced some VARCHAR by DATE, and an empty string is an invalid date.
+ */
+ String value = CharSequences.trimWhitespaces(buffer, start, end).toString();
+ if (value.equals("''")) {
+ value = "Null";
+ } else if (booleanIndex < booleanColumnIndices.length && columnIndex == booleanColumnIndices[booleanIndex]) {
+ booleanIndex++;
+ value = replaceIntegerByBoolean(value);
+ } else if (doubleIndex < doubleColumnIndices.length && columnIndex == doubleColumnIndices[doubleIndex]) {
+ doubleIndex++;
+ value = reformatFloatingPoints(value);
+ }
+ // Do the replacement even if the value didn't changed because it may remove spaces.
+ buffer.replace(start, end, value);
+ end = CharSequences.skipTrailingWhitespaces(buffer, 0, start - 1); // -1 is for skipping the coma.
+ }
+ StringBuilders.trimWhitespaces(buffer, 0, buffer.length());
+ data.add(buffer.toString());
+ }
+
+ /**
+ * Replaces the last {@code SMALLINT} types by {@code BOOLEAN}.
+ * This is for consistency with the table type documented in the class Javadoc.
+ */
+ private static String replaceIntegerByBoolean(String value) throws SQLException {
+ if (value.equals("0") || value.equalsIgnoreCase("'No'")) {
+ return "false";
+ } else if (value.equals("1") || value.equalsIgnoreCase("'Yes'")) {
+ return "true";
+ } else if (value.equalsIgnoreCase("Null") || value.equals("''")) {
+ return "Null";
+ } else {
+ throw new SQLException("Unexpected Boolean value \"" + value + "\".");
+ }
+ }
+
+ /**
+ * Reformats the given floating point number. This is used for replacing for example
+ * {@code -0.000000000000000000000003689471323} by {@code -3.689471323E-24}.
+ */
+ private static String reformatFloatingPoints(String value) {
+ if (!value.equalsIgnoreCase("Null")) {
+ value = Double.toString(Double.parseDouble(value));
+ value = CharSequences.trimFractionalPart(value).toString();
+ }
+ return value;
+ }
+
+ /**
+ * For private usage by the following method only.
+ */
+ private static final Pattern USELESS_EXPONENT =
+ Pattern.compile("([\\(\\,]\\-?\\d+\\.\\d+)E[\\+\\-]?0+([\\,\\)])");
+
+ /**
+ * Removes the useless "E0" exponents after floating point numbers.
+ */
+ private static String removeUselessExponents(String line) {
+ StringBuilder cleaned = null;
+ final Matcher matcher = USELESS_EXPONENT.matcher(line);
+ while (true) {
+ int lastIndex = 0;
+ while (matcher.find()) {
+ // Make sure this is not a quoted text.
+ boolean quoted = false;
+ for (int i=matcher.start(); (i=line.lastIndexOf('\'', i-1)) >= 0;) {
+ if (i == 0 || line.charAt(i-1) != '\\') {
+ quoted = !quoted;
+ }
+ }
+ if (!quoted) {
+ // Found a number outside quotes. Replace.
+ if (cleaned == null) {
+ cleaned = new StringBuilder();
+ }
+ cleaned.append(line, lastIndex, matcher.end(1));
+ lastIndex = matcher.end();
+ cleaned.append(line, matcher.start(2), lastIndex);
+ }
+ }
+ if (lastIndex == 0) {
+ return line;
+ }
+ cleaned.append(line, lastIndex, line.length());
+ line = cleaned.toString();
+ matcher.reset(line);
+ cleaned.setLength(0);
+ }
+ }
+
+ /**
+ * Returns a string representation for debugging purposes.
+ */
+ @Override
+ public String toString() {
+ return insertStatement;
+ }
+}
diff --git a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Updater.java b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Updater.java
new file mode 100644
index 0000000000..5718574c4b
--- /dev/null
+++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/Updater.java
@@ -0,0 +1,73 @@
+/*
+ * 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.referencing.factory.sql.epsg;
+
+import java.nio.file.Path;
+import java.sql.Connection;
+
+// Test dependencies
+import org.apache.sis.metadata.sql.TestDatabase;
+
+
+/**
+ * A command-line tool for updating the <abbr>EPSG</abbr> {@code Data.sql} file distributed by Apache <abbr>SIS</abbr>.
+ * The steps to follow are documented in the {@code README.md} file.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+public final class Updater {
+ /**
+ * Do not allow instantiation of this class.
+ */
+ private Updater() {
+ }
+
+ /**
+ * Compacts the {@code *Data_Script.sql} file provided by <abbr>EPSG</abbr>.
+ * This method expects two arguments:
+ *
+ * <ol>
+ * <li>The file of the <abbr>SQL</abbr> script to convert, which must exist.</li>
+ * <li>The file where to write the compacted script, which will be overwritten without warning if it exists.</li>
+ * </ol>
+ *
+ * The values of those arguments are typically as below, where the {@code EPSG_SCRIPTS} and {@code NON_FREE_DIR}
+ * environment variables are defined in the {@code README.md} file in the same directory as the source code of
+ * this class:
+ *
+ * <ol>
+ * <li>{@code $EPSG_SCRIPTS/PostgreSQL_Data_Script.sql}</li>
+ * <li>{@code $NON_FREE_DIR/epsg/Data.sql}</li>
+ * </ol>
+ *
+ * @param arguments the source files and the destination file.
+ * @throws Exception if an error occurred while reading of writing the file.
+ */
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ public static void main(String[] arguments) throws Exception {
+ if (arguments.length != 2) {
+ System.err.println("Expected two arguments: source SQL file and target SQL file.");
+ return;
+ }
+ try (TestDatabase db = TestDatabase.create("dummy");
+ Connection c = db.source.getConnection())
+ {
+ final var formatter = new DataScriptFormatter(c);
+ formatter.run(Path.of(arguments[0]), Path.of(arguments[1]));
+ }
+ }
+}