diff --git a/src/test/java/com/mongodb/hibernate/SessionFactoryTests.java b/src/integrationTest/java/com/mongodb/hibernate/SessionFactoryTests.java similarity index 100% rename from src/test/java/com/mongodb/hibernate/SessionFactoryTests.java rename to src/integrationTest/java/com/mongodb/hibernate/SessionFactoryTests.java diff --git a/src/test/java/com/mongodb/hibernate/SessionTests.java b/src/integrationTest/java/com/mongodb/hibernate/SessionTests.java similarity index 100% rename from src/test/java/com/mongodb/hibernate/SessionTests.java rename to src/integrationTest/java/com/mongodb/hibernate/SessionTests.java diff --git a/src/integrationTest/resources/logback-test.xml b/src/integrationTest/resources/logback-test.xml index 9719b07c..26b493f0 100644 --- a/src/integrationTest/resources/logback-test.xml +++ b/src/integrationTest/resources/logback-test.xml @@ -7,6 +7,7 @@ + diff --git a/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java b/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java index 39018105..0d383cf2 100644 --- a/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java +++ b/src/main/java/com/mongodb/hibernate/dialect/MongoDialect.java @@ -31,16 +31,6 @@ public final class MongoDialect extends Dialect { private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make(6); - /** Default constructor used when no version info is available. */ - public MongoDialect() { - super((DatabaseVersion) null); - } - - /** - * Constructor used when MongoDB meta data is available. - * - * @param info MongoDB meta data - */ public MongoDialect(DialectResolutionInfo info) { super(info); } diff --git a/src/main/java/com/mongodb/hibernate/internal/MongoAssertions.java b/src/main/java/com/mongodb/hibernate/internal/MongoAssertions.java index e4235225..bf43d39b 100644 --- a/src/main/java/com/mongodb/hibernate/internal/MongoAssertions.java +++ b/src/main/java/com/mongodb/hibernate/internal/MongoAssertions.java @@ -53,4 +53,18 @@ public static T assertNotNull(@Nullable T value) throws AssertionError { public static AssertionError fail(String msg) throws AssertionError { throw new AssertionError(assertNotNull(msg)); } + + /** + * Asserts that {@code value} is {@code true}. + * + * @param value A value to check. + * @return {@code true}. + * @throws AssertionError If {@code value} is {@code false}. + */ + public static boolean assertTrue(boolean value) throws AssertionError { + if (!value) { + throw new AssertionError(); + } + return true; + } } diff --git a/src/main/java/com/mongodb/hibernate/jdbc/DatabaseMetaDataAdapter.java b/src/main/java/com/mongodb/hibernate/jdbc/DatabaseMetaDataAdapter.java new file mode 100644 index 00000000..9a5ac0a3 --- /dev/null +++ b/src/main/java/com/mongodb/hibernate/jdbc/DatabaseMetaDataAdapter.java @@ -0,0 +1,939 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.hibernate.jdbc; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; + +/** + * A {@link java.sql.DatabaseMetaData} adapter interface that throws exceptions for all its API methods. + * + * @see MongoDatabaseMetaData + */ +interface DatabaseMetaDataAdapter extends DatabaseMetaData { + + @Override + default T unwrap(Class unwrapType) throws SQLException { + throw new SQLFeatureNotSupportedException("unwrap not implemented"); + } + + @Override + default boolean isWrapperFor(Class unwrapType) throws SQLException { + throw new SQLFeatureNotSupportedException("isWrapperFor not implemented"); + } + + @Override + default boolean allProceduresAreCallable() throws SQLException { + throw new SQLFeatureNotSupportedException("allProceduresAreCallable not implemented"); + } + + @Override + default boolean allTablesAreSelectable() throws SQLException { + throw new SQLFeatureNotSupportedException("allTablesAreSelectable not implemented"); + } + + @Override + default String getURL() throws SQLException { + throw new SQLFeatureNotSupportedException("getURL not implemented"); + } + + @Override + default String getUserName() throws SQLException { + throw new SQLFeatureNotSupportedException("getUserName not implemented"); + } + + @Override + default boolean isReadOnly() throws SQLException { + throw new SQLFeatureNotSupportedException("isReadOnly not implemented"); + } + + @Override + default boolean nullsAreSortedHigh() throws SQLException { + throw new SQLFeatureNotSupportedException("nullsAreSortedHigh not implemented"); + } + + @Override + default boolean nullsAreSortedLow() throws SQLException { + throw new SQLFeatureNotSupportedException("nullsAreSortedLow not implemented"); + } + + @Override + default boolean nullsAreSortedAtStart() throws SQLException { + throw new SQLFeatureNotSupportedException("nullsAreSortedAtStart not implemented"); + } + + @Override + default boolean nullsAreSortedAtEnd() throws SQLException { + throw new SQLFeatureNotSupportedException("nullsAreSortedAtEnd not implemented"); + } + + @Override + default String getDatabaseProductName() throws SQLException { + throw new SQLFeatureNotSupportedException("getDatabaseProductName not implemented"); + } + + @Override + default String getDatabaseProductVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getDatabaseProductVersion not implemented"); + } + + @Override + default String getDriverName() throws SQLException { + throw new SQLFeatureNotSupportedException("getDriverName not implemented"); + } + + @Override + default String getDriverVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getDriverVersion not implemented"); + } + + @Override + default int getDriverMajorVersion() { + throw new RuntimeException("getDriverMajorVersion not implemented"); + } + + @Override + default int getDriverMinorVersion() { + throw new RuntimeException("getDriverMinorVersion not implemented"); + } + + @Override + default boolean usesLocalFiles() throws SQLException { + throw new SQLFeatureNotSupportedException("usesLocalFiles not implemented"); + } + + @Override + default boolean usesLocalFilePerTable() throws SQLException { + throw new SQLFeatureNotSupportedException("usesLocalFilePerTable not implemented"); + } + + @Override + default boolean supportsMixedCaseIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMixedCaseIdentifiers not implemented"); + } + + @Override + default boolean storesUpperCaseIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesUpperCaseIdentifiers not implemented"); + } + + @Override + default boolean storesLowerCaseIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesLowerCaseIdentifiers not implemented"); + } + + @Override + default boolean storesMixedCaseIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesMixedCaseIdentifiers not implemented"); + } + + @Override + default boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMixedCaseQuotedIdentifiers not implemented"); + } + + @Override + default boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesUpperCaseQuotedIdentifiers not implemented"); + } + + @Override + default boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesLowerCaseQuotedIdentifiers not implemented"); + } + + @Override + default boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + throw new SQLFeatureNotSupportedException("storesMixedCaseQuotedIdentifiers not implemented"); + } + + @Override + default String getIdentifierQuoteString() throws SQLException { + throw new SQLFeatureNotSupportedException("getIdentifierQuoteString not implemented"); + } + + @Override + default String getSQLKeywords() throws SQLException { + throw new SQLFeatureNotSupportedException("getSQLKeywords not implemented"); + } + + @Override + default String getNumericFunctions() throws SQLException { + throw new SQLFeatureNotSupportedException("getNumericFunctions not implemented"); + } + + @Override + default String getStringFunctions() throws SQLException { + throw new SQLFeatureNotSupportedException("getStringFunctions not implemented"); + } + + @Override + default String getSystemFunctions() throws SQLException { + throw new SQLFeatureNotSupportedException("getSystemFunctions not implemented"); + } + + @Override + default String getTimeDateFunctions() throws SQLException { + throw new SQLFeatureNotSupportedException("getTimeDateFunctions not implemented"); + } + + @Override + default String getSearchStringEscape() throws SQLException { + throw new SQLFeatureNotSupportedException("getSearchStringEscape not implemented"); + } + + @Override + default String getExtraNameCharacters() throws SQLException { + throw new SQLFeatureNotSupportedException("getExtraNameCharacters not implemented"); + } + + @Override + default boolean supportsAlterTableWithAddColumn() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsAlterTableWithAddColumn not implemented"); + } + + @Override + default boolean supportsAlterTableWithDropColumn() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsAlterTableWithDropColumn not implemented"); + } + + @Override + default boolean supportsColumnAliasing() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsColumnAliasing not implemented"); + } + + @Override + default boolean nullPlusNonNullIsNull() throws SQLException { + throw new SQLFeatureNotSupportedException("nullPlusNonNullIsNull not implemented"); + } + + @Override + default boolean supportsConvert() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsConvert not implemented"); + } + + @Override + default boolean supportsConvert(int fromType, int toType) throws SQLException { + throw new SQLFeatureNotSupportedException("supportsConvert not implemented"); + } + + @Override + default boolean supportsTableCorrelationNames() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsTableCorrelationNames not implemented"); + } + + @Override + default boolean supportsDifferentTableCorrelationNames() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsDifferentTableCorrelationNames not implemented"); + } + + @Override + default boolean supportsExpressionsInOrderBy() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsExpressionsInOrderBy not implemented"); + } + + @Override + default boolean supportsOrderByUnrelated() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOrderByUnrelated not implemented"); + } + + @Override + default boolean supportsGroupBy() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsGroupBy not implemented"); + } + + @Override + default boolean supportsGroupByUnrelated() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsGroupByUnrelated not implemented"); + } + + @Override + default boolean supportsGroupByBeyondSelect() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsGroupByBeyondSelect not implemented"); + } + + @Override + default boolean supportsLikeEscapeClause() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsLikeEscapeClause not implemented"); + } + + @Override + default boolean supportsMultipleResultSets() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMultipleResultSets not implemented"); + } + + @Override + default boolean supportsMultipleTransactions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMultipleTransactions not implemented"); + } + + @Override + default boolean supportsNonNullableColumns() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsNonNullableColumns not implemented"); + } + + @Override + default boolean supportsMinimumSQLGrammar() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMinimumSQLGrammar not implemented"); + } + + @Override + default boolean supportsCoreSQLGrammar() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCoreSQLGrammar not implemented"); + } + + @Override + default boolean supportsExtendedSQLGrammar() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsExtendedSQLGrammar not implemented"); + } + + @Override + default boolean supportsANSI92EntryLevelSQL() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsANSI92EntryLevelSQL not implemented"); + } + + @Override + default boolean supportsANSI92IntermediateSQL() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsANSI92IntermediateSQL not implemented"); + } + + @Override + default boolean supportsANSI92FullSQL() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsANSI92FullSQL not implemented"); + } + + @Override + default boolean supportsIntegrityEnhancementFacility() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsIntegrityEnhancementFacility not implemented"); + } + + @Override + default boolean supportsOuterJoins() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOuterJoins not implemented"); + } + + @Override + default boolean supportsFullOuterJoins() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsFullOuterJoins not implemented"); + } + + @Override + default boolean supportsLimitedOuterJoins() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsLimitedOuterJoins not implemented"); + } + + @Override + default String getSchemaTerm() throws SQLException { + throw new SQLFeatureNotSupportedException("getSchemaTerm not implemented"); + } + + @Override + default String getProcedureTerm() throws SQLException { + throw new SQLFeatureNotSupportedException("getProcedureTerm not implemented"); + } + + @Override + default String getCatalogTerm() throws SQLException { + throw new SQLFeatureNotSupportedException("getCatalogTerm not implemented"); + } + + @Override + default boolean isCatalogAtStart() throws SQLException { + throw new SQLFeatureNotSupportedException("isCatalogAtStart not implemented"); + } + + @Override + default String getCatalogSeparator() throws SQLException { + throw new SQLFeatureNotSupportedException("getCatalogSeparator not implemented"); + } + + @Override + default boolean supportsSchemasInDataManipulation() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSchemasInDataManipulation not implemented"); + } + + @Override + default boolean supportsSchemasInProcedureCalls() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSchemasInProcedureCalls not implemented"); + } + + @Override + default boolean supportsSchemasInTableDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSchemasInTableDefinitions not implemented"); + } + + @Override + default boolean supportsSchemasInIndexDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSchemasInIndexDefinitions not implemented"); + } + + @Override + default boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSchemasInPrivilegeDefinitions not implemented"); + } + + @Override + default boolean supportsCatalogsInDataManipulation() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCatalogsInDataManipulation not implemented"); + } + + @Override + default boolean supportsCatalogsInProcedureCalls() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCatalogsInProcedureCalls not implemented"); + } + + @Override + default boolean supportsCatalogsInTableDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCatalogsInTableDefinitions not implemented"); + } + + @Override + default boolean supportsCatalogsInIndexDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCatalogsInIndexDefinitions not implemented"); + } + + @Override + default boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCatalogsInPrivilegeDefinitions not implemented"); + } + + @Override + default boolean supportsPositionedDelete() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsPositionedDelete not implemented"); + } + + @Override + default boolean supportsPositionedUpdate() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsPositionedUpdate not implemented"); + } + + @Override + default boolean supportsSelectForUpdate() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSelectForUpdate not implemented"); + } + + @Override + default boolean supportsStoredProcedures() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsStoredProcedures not implemented"); + } + + @Override + default boolean supportsSubqueriesInComparisons() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSubqueriesInComparisons not implemented"); + } + + @Override + default boolean supportsSubqueriesInExists() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSubqueriesInExists not implemented"); + } + + @Override + default boolean supportsSubqueriesInIns() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSubqueriesInIns not implemented"); + } + + @Override + default boolean supportsSubqueriesInQuantifieds() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSubqueriesInQuantifieds not implemented"); + } + + @Override + default boolean supportsCorrelatedSubqueries() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsCorrelatedSubqueries not implemented"); + } + + @Override + default boolean supportsUnion() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsUnion not implemented"); + } + + @Override + default boolean supportsUnionAll() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsUnionAll not implemented"); + } + + @Override + default boolean supportsOpenCursorsAcrossCommit() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOpenCursorsAcrossCommit not implemented"); + } + + @Override + default boolean supportsOpenCursorsAcrossRollback() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOpenCursorsAcrossRollback not implemented"); + } + + @Override + default boolean supportsOpenStatementsAcrossCommit() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOpenStatementsAcrossCommit not implemented"); + } + + @Override + default boolean supportsOpenStatementsAcrossRollback() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsOpenStatementsAcrossRollback not implemented"); + } + + @Override + default int getMaxBinaryLiteralLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxBinaryLiteralLength not implemented"); + } + + @Override + default int getMaxCharLiteralLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxCharLiteralLength not implemented"); + } + + @Override + default int getMaxColumnNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnNameLength not implemented"); + } + + @Override + default int getMaxColumnsInGroupBy() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnsInGroupBy not implemented"); + } + + @Override + default int getMaxColumnsInIndex() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnsInIndex not implemented"); + } + + @Override + default int getMaxColumnsInOrderBy() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnsInOrderBy not implemented"); + } + + @Override + default int getMaxColumnsInSelect() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnsInSelect not implemented"); + } + + @Override + default int getMaxColumnsInTable() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxColumnsInTable not implemented"); + } + + @Override + default int getMaxConnections() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxConnections not implemented"); + } + + @Override + default int getMaxCursorNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxCursorNameLength not implemented"); + } + + @Override + default int getMaxIndexLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxIndexLength not implemented"); + } + + @Override + default int getMaxSchemaNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxSchemaNameLength not implemented"); + } + + @Override + default int getMaxProcedureNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxProcedureNameLength not implemented"); + } + + @Override + default int getMaxCatalogNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxCatalogNameLength not implemented"); + } + + @Override + default int getMaxRowSize() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxRowSize not implemented"); + } + + @Override + default boolean doesMaxRowSizeIncludeBlobs() throws SQLException { + throw new SQLFeatureNotSupportedException("doesMaxRowSizeIncludeBlobs not implemented"); + } + + @Override + default int getMaxStatementLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxStatementLength not implemented"); + } + + @Override + default int getMaxStatements() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxStatements not implemented"); + } + + @Override + default int getMaxTableNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxTableNameLength not implemented"); + } + + @Override + default int getMaxTablesInSelect() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxTablesInSelect not implemented"); + } + + @Override + default int getMaxUserNameLength() throws SQLException { + throw new SQLFeatureNotSupportedException("getMaxUserNameLength not implemented"); + } + + @Override + default int getDefaultTransactionIsolation() throws SQLException { + throw new SQLFeatureNotSupportedException("getDefaultTransactionIsolation not implemented"); + } + + @Override + default boolean supportsTransactions() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsTransactions not implemented"); + } + + @Override + default boolean supportsTransactionIsolationLevel(int level) throws SQLException { + throw new SQLFeatureNotSupportedException("supportsTransactionIsolationLevel not implemented"); + } + + @Override + default boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { + throw new SQLFeatureNotSupportedException( + "supportsDataDefinitionAndDataManipulationTransactions not implemented"); + } + + @Override + default boolean supportsDataManipulationTransactionsOnly() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsDataManipulationTransactionsOnly not implemented"); + } + + @Override + default boolean dataDefinitionCausesTransactionCommit() throws SQLException { + throw new SQLFeatureNotSupportedException("dataDefinitionCausesTransactionCommit not implemented"); + } + + @Override + default boolean dataDefinitionIgnoredInTransactions() throws SQLException { + throw new SQLFeatureNotSupportedException("dataDefinitionIgnoredInTransactions not implemented"); + } + + @Override + default ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getProcedures not implemented"); + } + + @Override + default ResultSet getProcedureColumns( + String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getProcedureColumns not implemented"); + } + + @Override + default ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + throws SQLException { + throw new SQLFeatureNotSupportedException("getTables not implemented"); + } + + @Override + default ResultSet getSchemas() throws SQLException { + throw new SQLFeatureNotSupportedException("getSchemas not implemented"); + } + + @Override + default ResultSet getCatalogs() throws SQLException { + throw new SQLFeatureNotSupportedException("getCatalogs not implemented"); + } + + @Override + default ResultSet getTableTypes() throws SQLException { + throw new SQLFeatureNotSupportedException("getTableTypes not implemented"); + } + + @Override + default ResultSet getColumns( + String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getColumns not implemented"); + } + + @Override + default ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getColumnPrivileges not implemented"); + } + + @Override + default ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getTablePrivileges not implemented"); + } + + @Override + default ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException { + throw new SQLFeatureNotSupportedException("getBestRowIdentifier not implemented"); + } + + @Override + default ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getVersionColumns not implemented"); + } + + @Override + default ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getPrimaryKeys not implemented"); + } + + @Override + default ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getImportedKeys not implemented"); + } + + @Override + default ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getExportedKeys not implemented"); + } + + @Override + default ResultSet getCrossReference( + String parentCatalog, + String parentSchema, + String parentTable, + String foreignCatalog, + String foreignSchema, + String foreignTable) + throws SQLException { + throw new SQLFeatureNotSupportedException("getCrossReference not implemented"); + } + + @Override + default ResultSet getTypeInfo() throws SQLException { + throw new SQLFeatureNotSupportedException("getTypeInfo not implemented"); + } + + @Override + default ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) + throws SQLException { + throw new SQLFeatureNotSupportedException("getIndexInfo not implemented"); + } + + @Override + default boolean supportsResultSetType(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("supportsResultSetType not implemented"); + } + + @Override + default boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { + throw new SQLFeatureNotSupportedException("supportsResultSetConcurrency not implemented"); + } + + @Override + default boolean ownUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownUpdatesAreVisible not implemented"); + } + + @Override + default boolean ownDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownDeletesAreVisible not implemented"); + } + + @Override + default boolean ownInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownInsertsAreVisible not implemented"); + } + + @Override + default boolean othersUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersUpdatesAreVisible not implemented"); + } + + @Override + default boolean othersDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersDeletesAreVisible not implemented"); + } + + @Override + default boolean othersInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersInsertsAreVisible not implemented"); + } + + @Override + default boolean updatesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("updatesAreDetected not implemented"); + } + + @Override + default boolean deletesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("deletesAreDetected not implemented"); + } + + @Override + default boolean insertsAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("insertsAreDetected not implemented"); + } + + @Override + default boolean supportsBatchUpdates() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsBatchUpdates not implemented"); + } + + @Override + default ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) + throws SQLException { + throw new SQLFeatureNotSupportedException("getUDTs not implemented"); + } + + @Override + default Connection getConnection() throws SQLException { + throw new SQLFeatureNotSupportedException("getConnection not implemented"); + } + + @Override + default boolean supportsSavepoints() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsSavepoints not implemented"); + } + + @Override + default boolean supportsNamedParameters() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsNamedParameters not implemented"); + } + + @Override + default boolean supportsMultipleOpenResults() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsMultipleOpenResults not implemented"); + } + + @Override + default boolean supportsGetGeneratedKeys() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsGetGeneratedKeys not implemented"); + } + + @Override + default ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTypes not implemented"); + } + + @Override + default ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTables not implemented"); + } + + @Override + default ResultSet getAttributes( + String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getAttributes not implemented"); + } + + @Override + default boolean supportsResultSetHoldability(int holdability) throws SQLException { + throw new SQLFeatureNotSupportedException("supportsResultSetHoldability not implemented"); + } + + @Override + default int getResultSetHoldability() throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSetHoldability not implemented"); + } + + @Override + default int getDatabaseMajorVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getDatabaseMajorVersion not implemented"); + } + + @Override + default int getDatabaseMinorVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getDatabaseMinorVersion not implemented"); + } + + @Override + default int getJDBCMajorVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getJDBCMajorVersion not implemented"); + } + + @Override + default int getJDBCMinorVersion() throws SQLException { + throw new SQLFeatureNotSupportedException("getJDBCMinorVersion not implemented"); + } + + @Override + default int getSQLStateType() throws SQLException { + throw new SQLFeatureNotSupportedException("getSQLStateType not implemented"); + } + + @Override + default boolean locatorsUpdateCopy() throws SQLException { + throw new SQLFeatureNotSupportedException("locatorsUpdateCopy not implemented"); + } + + @Override + default boolean supportsStatementPooling() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsStatementPooling not implemented"); + } + + @Override + default RowIdLifetime getRowIdLifetime() throws SQLException { + throw new SQLFeatureNotSupportedException("getRowIdLifetime not implemented"); + } + + @Override + default ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSchemas not implemented"); + } + + @Override + default boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { + throw new SQLFeatureNotSupportedException("supportsStoredFunctionsUsingCallSyntax not implemented"); + } + + @Override + default boolean autoCommitFailureClosesAllResultSets() throws SQLException { + throw new SQLFeatureNotSupportedException("autoCommitFailureClosesAllResultSets not implemented"); + } + + @Override + default ResultSet getClientInfoProperties() throws SQLException { + throw new SQLFeatureNotSupportedException("getClientInfoProperties not implemented"); + } + + @Override + default ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getFunctions not implemented"); + } + + @Override + default ResultSet getFunctionColumns( + String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getFunctionColumns not implemented"); + } + + @Override + default ResultSet getPseudoColumns( + String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getPseudoColumns not implemented"); + } + + @Override + default boolean generatedKeyAlwaysReturned() throws SQLException { + throw new SQLFeatureNotSupportedException("generatedKeyAlwaysReturned not implemented"); + } +} diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoConnection.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoConnection.java index 7027e307..4c2458e5 100644 --- a/src/main/java/com/mongodb/hibernate/jdbc/MongoConnection.java +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoConnection.java @@ -16,8 +16,12 @@ package com.mongodb.hibernate.jdbc; +import static com.mongodb.hibernate.internal.MongoAssertions.assertNotNull; +import static java.lang.String.format; + import com.mongodb.client.ClientSession; import com.mongodb.client.MongoClient; +import com.mongodb.hibernate.BuildConfig; import com.mongodb.hibernate.internal.NotYetImplementedException; import java.sql.Array; import java.sql.DatabaseMetaData; @@ -26,7 +30,11 @@ import java.sql.SQLWarning; import java.sql.Statement; import java.sql.Struct; +import org.bson.BsonDocument; +import org.bson.BsonInt32; import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * MongoDB Dialect's JDBC {@linkplain java.sql.Connection connection} implementation class. @@ -36,6 +44,8 @@ */ final class MongoConnection implements ConnectionAdapter { + private final Logger logger = LoggerFactory.getLogger(MongoConnection.class); + // temporary hard-coded database prior to the db config tech design finalizing public static final String DATABASE = "mongo-hibernate-test"; @@ -171,8 +181,25 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep @Override public DatabaseMetaData getMetaData() throws SQLException { checkClosed(); - throw new NotYetImplementedException( - "To be implemented in scope of https://jira.mongodb.org/browse/HIBERNATE-37"); + try { + var commandResult = mongoClient + .getDatabase("admin") + .runCommand(clientSession, new BsonDocument("buildinfo", new BsonInt32(1))); + var versionText = commandResult.getString("version"); + var versionArray = commandResult.getList("versionArray", Integer.class); + if (versionArray.size() < 2) { + throw new SQLException( + format("Unexpected versionArray [%s] field length (should be 2 or more)", versionArray)); + } + return new MongoDatabaseMetaData( + this, versionText, versionArray.get(0), versionArray.get(1), assertNotNull(BuildConfig.VERSION)); + } catch (RuntimeException e) { + var msg = "Failed to get metadata"; + if (logger.isErrorEnabled()) { + logger.error(msg, e); + } + throw new SQLException(msg, e); + } } /** @@ -218,7 +245,8 @@ public void clearWarnings() throws SQLException { } @Override - public boolean isWrapperFor(Class iface) { + public boolean isWrapperFor(Class iface) throws SQLException { + checkClosed(); return false; } diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java index 73a66613..09c78687 100644 --- a/src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java @@ -127,11 +127,9 @@ public void configure(Map configValues) { var clientSettings = clientSettingsBuilder.build(); - assertNotNull(BuildConfig.NAME); - assertNotNull(BuildConfig.VERSION); var driverInfo = MongoDriverInformation.builder() - .driverName(BuildConfig.NAME) - .driverVersion(BuildConfig.VERSION) + .driverName(assertNotNull(BuildConfig.NAME)) + .driverVersion(assertNotNull(BuildConfig.VERSION)) .build(); mongoClient = MongoClients.create(clientSettings, driverInfo); diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaData.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaData.java new file mode 100644 index 00000000..c503e665 --- /dev/null +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaData.java @@ -0,0 +1,189 @@ +/* + * Copyright 2024-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.hibernate.jdbc; + +import static com.mongodb.hibernate.internal.MongoAssertions.assertTrue; +import static com.mongodb.hibernate.internal.MongoAssertions.fail; +import static com.mongodb.hibernate.internal.VisibleForTesting.AccessModifier.PRIVATE; + +import com.mongodb.hibernate.internal.VisibleForTesting; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; + +/** + * MongoDB Dialect's JDBC {@link java.sql.DatabaseMetaData} implementation class. + * + *

It only focuses on API methods MongoDB Dialect will support. All the other methods are implemented by throwing + * exceptions in its parent {@link DatabaseMetaDataAdapter adapter interface}. + */ +final class MongoDatabaseMetaData implements DatabaseMetaDataAdapter { + + public static final String MONGO_DATABASE_PRODUCT_NAME = "MongoDB"; + public static final String MONGO_JDBC_DRIVER_NAME = "MongoDB Java Driver JDBC Adapter"; + + @VisibleForTesting(otherwise = PRIVATE) + record VersionNumPair(int majorVersion, int minVersion) {} + + private final Connection connection; + + private final Version databaseVersion; + private final Version driverVersion; + + MongoDatabaseMetaData( + Connection connection, + String databaseVersionText, + int databaseMajorVersion, + int databaseMinorVersion, + String driverVersionText) { + this.connection = connection; + databaseVersion = new Version(databaseVersionText, databaseMajorVersion, databaseMinorVersion); + driverVersion = Version.parse(driverVersionText); + } + + @Override + public String getDatabaseProductName() { + return MONGO_DATABASE_PRODUCT_NAME; + } + + @Override + public String getDatabaseProductVersion() { + return databaseVersion.versionText; + } + + @Override + public String getDriverName() { + return MONGO_JDBC_DRIVER_NAME; + } + + @Override + public String getDriverVersion() { + return driverVersion.versionText; + } + + @Override + public int getDriverMajorVersion() { + return driverVersion.major; + } + + @Override + public int getDriverMinorVersion() { + return driverVersion.minor; + } + + @Override + public String getSQLKeywords() { + return ""; + } + + @Override + public boolean isCatalogAtStart() { + return true; + } + + @Override + public String getCatalogSeparator() { + return "."; + } + + @Override + public boolean supportsSchemasInTableDefinitions() { + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() { + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() { + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() { + return false; + } + + @Override + public boolean supportsResultSetType(int type) { + return type == ResultSet.TYPE_FORWARD_ONLY; + } + + @Override + public boolean supportsBatchUpdates() { + return true; + } + + @Override + public Connection getConnection() { + return connection; + } + + @Override + public boolean supportsNamedParameters() { + return false; + } + + @Override + public boolean supportsGetGeneratedKeys() { + return false; + } + + @Override + public int getDatabaseMajorVersion() { + return databaseVersion.major; + } + + @Override + public int getDatabaseMinorVersion() { + return databaseVersion.minor; + } + + @Override + public int getJDBCMajorVersion() { + return 4; + } + + @Override + public int getJDBCMinorVersion() { + return 3; + } + + @Override + public int getSQLStateType() { + return DatabaseMetaData.sqlStateSQL; + } + + @Override + public boolean isWrapperFor(Class iface) { + return false; + } + + private record Version(String versionText, int major, int minor) { + static Version parse(String versionText) { + String[] parts = versionText.split("[-.]", 3); + assertTrue(parts.length >= 2); + try { + return new Version(versionText, Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); + } catch (NumberFormatException e) { + throw fail(e.toString()); + } + } + } +} diff --git a/src/main/java/com/mongodb/hibernate/jdbc/MongoStatement.java b/src/main/java/com/mongodb/hibernate/jdbc/MongoStatement.java index f9be5d43..16c86f37 100644 --- a/src/main/java/com/mongodb/hibernate/jdbc/MongoStatement.java +++ b/src/main/java/com/mongodb/hibernate/jdbc/MongoStatement.java @@ -205,7 +205,8 @@ public boolean isClosed() { } @Override - public boolean isWrapperFor(Class iface) { + public boolean isWrapperFor(Class iface) throws SQLException { + checkClosed(); return false; } diff --git a/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionProviderTests.java b/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionProviderTests.java index 77ca53fc..1138a3ad 100644 --- a/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionProviderTests.java +++ b/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionProviderTests.java @@ -94,7 +94,7 @@ void testMongoClientCustomizerTakeEffect(boolean customizerAppliesConnectionStri @Test void testMongoClientCustomizerThrowException() { - assertThrows(ServiceException.class, () -> { + assertThrows(NullPointerException.class, () -> { try (var ignored = buildSessionFactory( (builder, cs) -> { throw new NullPointerException(); diff --git a/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java b/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java index 2c5dda28..42add1ac 100644 --- a/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java +++ b/src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java @@ -16,26 +16,39 @@ package com.mongodb.hibernate.jdbc; +import static com.mongodb.hibernate.jdbc.MongoDatabaseMetaData.MONGO_DATABASE_PRODUCT_NAME; +import static com.mongodb.hibernate.jdbc.MongoDatabaseMetaData.MONGO_JDBC_DRIVER_NAME; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoDatabase; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; +import java.util.Map; +import java.util.stream.Stream; +import org.bson.Document; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -47,6 +60,9 @@ class MongoConnectionTests { @Mock private ClientSession clientSession; + @Mock + private MongoClient mongoClient; + @InjectMocks private MongoConnection mongoConnection; @@ -80,6 +96,76 @@ void testClosedWhenSessionClosingThrowsException() { } } + @Nested + class ClosedTests { + + @FunctionalInterface + interface ConnectionMethodInvocation { + void runOn(MongoConnection conn) throws SQLException; + } + + @ParameterizedTest(name = "SQLException is thrown when \"{0}\" is called on a closed MongoConnection") + @MethodSource("getMongoConnectionMethodInvocationsImpactedByClosing") + void testCheckClosed(String label, ConnectionMethodInvocation methodInvocation) throws SQLException { + // given + mongoConnection.close(); + + // when && then + var exception = assertThrows(SQLException.class, () -> methodInvocation.runOn(mongoConnection)); + assertEquals("Connection has been closed", exception.getMessage()); + } + + private static Stream getMongoConnectionMethodInvocationsImpactedByClosing() { + var exampleQueryMql = + """ + { + find: "restaurants", + filter: { rating: { $gte: 9 }, cuisine: "italian" }, + projection: { name: 1, rating: 1, address: 1 }, + sort: { name: 1 }, + limit: 5 + }"""; + var exampleUpdateMql = + """ + { + update: "members", + updates: [ + { + q: {}, + u: { $inc: { points: 1 } }, + multi: true + } + ] + }"""; + return Map.ofEntries( + Map.entry("setAutoCommit(boolean)", conn -> conn.setAutoCommit(false)), + Map.entry("getAutoCommit()", MongoConnection::getAutoCommit), + Map.entry("commit()", MongoConnection::commit), + Map.entry("rollback()", MongoConnection::rollback), + Map.entry("createStatement()", MongoConnection::createStatement), + Map.entry("prepareStatement(String)", conn -> conn.prepareStatement(exampleUpdateMql)), + Map.entry( + "prepareStatement(String,int,int)", + conn -> conn.prepareStatement( + exampleQueryMql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)), + Map.entry( + "createArrayOf(String,Object[])", + conn -> conn.createArrayOf("myArrayType", new String[] {"value1", "value2"})), + Map.entry( + "createStruct(String,Object[])", + conn -> conn.createStruct("myStructType", new Object[] {1, "Toronto"})), + Map.entry("getMetaData()", MongoConnection::getMetaData), + Map.entry("getCatalog()", MongoConnection::getCatalog), + Map.entry("getSchema()", MongoConnection::getSchema), + Map.entry("getWarnings()", MongoConnection::getWarnings), + Map.entry("clearWarnings()", MongoConnection::clearWarnings), + Map.entry("isWrapperFor()", conn -> conn.isWrapperFor(Connection.class))) + .entrySet() + .stream() + .map(entry -> Arguments.of(entry.getKey(), entry.getValue())); + } + } + @Nested class TransactionTests { @@ -156,19 +242,6 @@ void testNoTransactionStartedWhenAutoCommitChangedToFalse() throws SQLException verify(clientSession, never()).startTransaction(); } } - - @ParameterizedTest( - name = "SQLException is thrown when 'setAutoCommit({0})' is called on a closed MongoConnection") - @ValueSource(booleans = {true, false}) - void testSQLExceptionThrowWhenCalledOnClosedConnection(boolean autoCommit) throws SQLException { - // given - mongoConnection.close(); - verify(clientSession).close(); - - // when && then - assertThrows(SQLException.class, () -> mongoConnection.setAutoCommit(autoCommit)); - verifyNoMoreInteractions(clientSession); - } } @Nested @@ -207,18 +280,6 @@ void testSQLExceptionThrownWhenTransactionCommitFailed() throws SQLException { // when && then assertThrows(SQLException.class, () -> mongoConnection.commit()); } - - @Test - @DisplayName("SQLException is thrown when 'commit()' is called on a closed MongoConnection") - void testSQLExceptionThrowWhenCalledOnClosedConnection() throws SQLException { - // given - mongoConnection.close(); - verify(clientSession).close(); - - // when && then - assertThrows(SQLException.class, () -> mongoConnection.commit()); - verifyNoMoreInteractions(clientSession); - } } @Nested @@ -257,81 +318,56 @@ void testSQLExceptionThrownWhenTransactionRollbackFailed() throws SQLException { // when && then assertThrows(SQLException.class, () -> mongoConnection.rollback()); } - - @Test - @DisplayName("SQLException is thrown when 'rollback()' is called on a closed MongoConnection") - void testSQLExceptionThrowWhenCalledOnClosedConnection() throws SQLException { - // given - mongoConnection.close(); - verify(clientSession).close(); - - // when && then - assertThrows(SQLException.class, () -> mongoConnection.rollback()); - verifyNoMoreInteractions(clientSession); - } } + } - @Nested - class TransactionIsolationLevelTests { - - @ParameterizedTest - @ValueSource( - ints = { - Connection.TRANSACTION_NONE, - Connection.TRANSACTION_READ_UNCOMMITTED, - Connection.TRANSACTION_READ_COMMITTED, - Connection.TRANSACTION_REPEATABLE_READ, - Connection.TRANSACTION_SERIALIZABLE - }) - @DisplayName("MongoDB Dialect doesn't support JDBC transaction isolation level setting") - void testSetUnsupported(int level) { - // when && then - assertThrows( - SQLFeatureNotSupportedException.class, () -> mongoConnection.setTransactionIsolation(level)); - verifyNoInteractions(clientSession); - } + @Nested + class GetMetaDataTests { - @ParameterizedTest - @ValueSource( - ints = { - Connection.TRANSACTION_NONE, - Connection.TRANSACTION_READ_UNCOMMITTED, - Connection.TRANSACTION_READ_COMMITTED, - Connection.TRANSACTION_REPEATABLE_READ, - Connection.TRANSACTION_SERIALIZABLE - }) - @DisplayName( - "SQLException is thrown when 'setTransactionIsolation({0})' is called on a closed MongoConnection") - void testSQLExceptionThrowWhenCalledOnClosedConnection(int level) throws SQLException { - // given - mongoConnection.close(); - verify(clientSession).close(); + @Mock + private MongoDatabase mongoDatabase; - // when && then - assertThrows(SQLException.class, () -> mongoConnection.setTransactionIsolation(level)); - verifyNoMoreInteractions(clientSession); - } + @Test + @DisplayName("Happy path for MongoDatabaseMetaData fetching") + void testSuccess() { + // given + doReturn(mongoDatabase).when(mongoClient).getDatabase(eq("admin")); + var commandResultJson = + """ + { + "ok": 1.0, + "version": "8.0.1", + "versionArray": [8, 0, 1, 0] + }"""; + var commandResultDoc = Document.parse(commandResultJson); + doReturn(commandResultDoc) + .when(mongoDatabase) + .runCommand(any(ClientSession.class), argThat(arg -> "buildinfo" + .equals(arg.toBsonDocument().getFirstKey()))); - @Test - @DisplayName("MongoDB Dialect doesn't support JDBC transaction isolation level fetching") - void testGetUnsupported() { - // when && then - assertThrows(SQLFeatureNotSupportedException.class, () -> mongoConnection.getTransactionIsolation()); - verifyNoInteractions(clientSession); - } + // when + var metaData = assertDoesNotThrow(() -> mongoConnection.getMetaData()); - @Test - @DisplayName( - "SQLException is thrown when 'getTransactionIsolation()' is called on a closed MongoConnection") - void testSQLExceptionThrowWhenCalledOnClosedConnection() throws SQLException { - // given - mongoConnection.close(); - verify(clientSession).close(); + // then + assertAll( + () -> assertEquals(MONGO_DATABASE_PRODUCT_NAME, metaData.getDatabaseProductName()), + () -> assertEquals(MONGO_JDBC_DRIVER_NAME, metaData.getDriverName()), + () -> assertEquals("8.0.1", metaData.getDatabaseProductVersion()), + () -> assertEquals(8, metaData.getDatabaseMajorVersion()), + () -> assertEquals(0, metaData.getDatabaseMinorVersion())); + } - // when && then - assertThrows(SQLException.class, () -> mongoConnection.getTransactionIsolation()); - verifyNoMoreInteractions(clientSession); - } + @Test + @DisplayName("SQLException is thrown when MongoConnection#getMetaData() failed while interacting with db") + void testSQLExceptionThrownWhenMetaDataFetchingFailed() { + // given + doReturn(mongoDatabase).when(mongoClient).getDatabase(eq("admin")); + doThrow(new RuntimeException()) + .when(mongoDatabase) + .runCommand(any(ClientSession.class), argThat(arg -> "buildinfo" + .equals(arg.toBsonDocument().getFirstKey()))); + // when && then + assertThrows(SQLException.class, () -> mongoConnection.getMetaData()); } } } diff --git a/src/test/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaDataTests.java b/src/test/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaDataTests.java new file mode 100644 index 00000000..309df6f9 --- /dev/null +++ b/src/test/java/com/mongodb/hibernate/jdbc/MongoDatabaseMetaDataTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.hibernate.jdbc; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.sql.Connection; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mock; + +class MongoDatabaseMetaDataTests { + @Mock + private Connection connection; + + @ParameterizedTest + @CsvSource({"3.2, 3, 2", "1.0.0-SNAPSHOT, 1, 0", "5.2.0-alpha, 5, 2", "2.1-beta, 2, 1", "1-0, 1, 0"}) + void constructor(String driverVersionText, int driverMajorVersion, int driverMinorVersion) { + MongoDatabaseMetaData metadata = new MongoDatabaseMetaData(connection, "DBMS 1.2", 1, 2, driverVersionText); + assertAll( + () -> assertSame(connection, metadata.getConnection()), + () -> assertEquals("DBMS 1.2", metadata.getDatabaseProductVersion()), + () -> assertEquals(1, metadata.getDatabaseMajorVersion()), + () -> assertEquals(2, metadata.getDatabaseMinorVersion()), + () -> assertEquals(driverVersionText, metadata.getDriverVersion()), + () -> assertEquals(driverMajorVersion, metadata.getDriverMajorVersion()), + () -> assertEquals(driverMinorVersion, metadata.getDriverMinorVersion())); + } + + @ParameterizedTest + @CsvSource({"3", "alpha", "."}) + void constructorFails(String invalidDriverVersionText) { + assertThrows( + AssertionError.class, + () -> new MongoDatabaseMetaData(connection, "DBMS 1.2", 1, 2, invalidDriverVersionText)); + } +} diff --git a/src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java b/src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java index 90ba779b..3f4ea3f3 100644 --- a/src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java +++ b/src/test/java/com/mongodb/hibernate/jdbc/MongoStatementTests.java @@ -31,6 +31,7 @@ import com.mongodb.client.MongoDatabase; import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; +import java.sql.Statement; import java.util.Map; import java.util.stream.Stream; import org.bson.BsonDocument; @@ -168,7 +169,8 @@ private static Stream getMongoStatementMethodInvocationsImpactedByClo Map.entry("addBatch(String)", stmt -> stmt.addBatch(exampleUpdateMql)), Map.entry("clearBatch()", MongoStatement::clearBatch), Map.entry("executeBatch()", MongoStatement::executeBatch), - Map.entry("getConnection()", MongoStatement::getConnection)) + Map.entry("getConnection()", MongoStatement::getConnection), + Map.entry("isWrapperFor(Class)", stmt -> stmt.isWrapperFor(Statement.class))) .entrySet() .stream() .map(entry -> Arguments.of(entry.getKey(), entry.getValue())); diff --git a/src/test/resources/hibernate.properties b/src/test/resources/hibernate.properties index 162cadb2..98d656f9 100644 --- a/src/test/resources/hibernate.properties +++ b/src/test/resources/hibernate.properties @@ -1,3 +1,4 @@ jakarta.persistence.jdbc.url=mongodb://localhost/mongo-hibernate-test?directConnection=false hibernate.dialect=com.mongodb.hibernate.dialect.MongoDialect hibernate.connection.provider_class=com.mongodb.hibernate.jdbc.MongoConnectionProvider +hibernate.boot.allow_jdbc_metadata_access=false