org.firebirdsql.jdbc.metadata.MetadataPattern Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird Show documentation
Show all versions of jaybird Show documentation
JDBC Driver for the Firebird RDBMS
The newest version!
/*
* Firebird Open Source JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc.metadata;
import org.firebirdsql.util.InternalApi;
import java.util.regex.Pattern;
import static org.firebirdsql.jdbc.metadata.FbMetadataConstants.OBJECT_NAME_LENGTH;
/**
* Holder of a database metadata pattern.
*
* Provides information whether the pattern is the all-pattern, or if the condition needs a normal equality comparison,
* a SQL {@code LIKE}, or a SQL {@code STARTING WITH}.
*
*
* @author Mark Rotteveel
* @since 4.0
*/
@InternalApi
public final class MetadataPattern {
// Extra space to allow for longer patterns (avoids string right truncation errors)
private static final int OBJECT_NAME_PARAMETER_LENGTH = OBJECT_NAME_LENGTH + 10;
private static final MetadataPattern ALL_PATTERN = new MetadataPattern(ConditionType.NONE, null);
private static final MetadataPattern EMPTY_PATTERN = new MetadataPattern(ConditionType.SQL_EQUALS, "");
private static final MetadataPattern IS_NULL = new MetadataPattern(ConditionType.SQL_IS_NULL, null);
private static final Pattern METADATA_SPECIALS = Pattern.compile("([\\\\_%])");
private final ConditionType conditionType;
private final String conditionValue;
/**
* Create a metadata pattern.
*
* @param conditionType
* Type of condition to be used.
* @param conditionValue
* Value to be used with the specified condition type
*/
private MetadataPattern(ConditionType conditionType, String conditionValue) {
this.conditionType = conditionType;
this.conditionValue = conditionValue;
}
/**
* @return Type of condition to use for this metadata pattern
*/
public ConditionType getConditionType() {
return conditionType;
}
/**
* @return Value for the condition; {@code null} signals no value
*/
public String getConditionValue() {
return conditionValue;
}
/**
* Renders the condition for this pattern.
*
* @param columnName
* column name
* @return Rendered condition (can be empty string if there is no condition).
*/
public String renderCondition(String columnName) {
return conditionType.renderCondition(columnName);
}
/**
* @return Metadata pattern matcher for this metadata pattern
*/
public MetadataPatternMatcher toMetadataPatternMatcher() {
return MetadataPatternMatcher.fromPattern(this);
}
/**
* Compiles the metadata pattern.
*
* @param metadataPattern
* Metadata pattern string
* @return MetadataPattern instance
*/
public static MetadataPattern compile(String metadataPattern) {
if (isAllCondition(metadataPattern)) {
return ALL_PATTERN;
}
if (metadataPattern.isEmpty()) {
return EMPTY_PATTERN;
}
if (!containsPatternSpecialChars(metadataPattern)) {
return new MetadataPattern(ConditionType.SQL_EQUALS, metadataPattern);
}
return parsePattern(metadataPattern);
}
/**
* Creates a {@code MetadataPattern} explicit for an equals ({@code =}) condition.
*
* @param value value for equals condition
* @return MetadataPattern of type {@code SQL_EQUALS}
*/
static MetadataPattern equalsCondition(String value) {
return new MetadataPattern(ConditionType.SQL_EQUALS, value);
}
/**
* Creates a {@code MetadataPattern} for an {@code IS NULL} condition
*
* @return MetadataPattern of type {@code SQL_IS_NULL}
*/
static MetadataPattern isNullCondition() {
return IS_NULL;
}
/**
* Scans string to determine if string contains any of {@code \_%} that indicates additional processing is needed.
*
* @param pattern
* metadata pattern
* @return {@code true} if the string contains any like special characters
*/
public static boolean containsPatternSpecialChars(String pattern) {
for (int idx = 0; idx < pattern.length(); idx++) {
if (isPatternSpecialChar(pattern.charAt(idx))) {
return true;
}
}
return false;
}
/**
* Checks if character is a database metadata pattern special.
*
* @param charVal
* Character to check
* @return {@code true} if {@code charVal} is a pattern special ({@code \_%})
*/
public static boolean isPatternSpecialChar(char charVal) {
return charVal == '%' || charVal == '_' || charVal == '\\';
}
/**
* Escapes the like wildcards and escape ({@code \_%} in the provided search string with a {@code \}.
*
* @param objectName
* Object name to escape.
* @return Object name with wildcards escaped.
*/
public static String escapeWildcards(String objectName) {
if (objectName == null) {
return null;
}
return METADATA_SPECIALS.matcher(objectName).replaceAll("\\\\$1");
}
@SuppressWarnings("java:S127")
private static MetadataPattern parsePattern(String metadataPattern) {
ConditionType conditionType = ConditionType.SQL_EQUALS;
boolean hasEscape = false;
final int patternLength = metadataPattern.length();
final StringBuilder likePattern = new StringBuilder(patternLength);
for (int idx = 0; idx < patternLength; idx++) {
char ch = metadataPattern.charAt(idx);
switch (ch) {
case '%' -> {
if (idx == patternLength - 1 && conditionType == ConditionType.SQL_EQUALS) {
// No other unescaped LIKE pattern characters: we can use STARTING_WITH
conditionType = ConditionType.SQL_STARTING_WITH;
// Pattern complete (without the final %)
} else {
conditionType = ConditionType.SQL_LIKE;
likePattern.append(ch);
}
}
case '_' -> {
conditionType = ConditionType.SQL_LIKE;
likePattern.append(ch);
}
case '\\' -> {
hasEscape = true;
if (idx < patternLength - 1 && isPatternSpecialChar(metadataPattern.charAt(idx + 1))) {
// We add the character here, so we skip it in the next iteration to distinguish
// unescaped wildcards (which trigger SQL_LIKE) from escaped wildcards
likePattern.append('\\').append(metadataPattern.charAt(++idx));
} else {
// Escape followed by non-special or end of string: introduce escape
likePattern.append('\\').append('\\');
}
}
default -> likePattern.append(ch);
}
}
if (!hasEscape) {
return new MetadataPattern(conditionType,
conditionType == ConditionType.SQL_EQUALS ? metadataPattern : likePattern.toString());
}
if (conditionType != ConditionType.SQL_LIKE) {
// We have no unescaped wildcards; strip escapes so we can use plain equals
stripEscapes(likePattern);
}
return new MetadataPattern(conditionType, likePattern.toString());
}
private static void stripEscapes(StringBuilder likePattern) {
for (int idx = 0; idx < likePattern.length(); idx++) {
if (likePattern.charAt(idx) == '\\') {
// This removes the escape and then skips the next (previously escaped) character
likePattern.deleteCharAt(idx);
}
}
}
public static boolean isAllCondition(String metadataPattern) {
return metadataPattern == null || "%".equals(metadataPattern);
}
public enum ConditionType {
NONE {
@Override
String renderCondition(String columnName) {
return "";
}
},
SQL_LIKE {
@Override
String renderCondition(String columnName) {
// We are trimming trailing spaces to avoid issues with LIKE and CHAR padding
return "trim(trailing from " + columnName + ") like "
+ "cast(? as varchar(" + OBJECT_NAME_PARAMETER_LENGTH + ")) escape '\\' ";
}
},
SQL_EQUALS {
@Override
String renderCondition(String columnName) {
return columnName + " = cast(? as varchar(" + OBJECT_NAME_PARAMETER_LENGTH + ")) ";
}
},
SQL_STARTING_WITH {
@Override
String renderCondition(String columnName) {
return columnName + " starting with cast(? as varchar(" + OBJECT_NAME_PARAMETER_LENGTH + ")) ";
}
},
SQL_IS_NULL {
@Override
String renderCondition(String columnName) {
return columnName + " is null ";
}
};
/**
* Renders a parameterized condition of this type.
*
* @param columnName
* Name of columns
* @return Rendered condition (or empty string for no condition)
*/
abstract String renderCondition(String columnName);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy