This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit d4bba49f28907f4c08d7e33ee4a80746786f999f Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Mar 18 12:46:15 2025 +0100 Make the search for database metadata more robust to drivers that do not define an escape character. --- .../apache/sis/metadata/sql/privy/SQLBuilder.java | 20 ++++--- .../sis/metadata/sql/privy/SQLUtilities.java | 39 ++++++++------ .../org/apache/sis/metadata/sql/privy/Syntax.java | 47 +++++++++++++++- .../apache/sis/storage/sql/feature/Analyzer.java | 8 --- .../apache/sis/storage/sql/feature/Database.java | 63 +++++++++++++--------- .../sis/storage/sql/feature/FeatureIterator.java | 5 +- .../sis/storage/sql/feature/FeatureStream.java | 4 +- .../sis/storage/sql/feature/InfoStatements.java | 2 +- .../storage/sql/feature/SelectionClauseWriter.java | 7 ++- .../sis/storage/sql/feature/TableAnalyzer.java | 48 ++++++++++------- .../apache/sis/io/stream/InternalOptionKey.java | 2 +- 11 files changed, 157 insertions(+), 88 deletions(-) diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java index cae7decee6..9c8c60629f 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLBuilder.java @@ -299,6 +299,8 @@ public class SQLBuilder extends Syntax { /** * Appends a string as an escaped {@code LIKE} argument. * This method does not put any {@code '} character, and does not accept null argument. + * If the database does not have predefined wildcard characters, then the query result + * may contain false positives. * * <p>This method does not double the simple quotes of the given string on intent, because * it may be used in a {@code PreparedStatement}. If the simple quotes need to be doubled, @@ -306,14 +308,20 @@ public class SQLBuilder extends Syntax { * * @param value the value to append. * @return this builder, for method call chaining. + * + * @see #escapeWildcards(String) */ public final SQLBuilder appendWildcardEscaped(final String value) { - final int start = buffer.length(); - buffer.append(value); - for (int i = buffer.length(); --i >= start;) { - final char c = buffer.charAt(i); - if (c == '_' || c == '%') { - buffer.insert(i, escape); + if (cannotEscapeWildcards()) { + buffer.append(value.replace("%", "_")); + } else { + final int start = buffer.length(); + buffer.append(value); + for (int i = buffer.length(); --i >= start;) { + final char c = buffer.charAt(i); + if (c == '_' || c == '%') { + buffer.insert(i, escape); + } } } return this; diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLUtilities.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLUtilities.java index c3196ad286..4e400eeaa9 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLUtilities.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/SQLUtilities.java @@ -91,28 +91,37 @@ public final class SQLUtilities extends Static { } /** - * Returns the given pattern with {@code '_'} and {@code '%'} characters escaped by the database-specific + * Returns the given text with {@code '_'} and {@code '%'} characters escaped by the database-specific * escape characters. This method should be invoked for escaping the values of all {@link DatabaseMetaData} - * method arguments with a name ending by {@code "Pattern"}. Note that not all arguments are pattern; please - * checks carefully {@link DatabaseMetaData} javadoc for each method. + * method arguments having a name ending by {@code "Pattern"}. Note that not all arguments are patterns, + * please check carefully the {@link DatabaseMetaData} javadoc for each method. * * <h4>Example</h4> - * If a method expects an argument named {@code tableNamePattern}, - * then that argument value should be escaped. But if the argument name is only {@code tableName}, - * then the value should not be escaped. + * If a method expects an argument named {@code tableNamePattern}, then the value should be escaped + * if an exact match is desired. But if the argument name is only {@code tableName}, then the value + * should not be escaped. * - * @param pattern the pattern to escape, or {@code null} if none. - * @param escape value of {@link DatabaseMetaData#getSearchStringEscape()}. - * @return escaped strings, or the same instance as {@code pattern} if there are no characters to escape. + * <h4>Missing escape characters</h4> + * Some databases do not provide an escape character. If the given {@code escape} is null or empty, + * then instead of escaping, this method will replace all occurrences of {@code '%'} by {@code '_'}. + * It will cause the database to return more metadata rows that desired. Callers should filter by + * comparing the table and schema name specified in each row against the original {@code name}. + * + * @param text the text to escape for use in a context equivalent to the {@code LIKE} statement. + * @param escape value of {@link DatabaseMetaData#getSearchStringEscape()}. May be null or empty. + * @return the given text with wildcard characters escaped. */ - public static String escape(final String pattern, final String escape) { - if (pattern != null) { + public static String escape(final String text, final String escape) { + if (text != null) { + if (escape == null || escape.isEmpty()) { + return text.replace("%", "_"); + } StringBuilder buffer = null; - for (int i = pattern.length(); --i >= 0;) { - final char c = pattern.charAt(i); + for (int i = text.length(); --i >= 0;) { + final char c = text.charAt(i); if (c == '_' || c == '%') { if (buffer == null) { - buffer = new StringBuilder(pattern); + buffer = new StringBuilder(text); } buffer.insert(i, escape); } @@ -121,7 +130,7 @@ public final class SQLUtilities extends Static { return buffer.toString(); } } - return pattern; + return text; } /** diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java index d4ab856b54..8021ffe8dd 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java @@ -50,8 +50,14 @@ public class Syntax { /** * The string that can be used to escape wildcard characters. * This is the value returned by {@link DatabaseMetaData#getSearchStringEscape()}. + * It may be null or empty if the database has no escape character, in which case + * the statement should be of the form {@code WHERE "column" LIKE ? ESCAPE '\'} + * (replace {@code '\'} by the desired escape character). + * + * @see #escapeWildcards(String) + * @see SQLBuilder#appendWildcardEscaped(String) */ - protected final String escape; + final String escape; /** * Creates a new {@code Syntax} initialized from the given database metadata. @@ -68,7 +74,7 @@ public class Syntax { } else { dialect = Dialect.ANSI; quote = "\""; - escape = "\\"; + escape = null; } this.quoteSchema = quoteSchema; } @@ -84,4 +90,41 @@ public class Syntax { quote = other.quote; quoteSchema = other.quoteSchema; } + + /** + * Returns the given text with {@code '_'} and {@code '%'} characters escaped by the database-specific + * escape characters. This method should be invoked for escaping the values of all {@link DatabaseMetaData} + * method arguments having a name ending by {@code "Pattern"}. Note that not all arguments are patterns, + * please check carefully the {@link DatabaseMetaData} javadoc for each method. + * + * <h4>Example</h4> + * If a method expects an argument named {@code tableNamePattern}, then the value should be escaped + * if an exact match is desired. But if the argument name is only {@code tableName}, then the value + * should not be escaped. + * + * <h4>Missing escape characters</h4> + * Some databases do not provide an escape character. If the given {@code escape} is null or empty, + * then instead of escaping, this method will replace all occurrences of {@code '%'} by {@code '_'}. + * It will cause the database to return more metadata rows that desired. Callers should filter by + * comparing the table and schema name specified in each row against the original {@code name}. + * + * @param text the text to escape for use in a context equivalent to the {@code LIKE} statement. + * @return the given text with wildcard characters escaped. + */ + public final String escapeWildcards(final String text) { + return SQLUtilities.escape(text, escape); + } + + /** + * Returns {@code true} if the database can <em>not</em> escape wildcard characters. + * In such case, the string returned by {@link #escapeWildcards(String)} may produce + * false positive, and the callers need to apply additional filtering. + * + * <p>This is rarely true, but may happen with incomplete <abbr>JDBC</abbr> drivers.</p> + * + * @return whether the database can <em>not</em> escape wildcard characters. + */ + public final boolean cannotEscapeWildcards() { + return (escape == null) || escape.isEmpty(); + } } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java index 4e6cec6d49..a8a6e0fd4d 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java @@ -97,13 +97,6 @@ public final class Analyzer { */ private final Map<String,String> uniqueStrings; - /** - * The string to insert before wildcard characters ({@code '_'} or {@code '%'}) to escape. - * This is used by {@link #escape(String)} before to pass argument values (e.g. table name) - * to {@link DatabaseMetaData} methods expecting a pattern. - */ - final String escape; - /** * The "TABLE" and "VIEW" keywords for table types, with unsupported keywords omitted. */ @@ -163,7 +156,6 @@ public final class Analyzer { throws Exception { this.metadata = metadata; - this.escape = metadata.getSearchStringEscape(); this.nameFactory = DefaultNameFactory.provider(); this.featureTables = new HashMap<>(); this.uniqueStrings = new HashMap<>(); diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java index e61f12d6ba..b83f7b6e4d 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java @@ -43,7 +43,6 @@ import org.apache.sis.metadata.sql.privy.Syntax; import org.apache.sis.metadata.sql.privy.Dialect; import org.apache.sis.metadata.sql.privy.Reflection; import org.apache.sis.metadata.sql.privy.SQLBuilder; -import org.apache.sis.metadata.sql.privy.SQLUtilities; import org.apache.sis.storage.FeatureSet; import org.apache.sis.storage.FeatureNaming; import org.apache.sis.storage.DataStoreException; @@ -80,7 +79,7 @@ import org.opengis.metadata.citation.PresentationForm; * specialized methods or have non-standard behavior for some data types.</p> * * <h2>Specializations</h2> - * Subclasses may be defined for some database engines. Methods that can be overridden are: + * Subclasses may be defined for some specific database drivers. Methods that can be overridden are: * <ul> * <li>{@link #getPossibleSpatialSchemas(Map)} for enumerating the spatial schema conventions that may be used.</li> * <li>{@link #getMapping(Column)} for adding column types to recognize.</li> @@ -327,18 +326,6 @@ public class Database<G> extends Syntax { return Collections.unmodifiableMap(softwareVersions); } - /** - * Returns the value of a {@code LIKE} statement with wildcard characters escaped. - * The wildcard characters are {@code '_'} and {@code '%'}. This method is invoked - * when an exact match for the given value is desired. - * - * @param value the {@code LIKE} pattern to search. - * @return the given pattern with wildcard characters escaped. - */ - final String escapeWildcards(final String value) { - return SQLUtilities.escape(value, escape); - } - /** * Detects automatically which spatial schema is in use. Detects also the catalog name and schema name. * This method is invoked exactly once after construction and before the analysis of feature tables. @@ -358,6 +345,7 @@ public class Database<G> extends Syntax { */ String crsTable = null; final var ignoredTables = new HashMap<String,Boolean>(8); + final boolean isSearchReliable = !cannotEscapeWildcards(); final SpatialSchema[] candidates = getPossibleSpatialSchemas(ignoredTables); for (int i=0; i<candidates.length; i++) { final SpatialSchema convention = candidates[i]; @@ -386,13 +374,16 @@ public class Database<G> extends Syntax { // Unconditionally check table existence during the first iteration. if (i == 0 || entry.getValue() == null) { boolean exists = false; - String table = escapeWildcards(entry.getKey()); - try (ResultSet reflect = metadata.getTables(null, null, table, tableTypes)) { + final String table = entry.getKey(); + try (ResultSet reflect = metadata.getTables(null, null, escapeWildcards(table), tableTypes)) { while (reflect.next()) { - consistent &= consistent(catalog, catalog = reflect.getString(Reflection.TABLE_CAT)); - consistent &= consistent(schema, schema = reflect.getString(Reflection.TABLE_SCHEM)); - found |= !Boolean.FALSE.equals(entry.getValue()); // Accept `true` and `null` values. - exists = true; + // Double-check of the table name because not all software can escape wildcards. + if (isSearchReliable || table.equals(reflect.getString(Reflection.TABLE_NAME))) { + consistent &= consistent(catalog, catalog = reflect.getString(Reflection.TABLE_CAT)); + consistent &= consistent(schema, schema = reflect.getString(Reflection.TABLE_SCHEM)); + found |= !Boolean.FALSE.equals(entry.getValue()); // Accept `true` and `null` values. + exists = true; + } } } entry.setValue(exists); @@ -415,17 +406,21 @@ public class Database<G> extends Syntax { * The preference order will be defined by the `CRSEncoding` enumeration order. */ if (spatialSchema != null) { - final String schema = escapeWildcards(schemaOfSpatialTables); - final String table = escapeWildcards(crsTable); for (Map.Entry<CRSEncoding, String> entry : spatialSchema.crsDefinitionColumn.entrySet()) { String column = entry.getValue(); if (metadata.storesLowerCaseIdentifiers()) { column = column.toLowerCase(Locale.US); } - column = escapeWildcards(column); - try (ResultSet reflect = metadata.getColumns(catalogOfSpatialTables, schema, table, column)) { - if (reflect.next()) { - crsEncodings.add(entry.getKey()); + try (ResultSet reflect = metadata.getColumns(catalogOfSpatialTables, // No escape for this argument. + escapeWildcards(schemaOfSpatialTables), + escapeWildcards(crsTable), + escapeWildcards(column))) + { + while (reflect.next()) { + if (isSearchReliable || filterMetadata(reflect, schemaOfSpatialTables, crsTable, column)) { + crsEncodings.add(entry.getKey()); + break; + } } } } @@ -572,6 +567,22 @@ public class Database<G> extends Syntax { sql.append(name); } + /** + * Double-checks whether the metadata about a table or a column are for the item that we requested. + * We perform this double check because some database drivers have no predefined escape characters + * for wildcards. If any {@code String} argument is {@code null} or empty, it will be ignored. + * + * <p>Note that the catalog is not verified because the {@code catalog} argument in + * {@link DatabaseMetaData} is not a pattern.</p> + */ + static boolean filterMetadata(ResultSet reflect, String schema, String table, String column) + throws SQLException + { + return (Strings.isNullOrEmpty(schema) || schema.equals(reflect.getString(Reflection.TABLE_SCHEM))) && + (Strings.isNullOrEmpty(table) || table.equals(reflect.getString(Reflection.TABLE_NAME))) && + (Strings.isNullOrEmpty(column) || column.equals(reflect.getString(Reflection.COLUMN_NAME))); + } + /** * Returns an identification of the table and column naming conventions. * This is absent if the database is not spatial. diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java index ba0836a33d..7634cb5178 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureIterator.java @@ -16,7 +16,6 @@ */ package org.apache.sis.storage.sql.feature; -import java.util.List; import java.util.ArrayList; import java.util.Spliterator; import java.util.function.Consumer; @@ -121,7 +120,7 @@ final class FeatureIterator implements Spliterator<Feature>, AutoCloseable { ? table.database.createInfoStatements(connection) : null; String sql = adapter.sql; if (distinct || filter != null || sort != null || offset > 0 || count > 0) { - final SQLBuilder builder = new SQLBuilder(table.database).append(sql); + final var builder = new SQLBuilder(table.database).append(sql); if (distinct) { builder.insertDistinctAfterSelect(); } @@ -293,7 +292,7 @@ final class FeatureIterator implements Spliterator<Feature>, AutoCloseable { * @return the feature as a singleton {@code Feature} or as a {@code Collection<Feature>}. */ private Object fetchReferenced(final Feature owner) throws Exception { - final List<Feature> features = new ArrayList<>(); + final var features = new ArrayList<Feature>(); try (ResultSet r = statement.executeQuery()) { result = r; fetch(features::add, true); diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java index f1485ce02f..c24daa5e88 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/FeatureStream.java @@ -311,7 +311,7 @@ final class FeatureStream extends DeferredStream<Feature> { * Build the full SQL statement here, without using `FeatureAdapter.sql`, * because we do not need to follow foreigner keys. */ - final SQLBuilder sql = new SQLBuilder(table.database).append(SQLBuilder.SELECT).append("COUNT("); + final var sql = new SQLBuilder(table.database).append(SQLBuilder.SELECT).append("COUNT("); if (distinct) { String separator = "DISTINCT "; for (final Column attribute : table.attributes) { @@ -401,7 +401,7 @@ final class FeatureStream extends DeferredStream<Feature> { final Connection connection = getConnection(); setCloseHandler(connection); // Executed only if `FeatureIterator` creation fails, discarded later otherwise. makeReadOnly(connection); - final FeatureIterator features = new FeatureIterator(table, connection, distinct, filter, sort, offset, count); + final var features = new FeatureIterator(table, connection, distinct, filter, sort, offset, count); setCloseHandler(features); return features; } diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java index ce4c1dbfb1..f8590e8d43 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/InfoStatements.java @@ -236,7 +236,7 @@ public class InfoStatements implements Localized, AutoCloseable { /** * Sets the parameter value for a table catalog or schema. Those parameters use the {@code LIKE} statement * in order to ignore the catalog or schema when it is not specified. A catalog or schema is not specified - * if the string is null or empty. The latter case may happens with some drivers with, for example, + * if the string is null or empty. The latter case may happen with some drivers with, for example, * materialized views. * * @param columnQuery the query where to set the parameter. diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java index d575e0a8c0..a47317c3ca 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/SelectionClauseWriter.java @@ -17,7 +17,6 @@ package org.apache.sis.storage.sql.feature; import java.util.List; -import java.util.Map; import java.util.HashMap; import java.util.Locale; import java.util.function.BiConsumer; @@ -150,7 +149,7 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> { * @return a writer with unsupported functions removed. */ final SelectionClauseWriter removeUnsupportedFunctions(final Database<?> database) { - final Map<String,SpatialOperatorName> unsupported = new HashMap<>(); + final var unsupported = new HashMap<String, SpatialOperatorName>(); try (Connection c = database.source.getConnection()) { final DatabaseMetaData metadata = c.getMetaData(); /* @@ -173,9 +172,9 @@ public class SelectionClauseWriter extends Visitor<Feature, SelectionClause> { * Remove from above map all functions that are supported by the database. * This list is potentially large so we do not put those items in a map. */ - final String pattern = (lowerCase ? "st_%" : "ST\\_%").replace("\\", metadata.getSearchStringEscape()); + final String prefix = database.escapeWildcards(lowerCase ? "st_" : "ST_"); try (ResultSet r = metadata.getFunctions(database.catalogOfSpatialTables, - database.schemaOfSpatialTables, pattern)) + database.schemaOfSpatialTables, prefix + '%')) { while (r.next()) { unsupported.remove(r.getString("FUNCTION_NAME")); diff --git a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/TableAnalyzer.java b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/TableAnalyzer.java index 043923ec66..5bcdd8dab1 100644 --- a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/TableAnalyzer.java +++ b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/TableAnalyzer.java @@ -22,7 +22,6 @@ import java.sql.SQLException; import java.sql.ResultSet; import org.apache.sis.storage.DataStoreException; import org.apache.sis.metadata.sql.privy.Reflection; -import org.apache.sis.metadata.sql.privy.SQLUtilities; import org.apache.sis.util.privy.Strings; @@ -47,6 +46,13 @@ final class TableAnalyzer extends FeatureAnalyzer { */ private final String tableEsc, schemaEsc; + /** + * Whether the filtering by schema name and table name is reliable. + * This is usually true, but may be false with incomplete drivers + * that do not declare search escape characters. + */ + private final boolean isSearchReliable; + /** * Creates an analyzer for the table of the given name. * The table is identified by {@code id}, which contains a (catalog, schema, name) tuple. @@ -57,11 +63,15 @@ final class TableAnalyzer extends FeatureAnalyzer { * the table that "contains" this table. Otherwise {@code null}. * @throws SQLException if an error occurred while fetching information from the database. */ + @SuppressWarnings("StringEquality") TableAnalyzer(final Analyzer analyzer, final TableReference id, final TableReference dependencyOf) throws SQLException { super(analyzer, id); this.dependencyOf = dependencyOf; - this.tableEsc = escape(id.table); - this.schemaEsc = escape(id.schema); + this.tableEsc = analyzer.database.escapeWildcards(id.table); + this.schemaEsc = analyzer.database.escapeWildcards(id.schema); + isSearchReliable = !analyzer.database.cannotEscapeWildcards() + || (tableEsc == id.table && schemaEsc == id.schema); // Identity checks are okay. + try (ResultSet reflect = analyzer.metadata.getPrimaryKeys(id.catalog, id.schema, id.table)) { while (reflect.next()) { primaryKey.add(analyzer.getUniqueString(reflect, Reflection.COLUMN_NAME)); @@ -76,18 +86,12 @@ final class TableAnalyzer extends FeatureAnalyzer { } /** - * Returns the given pattern with {@code '_'} and {@code '%'} characters escaped by the database-specific - * escape characters. This method should be invoked for escaping the values of all {@link DatabaseMetaData} - * method arguments with a name ending by {@code "Pattern"}. Note that not all arguments are pattern; please - * checks carefully {@link DatabaseMetaData} javadoc for each method. - * - * <h4>Example</h4> - * If a method expects an argument named {@code tableNamePattern}, - * then that argument value should be escaped. But if the argument name is only {@code tableName}, - * then the value should not be escaped. + * Double-checks whether the metadata about a table are for the item that we requested. + * We perform this double check because some database drivers have no predefined escape + * characters for wildcards. */ - private String escape(final String pattern) { - return SQLUtilities.escape(pattern, analyzer.escape); + private boolean filterMetadata(final ResultSet reflect) throws SQLException { + return isSearchReliable || Database.filterMetadata(reflect, id.schema, id.table, null); } /** @@ -155,9 +159,11 @@ final class TableAnalyzer extends FeatureAnalyzer { final String quote = analyzer.metadata.getIdentifierQuoteString(); try (ResultSet reflect = analyzer.metadata.getColumns(id.catalog, schemaEsc, tableEsc, null)) { while (reflect.next()) { - final var column = new Column(analyzer, reflect, quote); - if (columns.put(column.name, column) != null) { - throw duplicatedColumn(column); + if (filterMetadata(reflect)) { + final var column = new Column(analyzer, reflect, quote); + if (columns.put(column.name, column) != null) { + throw duplicatedColumn(column); + } } } } @@ -185,9 +191,11 @@ final class TableAnalyzer extends FeatureAnalyzer { if (id instanceof Relation) { try (ResultSet reflect = analyzer.metadata.getTables(id.catalog, schemaEsc, tableEsc, null)) { while (reflect.next()) { - final String remarks = Strings.trimOrNull(analyzer.getUniqueString(reflect, Reflection.REMARKS)); - if (remarks != null) { - return remarks; + if (filterMetadata(reflect)) { + String remarks = Strings.trimOrNull(analyzer.getUniqueString(reflect, Reflection.REMARKS)); + if (remarks != null) { + return remarks; + } } } } diff --git a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/InternalOptionKey.java b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/InternalOptionKey.java index b34ff79a7d..f36ac483f2 100644 --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/InternalOptionKey.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/io/stream/InternalOptionKey.java @@ -58,7 +58,7 @@ public final class InternalOptionKey<T> extends OptionKey<T> { /** * The lock to use in a data store when those locks are optional. For example, data stores on * <abbr>SQL</abbr> databases should not need locks because <abbr>ACID</abbr>-compliant databases - * should support thread-safe transactions. However, some database products do not provide the + * should support thread-safe transactions. However, some database drivers do not provide the * expected thread-safety, in which case Apache <abbr>SIS</abbr> may need to do locking itself. */ public static final InternalOptionKey<ReadWriteLock> LOCKS =