From d5ecab1e71b712c4f82f512a8e8df59b4310f19f Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Fri, 6 Dec 2019 11:15:19 +0100 Subject: [PATCH] [jOOQ/jOOQ#9633] Add a new Settings.interpreterNameLookupCaseSensitivity - Default is now ParseNameCase.UPPER_IF_UNQUOTED ([jOOQ/jOOQ#9238]) - Made ParseNameCase logic reusable for ParserImpl and DDLInterpreter - DDLInterpreter MutableNamed model now uses inner classes to share Configuration instance access - Case insensitive comparisons now use Settings.renderLocale() TODO: [jOOQ/jOOQ#9636] --- .../src/main/java/org/jooq/conf/Settings.java | 24 ++-- .../java/org/jooq/impl/DDLInterpreter.java | 117 +++++++++++++----- .../main/java/org/jooq/impl/ParserImpl.java | 91 ++------------ jOOQ/src/main/java/org/jooq/impl/Tools.java | 87 +++++++++++++ .../resources/xsd/jooq-runtime-3.13.0.xsd | 2 +- 5 files changed, 193 insertions(+), 128 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index ab7f48231d..4467251a85 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -192,7 +192,7 @@ public class Settings protected SQLDialect interpreterDialect; @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") - protected InterpreterNameLookupCaseSensitivity interpreterNameCaseSensitivity = InterpreterNameLookupCaseSensitivity.DEFAULT; + protected InterpreterNameLookupCaseSensitivity interpreterNameLookupCaseSensitivity = InterpreterNameLookupCaseSensitivity.DEFAULT; @XmlElement(type = String.class, defaultValue = "DEFAULT") @XmlJavaTypeAdapter(SQLDialectAdapter.class) protected SQLDialect parseDialect; @@ -1617,16 +1617,16 @@ public class Settings * [#9633] The case sensitivity of identifiers used when interpreting SQL DDL statements. * */ - public InterpreterNameLookupCaseSensitivity getInterpreterNameCaseSensitivity() { - return interpreterNameCaseSensitivity; + public InterpreterNameLookupCaseSensitivity getInterpreterNameLookupCaseSensitivity() { + return interpreterNameLookupCaseSensitivity; } /** * [#9633] The case sensitivity of identifiers used when interpreting SQL DDL statements. * */ - public void setInterpreterNameCaseSensitivity(InterpreterNameLookupCaseSensitivity value) { - this.interpreterNameCaseSensitivity = value; + public void setInterpreterNameLookupCaseSensitivity(InterpreterNameLookupCaseSensitivity value) { + this.interpreterNameLookupCaseSensitivity = value; } /** @@ -2296,8 +2296,8 @@ public class Settings * [#9633] The case sensitivity of identifiers used when interpreting SQL DDL statements. * */ - public Settings withInterpreterNameCaseSensitivity(InterpreterNameLookupCaseSensitivity value) { - setInterpreterNameCaseSensitivity(value); + public Settings withInterpreterNameLookupCaseSensitivity(InterpreterNameLookupCaseSensitivity value) { + setInterpreterNameLookupCaseSensitivity(value); return this; } @@ -2456,7 +2456,7 @@ public class Settings builder.append("executeUpdateWithoutWhere", executeUpdateWithoutWhere); builder.append("executeDeleteWithoutWhere", executeDeleteWithoutWhere); builder.append("interpreterDialect", interpreterDialect); - builder.append("interpreterNameCaseSensitivity", interpreterNameCaseSensitivity); + builder.append("interpreterNameLookupCaseSensitivity", interpreterNameLookupCaseSensitivity); builder.append("parseDialect", parseDialect); builder.append("parseNameCase", parseNameCase); builder.append("parseWithMetaLookups", parseWithMetaLookups); @@ -3063,12 +3063,12 @@ public class Settings return false; } } - if (interpreterNameCaseSensitivity == null) { - if (other.interpreterNameCaseSensitivity!= null) { + if (interpreterNameLookupCaseSensitivity == null) { + if (other.interpreterNameLookupCaseSensitivity!= null) { return false; } } else { - if (!interpreterNameCaseSensitivity.equals(other.interpreterNameCaseSensitivity)) { + if (!interpreterNameLookupCaseSensitivity.equals(other.interpreterNameLookupCaseSensitivity)) { return false; } } @@ -3224,7 +3224,7 @@ public class Settings result = ((prime*result)+((executeUpdateWithoutWhere == null)? 0 :executeUpdateWithoutWhere.hashCode())); result = ((prime*result)+((executeDeleteWithoutWhere == null)? 0 :executeDeleteWithoutWhere.hashCode())); result = ((prime*result)+((interpreterDialect == null)? 0 :interpreterDialect.hashCode())); - result = ((prime*result)+((interpreterNameCaseSensitivity == null)? 0 :interpreterNameCaseSensitivity.hashCode())); + result = ((prime*result)+((interpreterNameLookupCaseSensitivity == null)? 0 :interpreterNameLookupCaseSensitivity.hashCode())); result = ((prime*result)+((parseDialect == null)? 0 :parseDialect.hashCode())); result = ((prime*result)+((parseNameCase == null)? 0 :parseNameCase.hashCode())); result = ((prime*result)+((parseWithMetaLookups == null)? 0 :parseWithMetaLookups.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java index 9f75da1ae5..fdfa388fa7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java +++ b/jOOQ/src/main/java/org/jooq/impl/DDLInterpreter.java @@ -38,6 +38,7 @@ package org.jooq.impl; import static org.jooq.Name.Quoted.QUOTED; +import static org.jooq.conf.SettingsTools.renderLocale; import static org.jooq.impl.AbstractName.NO_NAME; import static org.jooq.impl.Cascade.CASCADE; import static org.jooq.impl.Cascade.RESTRICT; @@ -46,7 +47,9 @@ import static org.jooq.impl.ConstraintType.PRIMARY_KEY; import static org.jooq.impl.SQLDataType.BIGINT; import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.intersect; +import static org.jooq.impl.Tools.normaliseNameCase; import static org.jooq.impl.Tools.reverseIterable; +import static org.jooq.tools.StringUtils.defaultIfNull; import java.util.AbstractList; import java.util.ArrayList; @@ -96,16 +99,18 @@ import org.jooq.tools.JooqLogger; @SuppressWarnings("serial") final class DDLInterpreter { - private static final JooqLogger log = JooqLogger.getLogger(DDLInterpreter.class); + private static final JooqLogger log = JooqLogger.getLogger(DDLInterpreter.class); - private final Configuration configuration; - private final Map catalogs = new LinkedHashMap<>(); - private final MutableCatalog defaultCatalog; - private final MutableSchema defaultSchema; - private MutableSchema currentSchema; + private final Configuration configuration; + private final InterpreterNameLookupCaseSensitivity caseSensitivity; + private final Map catalogs = new LinkedHashMap<>(); + private final MutableCatalog defaultCatalog; + private final MutableSchema defaultSchema; + private MutableSchema currentSchema; DDLInterpreter(Configuration configuration) { this.configuration = configuration; + this.caseSensitivity = caseSensitivity(configuration); this.defaultCatalog = new MutableCatalog(NO_NAME); this.catalogs.put(defaultCatalog.name(), defaultCatalog); this.defaultSchema = new MutableSchema(NO_NAME, defaultCatalog); @@ -1029,7 +1034,7 @@ final class DDLInterpreter { return schema; } - private static final MutableTable newTable( + private final MutableTable newTable( Table table, MutableSchema schema, List> columns, @@ -1172,18 +1177,62 @@ final class DDLInterpreter { return result; } + private static final InterpreterNameLookupCaseSensitivity caseSensitivity(Configuration configuration) { + InterpreterNameLookupCaseSensitivity result = defaultIfNull(configuration.settings().getInterpreterNameLookupCaseSensitivity(), InterpreterNameLookupCaseSensitivity.DEFAULT); + + if (result == InterpreterNameLookupCaseSensitivity.DEFAULT) { + switch (defaultIfNull(configuration.settings().getInterpreterDialect(), configuration.family()).family()) { + + + + + + + + + + + + + case DERBY: + case FIREBIRD: + case H2: + case HSQLDB: + case POSTGRES: + return InterpreterNameLookupCaseSensitivity.WHEN_QUOTED; + + + + + + + + + + + + case MYSQL: + case SQLITE: + return InterpreterNameLookupCaseSensitivity.NEVER; + + case DEFAULT: + default: + return InterpreterNameLookupCaseSensitivity.WHEN_QUOTED; + } + } + + return result; + } + // ------------------------------------------------------------------------- // Data model // ------------------------------------------------------------------------- - private static abstract class MutableNamed { + private abstract class MutableNamed { private UnqualifiedName name; private String upper; private Comment comment; - // TODO: Access this from Settings - private InterpreterNameLookupCaseSensitivity caseSensitive = InterpreterNameLookupCaseSensitivity.WHEN_QUOTED; - MutableNamed(UnqualifiedName name) { this(name, null); } @@ -1202,7 +1251,7 @@ final class DDLInterpreter { this.name = n; // TODO: Use Settings.renderLocale() - this.upper = name.last().toUpperCase(); + this.upper = name.last().toUpperCase(renderLocale(configuration.settings())); } Comment comment() { @@ -1214,15 +1263,21 @@ final class DDLInterpreter { } boolean nameEquals(UnqualifiedName other) { + switch (caseSensitivity) { + case ALWAYS: + return name.last().equals(other.last()); - // TODO: Implement InterpreterNameLookupCaseSensitivity.DEFAULT - if (caseSensitive == InterpreterNameLookupCaseSensitivity.ALWAYS || - (caseSensitive == InterpreterNameLookupCaseSensitivity.WHEN_QUOTED && (name.quoted() == QUOTED || other.quoted() == QUOTED))) - return name.last().equals(other.last()); - else + case WHEN_QUOTED: + return normaliseNameCase(configuration, name.last(), name.quoted() == QUOTED).equals( + normaliseNameCase(configuration, other.last(), other.quoted() == QUOTED)); - // TODO: Use Settings.renderLocale() - return upper.equalsIgnoreCase(other.last().toUpperCase()); + case NEVER: + return upper.equalsIgnoreCase(other.last().toUpperCase(renderLocale(configuration.settings()))); + + case DEFAULT: + default: + throw new IllegalStateException(); + } } abstract void drop(); @@ -1233,7 +1288,7 @@ final class DDLInterpreter { } } - private static final class MutableCatalog extends MutableNamed { + private final class MutableCatalog extends MutableNamed { List schemas = new MutableNamedList<>(); MutableCatalog(UnqualifiedName name) { @@ -1262,7 +1317,7 @@ final class DDLInterpreter { } } - private static final class MutableSchema extends MutableNamed { + private final class MutableSchema extends MutableNamed { MutableCatalog catalog; List tables = new MutableNamedList<>(); List sequences = new MutableNamedList<>(); @@ -1319,7 +1374,7 @@ final class DDLInterpreter { } } - private static final class MutableTable extends MutableNamed { + private final class MutableTable extends MutableNamed { MutableSchema schema; List fields = new MutableNamedList<>(); MutableUniqueKey primaryKey; @@ -1524,7 +1579,7 @@ final class DDLInterpreter { } @SuppressWarnings("unused") - private static final class MutableSequence extends MutableNamed { + private final class MutableSequence extends MutableNamed { MutableSchema schema; Field startWith; Field incrementBy; @@ -1557,7 +1612,7 @@ final class DDLInterpreter { } } - private static abstract class MutableKey extends MutableNamed { + private abstract class MutableKey extends MutableNamed { MutableTable keyTable; List keyFields; @@ -1569,7 +1624,7 @@ final class DDLInterpreter { } } - private static final class MutableCheck extends MutableNamed { + private final class MutableCheck extends MutableNamed { MutableTable table; Condition condition; @@ -1584,7 +1639,7 @@ final class DDLInterpreter { final void drop() {} } - private static final class MutableUniqueKey extends MutableKey { + private final class MutableUniqueKey extends MutableKey { List referencingKeys = new MutableNamedList<>(); MutableUniqueKey(UnqualifiedName name, MutableTable keyTable, List keyFields) { @@ -1598,7 +1653,7 @@ final class DDLInterpreter { } } - private static final class MutableForeignKey extends MutableKey { + private final class MutableForeignKey extends MutableKey { MutableUniqueKey referencedKey; Action onDelete; Action onUpdate; @@ -1625,7 +1680,7 @@ final class DDLInterpreter { } } - private static final class MutableIndex extends MutableNamed { + private final class MutableIndex extends MutableNamed { MutableTable table; List fields; boolean unique; @@ -1642,7 +1697,7 @@ final class DDLInterpreter { final void drop() {} } - private static final class MutableField extends MutableNamed { + private final class MutableField extends MutableNamed { MutableTable table; DataType type; @@ -1657,7 +1712,7 @@ final class DDLInterpreter { final void drop() {} } - private static final class MutableSortField extends MutableNamed { + private final class MutableSortField extends MutableNamed { MutableField field; SortOrder sort; @@ -1672,7 +1727,7 @@ final class DDLInterpreter { final void drop() {} } - private static final class MutableNamedList extends AbstractList { + private final class MutableNamedList extends AbstractList { private final List delegate = new ArrayList<>(); @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 033a68106e..5ab978297c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -50,7 +50,6 @@ import static org.jooq.JoinType.JOIN; // ... import static org.jooq.conf.ParseWithMetaLookups.IGNORE_ON_FAILURE; import static org.jooq.conf.ParseWithMetaLookups.THROW_ON_FAILURE; -import static org.jooq.conf.SettingsTools.renderLocale; import static org.jooq.impl.AbstractName.NO_NAME; import static org.jooq.impl.DSL.abs; import static org.jooq.impl.DSL.acos; @@ -311,7 +310,7 @@ import static org.jooq.impl.Tools.EMPTY_NAME; import static org.jooq.impl.Tools.EMPTY_QUERYPART; import static org.jooq.impl.Tools.EMPTY_ROWN; import static org.jooq.impl.Tools.EMPTY_SORTFIELD; -import static org.jooq.tools.StringUtils.defaultIfNull; +import static org.jooq.impl.Tools.normaliseNameCase; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; @@ -486,7 +485,6 @@ import org.jooq.WindowSpecificationExcludeStep; import org.jooq.WindowSpecificationOrderByStep; import org.jooq.WindowSpecificationRowsAndStep; import org.jooq.WindowSpecificationRowsStep; -import org.jooq.conf.ParseNameCase; import org.jooq.conf.ParseSearchSchema; import org.jooq.conf.ParseUnknownFunctions; import org.jooq.conf.ParseUnsupportedSyntax; @@ -9164,9 +9162,10 @@ final class ParserImpl implements Parser { private static final Name parseIdentifierIf(ParserContext ctx, boolean allowAposQuotes) { char quoteEnd = parseQuote(ctx, allowAposQuotes); + boolean quoted = quoteEnd != 0; int start = ctx.position(); - if (quoteEnd != 0) + if (quoted) while (ctx.character() != quoteEnd && ctx.hasMore()) ctx.positionInc(); else @@ -9176,98 +9175,22 @@ final class ParserImpl implements Parser { if (ctx.position() == start) return null; - String result = ctx.substring(start, ctx.position()); + String name = normaliseNameCase(ctx.configuration(), ctx.substring(start, ctx.position()), quoted); - switch (parseNameCase(ctx)) { - case LOWER_IF_UNQUOTED: - if (quoteEnd != 0) - break; - - // no-break - - case LOWER: - result = result.toLowerCase(renderLocale(ctx.settings())); - break; - - case UPPER_IF_UNQUOTED: - if (quoteEnd != 0) - break; - - // no-break - case UPPER: - result = result.toUpperCase(renderLocale(ctx.settings())); - break; - - case AS_IS: - case DEFAULT: - default: - // Keep result - break; - } - - if (quoteEnd != 0) { + if (quoted) { if (ctx.character() != quoteEnd) throw ctx.exception("Quoted identifier must terminate in " + quoteEnd); ctx.positionInc(); parseWhitespaceIf(ctx); - return DSL.quotedName(result); + return DSL.quotedName(name); } else { parseWhitespaceIf(ctx); - return DSL.unquotedName(result); + return DSL.unquotedName(name); } } - private static final ParseNameCase parseNameCase(ParserContext ctx) { - ParseNameCase result = defaultIfNull(ctx.settings().getParseNameCase(), ParseNameCase.DEFAULT); - - if (result == ParseNameCase.DEFAULT) { - switch (defaultIfNull(ctx.settings().getParseDialect(), ctx.family()).family()) { - - - - - - - - case POSTGRES: - return ParseNameCase.LOWER_IF_UNQUOTED; - - - - - - - - - case DERBY: - case FIREBIRD: - case H2: - case HSQLDB: - return ParseNameCase.UPPER_IF_UNQUOTED; - - - - - - - - - - case MYSQL: - case SQLITE: - return ParseNameCase.AS_IS; - - case DEFAULT: - default: - // Keep default if we don't know the case - } - } - - return result; - } - private static final char parseQuote(ParserContext ctx, boolean allowAposQuotes) { return parseIf(ctx, '"', false) ? '"' : parseIf(ctx, '`', false) ? '`' diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 400a31a4a0..671b991d9e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -67,6 +67,7 @@ import static org.jooq.conf.ParamType.NAMED; import static org.jooq.conf.ParamType.NAMED_OR_INLINED; import static org.jooq.conf.SettingsTools.getBackslashEscaping; import static org.jooq.conf.SettingsTools.reflectionCaching; +import static org.jooq.conf.SettingsTools.renderLocale; import static org.jooq.conf.SettingsTools.updatablePrimaryKeys; import static org.jooq.conf.ThrowExceptions.THROW_FIRST; import static org.jooq.conf.ThrowExceptions.THROW_NONE; @@ -151,6 +152,7 @@ import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_MATCHIN import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_GET_MATCHING_SETTERS; import static org.jooq.impl.Tools.DataCacheKey.DATA_REFLECTION_CACHE_HAS_COLUMN_ANNOTATIONS; import static org.jooq.impl.Tools.DataKey.DATA_BLOCK_NESTING; +import static org.jooq.tools.StringUtils.defaultIfNull; import static org.jooq.tools.reflect.Reflect.accessible; import java.io.Serializable; @@ -252,6 +254,7 @@ import org.jooq.UDT; import org.jooq.UDTRecord; import org.jooq.UpdatableRecord; import org.jooq.conf.BackslashEscaping; +import org.jooq.conf.ParseNameCase; import org.jooq.conf.Settings; import org.jooq.conf.SettingsTools; import org.jooq.conf.ThrowExceptions; @@ -5317,4 +5320,88 @@ final class Tools { return false; } + + /** + * Normalise a name case depending on the dialect and the setting for + * {@link ParseNameCase}. + */ + static final String normaliseNameCase(Configuration configuration, String name, boolean quoted) { + switch (parseNameCase(configuration)) { + case LOWER_IF_UNQUOTED: + if (quoted) + return name; + + // no-break + + case LOWER: + return name.toLowerCase(renderLocale(configuration.settings())); + + case UPPER_IF_UNQUOTED: + if (quoted) + return name; + + // no-break + case UPPER: + return name.toUpperCase(renderLocale(configuration.settings())); + + case AS_IS: + case DEFAULT: + default: + return name; + } + } + + /** + * Get the {@link ParseNameCase}, looking up the default value from the + * parse dialect. + */ + private static final ParseNameCase parseNameCase(Configuration configuration) { + ParseNameCase result = defaultIfNull(configuration.settings().getParseNameCase(), ParseNameCase.DEFAULT); + + if (result == ParseNameCase.DEFAULT) { + switch (defaultIfNull(configuration.settings().getParseDialect(), configuration.family()).family()) { + + + + + + + + case POSTGRES: + return ParseNameCase.LOWER_IF_UNQUOTED; + + + + + + + + + case DERBY: + case FIREBIRD: + case H2: + case HSQLDB: + return ParseNameCase.UPPER_IF_UNQUOTED; + + + + + + + + + + case MYSQL: + case SQLITE: + return ParseNameCase.AS_IS; + + case DEFAULT: + default: + // The SQL standard uses upper case identifiers if unquoted + return ParseNameCase.UPPER_IF_UNQUOTED; + } + } + + return result; + } } diff --git a/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd b/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd index 8e4abc9d9f..5f0bc340bc 100644 --- a/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd +++ b/jOOQ/src/main/resources/xsd/jooq-runtime-3.13.0.xsd @@ -391,7 +391,7 @@ jOOQ queries, for which no specific fetchSize value was specified.]]> - +