From fc791bc51f02c29a86d4bc27f785bd42c5dd62ad Mon Sep 17 00:00:00 2001 From: rusher Date: Tue, 2 Apr 2024 15:22:36 +0200 Subject: [PATCH] [CONJ-1103] support for nullDatabaseMeansCurrent (alias for nullCatalogMeansCurrent) --- .../java/org/mariadb/jdbc/Configuration.java | 30 +++++++++++++++ .../org/mariadb/jdbc/DatabaseMetaData.java | 5 ++- .../jdbc/util/options/OptionAliases.java | 1 + src/main/resources/driver.properties | 1 + .../integration/DatabaseMetadataTest.java | 38 +++++++++++++++++++ .../jdbc/unit/util/ConfigurationTest.java | 3 +- 6 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mariadb/jdbc/Configuration.java b/src/main/java/org/mariadb/jdbc/Configuration.java index 37f7685f6..686dda5e7 100644 --- a/src/main/java/org/mariadb/jdbc/Configuration.java +++ b/src/main/java/org/mariadb/jdbc/Configuration.java @@ -65,6 +65,7 @@ public class Configuration { private String timezone = null; private Boolean autocommit = null; private boolean useMysqlMetadata = false; + private boolean nullDatabaseMeansCurrent = false; private CatalogTerm useCatalogTerm = CatalogTerm.UseCatalog; private boolean createDatabaseIfNotExist = false; private boolean useLocalSessionState = false; @@ -170,6 +171,7 @@ private Configuration( String timezone, Boolean autocommit, boolean useMysqlMetadata, + boolean nullDatabaseMeansCurrent, CatalogTerm useCatalogTerm, boolean createDatabaseIfNotExist, boolean useLocalSessionState, @@ -252,6 +254,7 @@ private Configuration( this.timezone = timezone; this.autocommit = autocommit; this.useMysqlMetadata = useMysqlMetadata; + this.nullDatabaseMeansCurrent = nullDatabaseMeansCurrent; this.useCatalogTerm = useCatalogTerm; this.createDatabaseIfNotExist = createDatabaseIfNotExist; this.returnMultiValuesGeneratedIds = returnMultiValuesGeneratedIds; @@ -372,6 +375,7 @@ private Configuration( Boolean disablePipeline, Boolean autocommit, Boolean useMysqlMetadata, + Boolean nullDatabaseMeansCurrent, String useCatalogTerm, Boolean createDatabaseIfNotExist, Boolean useLocalSessionState, @@ -472,6 +476,7 @@ private Configuration( if (disablePipeline != null) this.disablePipeline = disablePipeline; if (autocommit != null) this.autocommit = autocommit; if (useMysqlMetadata != null) this.useMysqlMetadata = useMysqlMetadata; + if (nullDatabaseMeansCurrent != null) this.nullDatabaseMeansCurrent = nullDatabaseMeansCurrent; if (useCatalogTerm != null) { if (!"CATALOG".equalsIgnoreCase(useCatalogTerm) && !"SCHEMA".equalsIgnoreCase(useCatalogTerm)) { @@ -575,6 +580,7 @@ public Builder toBuilder() { .timezone(this.timezone) .autocommit(this.autocommit) .useMysqlMetadata(this.useMysqlMetadata) + .nullDatabaseMeansCurrent(this.nullDatabaseMeansCurrent) .useCatalogTerm(this.useCatalogTerm == CatalogTerm.UseCatalog ? "CATALOG" : "SCHEMA") .createDatabaseIfNotExist(this.createDatabaseIfNotExist) .useLocalSessionState(this.useLocalSessionState) @@ -1633,6 +1639,16 @@ public boolean useMysqlMetadata() { return useMysqlMetadata; } + /** + * When enable, in DatabaseMetadata, will handle null database/schema (depending on + * UseCatalog=catalog/schema) as current + * + * @return must null value be considered as current catalog/schema + */ + public boolean nullDatabaseMeansCurrent() { + return nullDatabaseMeansCurrent; + } + /** * Indicating using Catalog or Schema * @@ -1995,6 +2011,7 @@ public static final class Builder implements Cloneable { private String timezone; private Boolean autocommit; private Boolean useMysqlMetadata; + private Boolean nullDatabaseMeansCurrent; private String useCatalogTerm; private Boolean createDatabaseIfNotExist; private Boolean useLocalSessionState; @@ -2709,6 +2726,18 @@ public Builder useMysqlMetadata(Boolean useMysqlMetadata) { return this; } + /** + * Permit indicating in DatabaseMetadata if null value must be considered current schema/catalog + * + * @param nullDatabaseMeansCurrent indicating in DatabaseMetadata if null value must be + * considered current schema/catalog + * @return this {@link Builder} + */ + public Builder nullDatabaseMeansCurrent(Boolean nullDatabaseMeansCurrent) { + this.nullDatabaseMeansCurrent = nullDatabaseMeansCurrent; + return this; + } + /** * "schema" and "database" are server synonymous. Connector historically get/set database using * Connection.setCatalog()/getCatalog(), setSchema()/getSchema() being no-op This parameter @@ -3102,6 +3131,7 @@ public Configuration build() throws SQLException { this.disablePipeline, this.autocommit, this.useMysqlMetadata, + this.nullDatabaseMeansCurrent, this.useCatalogTerm, this.createDatabaseIfNotExist, this.useLocalSessionState, diff --git a/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java b/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java index b925872d2..036bc71d3 100644 --- a/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java +++ b/src/main/java/org/mariadb/jdbc/DatabaseMetaData.java @@ -597,10 +597,11 @@ private boolean databaseCond( String database, boolean usePattern) { // null database => searching without any database restriction - if (database == null || ("%".equals(database) && usePattern)) return firstCondition; + if ((database == null && !conf.nullDatabaseMeansCurrent()) + || ("%".equals(database) && usePattern)) return firstCondition; // empty database => search restricting to current database - if (database.isEmpty()) { + if ((database == null && conf.nullDatabaseMeansCurrent()) || database.isEmpty()) { sb.append(firstCondition ? " WHERE " : " AND ").append(columnName).append(" = database()"); return false; } diff --git a/src/main/java/org/mariadb/jdbc/util/options/OptionAliases.java b/src/main/java/org/mariadb/jdbc/util/options/OptionAliases.java index a78567a5f..765ed5aea 100644 --- a/src/main/java/org/mariadb/jdbc/util/options/OptionAliases.java +++ b/src/main/java/org/mariadb/jdbc/util/options/OptionAliases.java @@ -17,5 +17,6 @@ public final class OptionAliases { OPTIONS_ALIASES.put("clientcertificatekeystoreurl", "keyStore"); OPTIONS_ALIASES.put("clientcertificatekeystorepassword", "keyStorePassword"); OPTIONS_ALIASES.put("clientcertificatekeystoretype", "keyStoreType"); + OPTIONS_ALIASES.put("nullcatalogmeanscurrent", "nullDatabaseMeansCurrent"); } } diff --git a/src/main/resources/driver.properties b/src/main/resources/driver.properties index 7c835c965..21c10acf2 100644 --- a/src/main/resources/driver.properties +++ b/src/main/resources/driver.properties @@ -77,3 +77,4 @@ jdbcCompliantTruncation=If set, the connector ensures STRICT_TRANS_TABLES is alw permitRedirect=permit server redirection. Default is true fallbackToSystemKeyStore=Indicate if keystore default implementation can be used. Connector use keystore option if set or if not, use javax.net.ssl.keyStore* system properties to load keystore if this option is enable fallbackToSystemTrustStore=Indicate if truststore default implementation can be used. Connector use serverSslCert option if set or if not, use java default truststore if this option is enable +nullDatabaseMeansCurrent=When enable, in DatabaseMetadata, will handle null database/schema (depending on UseCatalog=catalog/schema) as current. (Alias nullCatalogMeansCurrent) diff --git a/src/test/java/org/mariadb/jdbc/integration/DatabaseMetadataTest.java b/src/test/java/org/mariadb/jdbc/integration/DatabaseMetadataTest.java index 010660aef..095855cc9 100644 --- a/src/test/java/org/mariadb/jdbc/integration/DatabaseMetadataTest.java +++ b/src/test/java/org/mariadb/jdbc/integration/DatabaseMetadataTest.java @@ -140,6 +140,44 @@ public void metaUnsigned() throws SQLException { public void primaryKeysTest() throws SQLException { DatabaseMetaData meta = sharedConn.getMetaData(); ResultSet rs = meta.getPrimaryKeys(sharedConn.getCatalog(), null, "dbpk_test"); + primaryKeysTest(rs); + + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + primaryKeysTest(rs); + + try (Connection con = createCon("&nullDatabaseMeansCurrent=false")) { + meta = con.getMetaData(); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + primaryKeysTest(rs); + + con.setCatalog("information_schema"); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + primaryKeysTest(rs); + } + + try (Connection con = createCon("&nullDatabaseMeansCurrent=true")) { + meta = con.getMetaData(); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + primaryKeysTest(rs); + + con.setCatalog("information_schema"); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + assertFalse(rs.next()); + } + + try (Connection con = createCon("&nullCatalogMeansCurrent=true")) { + meta = con.getMetaData(); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + primaryKeysTest(rs); + + con.setCatalog("information_schema"); + rs = meta.getPrimaryKeys(null, null, "dbpk_test"); + assertFalse(rs.next()); + } + + } + + private void primaryKeysTest(ResultSet rs) throws SQLException { Assertions.assertEquals(ResultSet.TYPE_SCROLL_INSENSITIVE, rs.getType()); Assertions.assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); diff --git a/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java b/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java index 1f827c487..f5464cf4c 100644 --- a/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java +++ b/src/test/java/org/mariadb/jdbc/unit/util/ConfigurationTest.java @@ -862,13 +862,14 @@ public void builder() throws SQLException { .createDatabaseIfNotExist(true) .disablePipeline(true) .maxAllowedPacket(8000) + .nullDatabaseMeansCurrent(true) .fallbackToSystemKeyStore(false) .fallbackToSystemTrustStore(false) .initSql("SET @@a='10'") .useCatalogTerm("schema") .build(); String expected = - "jdbc:mariadb://host1:3305,address=(host=host2)(port=3307)(type=replica)/db?user=me&password=***&timezone=UTC&autocommit=false&useCatalogTerm=SCHEMA&createDatabaseIfNotExist=true&useLocalSessionState=true&returnMultiValuesGeneratedIds=true&permitRedirect=false&transactionIsolation=REPEATABLE_READ&defaultFetchSize=10&maxQuerySizeToLog=100&maxAllowedPacket=8000&geometryDefaultType=default&restrictedAuth=mysql_native_password,client_ed25519&initSql=SET" + "jdbc:mariadb://host1:3305,address=(host=host2)(port=3307)(type=replica)/db?user=me&password=***&timezone=UTC&autocommit=false&nullDatabaseMeansCurrent=true&useCatalogTerm=SCHEMA&createDatabaseIfNotExist=true&useLocalSessionState=true&returnMultiValuesGeneratedIds=true&permitRedirect=false&transactionIsolation=REPEATABLE_READ&defaultFetchSize=10&maxQuerySizeToLog=100&maxAllowedPacket=8000&geometryDefaultType=default&restrictedAuth=mysql_native_password,client_ed25519&initSql=SET" + " @@a='10'&socketFactory=someSocketFactory&connectTimeout=22&pipe=pipeName&localSocket=localSocket&uuidAsString=true&tcpKeepAlive=false&tcpKeepIdle=10&tcpKeepCount=50&tcpKeepInterval=50&tcpAbortiveClose=true&localSocketAddress=localSocketAddress&socketTimeout=1000&useReadAheadInput=true&tlsSocketType=TLStype&sslMode=TRUST&serverSslCert=mycertPath&keyStore=/tmp&keyStorePassword=MyPWD&keyStoreType=JKS&trustStoreType=JKS&enabledSslCipherSuites=myCipher,cipher2&enabledSslProtocolSuites=TLSv1.2&fallbackToSystemKeyStore=false&fallbackToSystemTrustStore=false&allowMultiQueries=true&allowLocalInfile=false&useCompression=true&useAffectedRows=true&useBulkStmts=true&disablePipeline=true&cachePrepStmts=false&prepStmtCacheSize=2&useServerPrepStmts=true&credentialType=ENV&sessionVariables=blabla&connectionAttributes=bla=bla&servicePrincipalName=SPN&blankTableNameMeta=true&tinyInt1isBit=false&yearIsDateType=false&dumpQueriesOnException=true&includeInnodbStatusInDeadlockExceptions=true&includeThreadDumpInDeadlockExceptions=true&retriesAllDown=10&galeraAllowedState=A,B&transactionReplay=true&pool=true&poolName=myPool&maxPoolSize=16&minPoolSize=12&maxIdleTime=25000®isterJmxPool=false&poolValidMinDelay=260&useResetConnection=true&serverRsaPublicKeyFile=RSAPath&allowPublicKeyRetrieval=true"; assertEquals(expected, conf.toString()); assertEquals(expected, conf.toBuilder().build().toString());