Skip to content

Commit

Permalink
[SQLLINE-284] Use proxy for DatabaseMetaData
Browse files Browse the repository at this point in the history
Obsoletes the previous DatabaseMetaDataWrapper, which had a similar
purpose but did not implement the java.sql.DatabaseMetaData interface.

Use an enum rather than a string map for methods, so that we know
immediately when the class loads if we have mis-typed a method name.
(Julian Hyde)
  • Loading branch information
snuyanzin authored and julianhyde committed Apr 2, 2019
1 parent 196b28a commit b0cdb42
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 921 deletions.
6 changes: 3 additions & 3 deletions src/main/java/sqlline/Commands.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ public void metadata(
}

try {
Method[] methods =
sqlLine.getDatabaseMetaData().getClass().getDeclaredMethods();
Method[] methods = sqlLine.getConnection()
.getMetaData().getClass().getDeclaredMethods();
Set<String> methodNames = new TreeSet<>();
Set<String> methodNamesUpper = new TreeSet<>();
for (Method method : methods) {
Expand All @@ -215,7 +215,7 @@ public void metadata(
}

final Object res = sqlLine.getReflector()
.invoke(sqlLine.getDatabaseMetaData(), DatabaseMetaDataWrapper.class,
.invoke(sqlLine.getDatabaseMetaData(), DatabaseMetaData.class,
cmd, argList);
if (res instanceof ResultSet) {
try (ResultSet rs = (ResultSet) res) {
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/sqlline/DatabaseConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/
package sqlline;

import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;
Expand All @@ -25,7 +26,7 @@
class DatabaseConnection {
private final SqlLine sqlLine;
Connection connection;
DatabaseMetaDataWrapper meta;
DatabaseMetaData meta;
private final String driver;
private final String url;
private final Properties info;
Expand Down Expand Up @@ -128,8 +129,10 @@ boolean connect() throws SQLException {
// Instead, we use the driver instance to make the connection

connection = theDriver.connect(url, info);
meta = new DatabaseMetaDataWrapper(sqlLine, connection.getMetaData());

meta = (DatabaseMetaData) Proxy.newProxyInstance(
DatabaseMetaData.class.getClassLoader(),
new Class[] {DatabaseMetaData.class},
new DatabaseMetaDataHandler(connection.getMetaData()));
try {
sqlLine.debug(
sqlLine.loc("connected",
Expand Down Expand Up @@ -219,7 +222,7 @@ Schema getSchema() {
return schema;
}

DatabaseMetaDataWrapper getDatabaseMetaData() {
DatabaseMetaData getDatabaseMetaData() {
return meta;
}

Expand Down
228 changes: 228 additions & 0 deletions src/main/java/sqlline/DatabaseMetaDataHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde licenses this file to you under the Modified BSD License
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/BSD-3-Clause
*/
package sqlline;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.DatabaseMetaData;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
* Invocation handler for {@link DatabaseMetaData}, to ensure that sqlline
* does not fail in case the connection's JDBC driver
* does not implement {@link DatabaseMetaData} correctly.
*/
class DatabaseMetaDataHandler implements InvocationHandler {
private final DatabaseMetaData metaData;

DatabaseMetaDataHandler(DatabaseMetaData metaData) {
this.metaData = Objects.requireNonNull(metaData);
}

@Override public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
return method.invoke(metaData, args);
} catch (Throwable e) {
final MethodWithDefault methodWithDefault =
MethodWithDefault.lookup(method);
if (methodWithDefault != null) {
return methodWithDefault.defaultValue;
}
throw e;
}
}

/** Defines all methods of {@link DatabaseMetaData} for which we wish to
* provide a default value.
*
* <p>The name of each enum member corresponds to the method name. We assume
* that there are no overloaded methods. */
private enum MethodWithDefault {
// the list is ordered
allProceduresAreCallable(false),
allTablesAreSelectable(false),
autoCommitFailureClosesAllResultSets(false),
dataDefinitionCausesTransactionCommit(false),
deletesAreDetected(false, int.class),
dataDefinitionIgnoredInTransactions(false),
doesMaxRowSizeIncludeBlobs(false),
generatedKeyAlwaysReturned(false),
getDatabaseProductName(null),
getIdentifierQuoteString(" "),
getMaxBinaryLiteralLength(0),
getMaxCatalogNameLength(0),
getMaxCharLiteralLength(0),
getMaxColumnNameLength(0),
getMaxColumnsInGroupBy(0),
getMaxColumnsInIndex(0),
getMaxColumnsInOrderBy(0),
getMaxColumnsInSelect(0),
getMaxColumnsInTable(0),
getMaxConnections(0),
getMaxCursorNameLength(0),
getMaxLogicalLobSize(0),
getMaxIndexLength(0),
getMaxProcedureNameLength(0),
getMaxRowSize(0),
getMaxSchemaNameLength(0),
getMaxStatementLength(0),
getMaxStatements(0),
getMaxTableNameLength(0),
getMaxTablesInSelect(0),
getMaxUserNameLength(0),
getNumericFunctions(""),
getStringFunctions(""),
getSQLKeywords(""),
getSystemFunctions(""),
getTimeDateFunctions(""),
getURL(null),
insertsAreDetected(false, int.class),
isCatalogAtStart(false),
isReadOnly(false),
nullPlusNonNullIsNull(false),
nullsAreSortedHigh(false),
nullsAreSortedLow(false),
nullsAreSortedAtStart(false),
nullsAreSortedAtEnd(false),
othersDeletesAreVisible(false, int.class),
othersInsertsAreVisible(false, int.class),
othersUpdatesAreVisible(false, int.class),
ownDeletesAreVisible(false, int.class),
ownInsertsAreVisible(false, int.class),
ownUpdatesAreVisible(false, int.class),
storesLowerCaseIdentifiers(false),
storesLowerCaseQuotedIdentifiers(false),
storesMixedCaseIdentifiers(false),
storesMixedCaseQuotedIdentifiers(false),
storesUpperCaseIdentifiers(false),
storesUpperCaseQuotedIdentifiers(false),
supportsAlterTableWithAddColumn(false),
supportsAlterTableWithDropColumn(false),
supportsANSI92EntryLevelSQL(false),
supportsANSI92FullSQL(false),
supportsANSI92IntermediateSQL(false),
supportsBatchUpdates(false),
supportsCatalogsInDataManipulation(false),
supportsCatalogsInIndexDefinitions(false),
supportsCatalogsInPrivilegeDefinitions(false),
supportsCatalogsInProcedureCalls(false),
supportsCatalogsInTableDefinitions(false),
supportsColumnAliasing(false),
supportsConvert(false),
supportsCoreSQLGrammar(false),
supportsCorrelatedSubqueries(false),
supportsDataDefinitionAndDataManipulationTransactions(false),
supportsDataManipulationTransactionsOnly(false),
supportsExtendedSQLGrammar(false),
supportsGroupBy(false),
supportsFullOuterJoins(false),
supportsGetGeneratedKeys(false),
supportsGroupByBeyondSelect(false),
supportsGroupByUnrelated(false),
supportsIntegrityEnhancementFacility(false),
supportsLikeEscapeClause(false),
supportsLimitedOuterJoins(false),
supportsMinimumSQLGrammar(false),
supportsMixedCaseIdentifiers(false),
supportsMultipleOpenResults(false),
supportsMultipleResultSets(false),
supportsMultipleTransactions(false),
supportsDifferentTableCorrelationNames(false),
supportsExpressionsInOrderBy(false),
supportsMixedCaseQuotedIdentifiers(false),
supportsNamedParameters(false),
supportsNonNullableColumns(false),
supportsOrderByUnrelated(false),
supportsOuterJoins(false),
supportsPositionedDelete(false),
supportsPositionedUpdate(false),
supportsRefCursors(false),
supportsResultSetConcurrency(false, int.class, int.class),
supportsResultSetHoldability(false, int.class),
supportsResultSetType(false, int.class),
supportsSavepoints(false),
supportsSchemasInDataManipulation(false),
supportsSchemasInIndexDefinitions(false),
supportsSchemasInPrivilegeDefinitions(false),
supportsSchemasInProcedureCalls(false),
supportsSchemasInTableDefinitions(false),
supportsSelectForUpdate(false),
supportsStatementPooling(false),
supportsStoredFunctionsUsingCallSyntax(false),
supportsStoredProcedures(false),
supportsSubqueriesInComparisons(false),
supportsSubqueriesInExists(false),
supportsSubqueriesInIns(false),
supportsSubqueriesInQuantifieds(false),
supportsTableCorrelationNames(false),
supportsTransactionIsolationLevel(false, int.class),
supportsTransactions(false),
supportsUnion(false),
supportsUnionAll(false),
updatesAreDetected(false, int.class),
usesLocalFiles(false),
usesLocalFilePerTable(false);

private final Method method;
/** May be null, which means that null is the default value. */
private final Object defaultValue;

private static final Map<String, MethodWithDefault> MAP = new HashMap<>();

static {
for (MethodWithDefault methodWithDefault : MethodWithDefault.values()) {
MAP.put(methodWithDefault.method.getName(), methodWithDefault);
}
}

MethodWithDefault(Object defaultValue, Class... parameterTypes) {
this.method = Objects.requireNonNull(findMethod(name(), parameterTypes));
this.defaultValue = defaultValue;
}

static MethodWithDefault lookup(Method method) {
return MAP.get(method.getName());
}

/** Looks up a method of {@link DatabaseMetaData}.
*
* <p>Throws {@link AssertionError} if method is not found.
*
* <p>Note: In future we may allow defining a default for a method that is
* in some but not all JDK versions. For example, suppose that
* {@code supportsFoo} is introduced in JDK 13 but we are running JDK 10.
* This method will not find method {@code supportsFoo}, and a call to it
* will not occur at runtime. But it is not an error, so rather than
* failing, this method should return a dummy method.
*
* @param methodName Method name
* @param parameterTypes Parameter types
* @return Method, never null
*/
private static Method findMethod(String methodName,
Class... parameterTypes) {
try {
return DatabaseMetaData.class.getDeclaredMethod(methodName,
parameterTypes);
} catch (NoSuchMethodException e) {
throw new AssertionError("not found: " + methodName
+ Arrays.asList(parameterTypes), e);
}
}
}
}

// End DatabaseMetaDataHandler.java
Loading

0 comments on commit b0cdb42

Please sign in to comment.