DBDatabaseHandle.java

/*
 * Copyright 2020 Gregory Graham.
 *
 * Commercial licenses are available, please contact info@gregs.co.nz for details.
 * 
 * This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/ 
 * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
 * 
 * You are free to:
 *     Share - copy and redistribute the material in any medium or format
 *     Adapt - remix, transform, and build upon the material
 * 
 *     The licensor cannot revoke these freedoms as long as you follow the license terms.               
 *     Under the following terms:
 *                 
 *         Attribution - 
 *             You must give appropriate credit, provide a link to the license, and indicate if changes were made. 
 *             You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
 *         NonCommercial - 
 *             You may not use the material for commercial purposes.
 *         ShareAlike - 
 *             If you remix, transform, or build upon the material, 
 *             you must distribute your contributions under the same license as the original.
 *         No additional restrictions - 
 *             You may not apply legal terms or technological measures that legally restrict others from doing anything the 
 *             license permits.
 * 
 * Check the Creative Commons website for any details, legalese, and updates.
 */
package nz.co.gregs.dbvolution.databases;

import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
import nz.co.gregs.dbvolution.*;
import nz.co.gregs.dbvolution.actions.DBAction;
import nz.co.gregs.dbvolution.actions.DBActionList;
import nz.co.gregs.dbvolution.actions.DBQueryable;
import nz.co.gregs.dbvolution.columns.ColumnProvider;
import nz.co.gregs.dbvolution.databases.DBDatabaseImplementation.ResponseToException;
import nz.co.gregs.dbvolution.databases.connections.DBConnection;
import nz.co.gregs.dbvolution.databases.definitions.DBDefinition;
import nz.co.gregs.dbvolution.databases.metadata.DBDatabaseMetaData;
import nz.co.gregs.dbvolution.databases.settingsbuilders.SettingsBuilder;
import nz.co.gregs.dbvolution.exceptions.*;
import nz.co.gregs.dbvolution.databases.metadata.Options;
import nz.co.gregs.dbvolution.internal.query.StatementDetails;
import nz.co.gregs.dbvolution.transactions.DBTransaction;

/**
 * A DBDatabaseHandle makes it easy to switch between databases.
 *
 * <p>
 * This is intended to be useful for "superuser" applications rather than
 * everyday data entry or reporting.
 *
 * <p>
 * DBDatabaseHandle is a simple wrapper on a DBDatabase, with the addition of
 * the
 * {@link DBDatabaseHandle#setDatabase(nz.co.gregs.dbvolution.databases.DBDatabase) setDatabase method}.
 * This allows the underlying database to be changed for all references to this
 * object.
 *
 * <p>
 * This allows for an application to be used with several databases while being
 * designed for a unitary database.
 *
 * @author gregorygraham
 */
public class DBDatabaseHandle implements DBDatabase {

	private static final long serialVersionUID = 1L;

	private DBDatabase wrappedDatabase;

	@Override
	public void setPreventAccidentalDeletingAllRowsFromTable(boolean b) {
		wrappedDatabase.setPreventAccidentalDeletingAllRowsFromTable(b);
	}

	@Override
	public void createOrUpdateTable(DBRow newTableRow) throws SQLException, AutoCommitActionDuringTransactionException {
		wrappedDatabase.createOrUpdateTable(newTableRow);
	}

	@Override
	public void createTableNoExceptions(DBRow newTable) throws AutoCommitActionDuringTransactionException {
		wrappedDatabase.createTableNoExceptions(newTable);
	}

	@Override
	public void createTableNoExceptions(boolean includeForeignKeyClauses, DBRow newTable) throws AutoCommitActionDuringTransactionException {
		wrappedDatabase.createTableNoExceptions(includeForeignKeyClauses, newTable);
	}

	@Override
	public void createTableWithForeignKeys(DBRow newTableRow) throws SQLException, AutoCommitActionDuringTransactionException {
		wrappedDatabase.createTableWithForeignKeys(newTableRow);
	}

	@Override
	public void createTablesNoExceptions(DBRow... newTables) {
		wrappedDatabase.createTablesNoExceptions(newTables);
	}

	@Override
	public void createTablesNoExceptions(boolean includeForeignKeyClauses, DBRow... newTables) {
		wrappedDatabase.createTablesNoExceptions(includeForeignKeyClauses, newTables);
	}

	@Override
	public void createTablesWithForeignKeysNoExceptions(DBRow... newTables) {
		wrappedDatabase.createTablesWithForeignKeysNoExceptions(newTables);
	}

	@Override
	public void dropDatabase(String databaseName, boolean doIt) throws UnsupportedOperationException, AutoCommitActionDuringTransactionException, AccidentalDroppingOfDatabaseException, SQLException, ExceptionThrownDuringTransaction {
		wrappedDatabase.dropDatabase(databaseName, doIt);
	}

	@Override
	public void dropDatabase(boolean doIt) throws AccidentalDroppingOfDatabaseException, UnableToDropDatabaseException, SQLException, AutoCommitActionDuringTransactionException, ExceptionThrownDuringTransaction {
		wrappedDatabase.dropDatabase(doIt);
	}

	@Override
	public <TR extends DBRow> void dropTableIfExists(TR tableRow) throws AccidentalDroppingOfTableException, AutoCommitActionDuringTransactionException, SQLException {
		wrappedDatabase.dropTableIfExists(tableRow);
	}

	@Override
	public <R extends DBRow> List<R> getByExample(Long expectedNumberOfRows, R exampleRow) throws SQLException, UnexpectedNumberOfRowsException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.getByExample(expectedNumberOfRows, exampleRow);
	}

	@Override
	public <R extends DBRow> List<R> getByExample(R exampleRow) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.getByExample(exampleRow);
	}

	@Override
	public List<DBQueryRow> getByExamples(DBRow row, DBRow... rows) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.getByExamples(row, rows);
	}

	@Override
	public <T extends DBRow> DBRecursiveQuery<T> getDBRecursiveQuery(DBQuery query, ColumnProvider keyToFollow, T dbRow) {
		return wrappedDatabase.getDBRecursiveQuery(query, keyToFollow, dbRow);
	}

	@Override
	public DBActionList update(Collection<? extends DBRow> listOfRowsToUpdate) throws SQLException {
		return wrappedDatabase.update(listOfRowsToUpdate);
	}

	@Override
	public DBActionList update(DBRow... rows) throws SQLException {
		return wrappedDatabase.update(rows);
	}

	@Override
	public DBActionList implement(DBScript script) throws Exception {
		return wrappedDatabase.implement(script);
	}

	@Override
	public Instant getCurrentInstant() throws UnexpectedNumberOfRowsException, AccidentalCartesianJoinException, AccidentalBlankQueryException, SQLException {
		return wrappedDatabase.getCurrentInstant();
	}

	@Override
	public LocalDateTime getCurrentLocalDatetime() throws SQLException {
		return wrappedDatabase.getCurrentLocalDatetime();
	}

	@Override
	public DBQuery getDBQuery(DBRow example, DBRow... examples) {
		return wrappedDatabase.getDBQuery(example, examples);
	}

	@Override
	public DBQuery getDBQuery(Collection<DBRow> examples) {
		return wrappedDatabase.getDBQuery(examples);
	}

	@Override
	public <K extends DBRow> DBQueryInsert<K> getDBQueryInsert(K mapper) {
		return wrappedDatabase.getDBQueryInsert(mapper);
	}

	@Override
	public void setLastException(Throwable except) {
		wrappedDatabase.setLastException(except);
	}

	@Override
	public void createTable(DBRow newTableRow) throws SQLException, AutoCommitActionDuringTransactionException {
		wrappedDatabase.createTable(newTableRow);
	}

	@Override
	public boolean getPrintSQLBeforeExecuting() {
		return wrappedDatabase.getPrintSQLBeforeExecuting();
	}

	@Override
	public <R extends DBRow> DBTable<R> getDBTable(R example) {
		return wrappedDatabase.getDBTable(example);
	}

	@Override
	public DBActionList insert(Collection<? extends DBRow> listOfRowsToInsert) throws SQLException {
		return wrappedDatabase.insert(listOfRowsToInsert);
	}

	@Override
	public DBActionList insert(DBRow row) throws SQLException {
		return wrappedDatabase.insert(row);
	}

	@Override
	public DBActionList insert(DBRow... listOfRowsToInsert) throws SQLException {
		return wrappedDatabase.insert(listOfRowsToInsert);
	}

	@Override
	public DBActionList insertOrUpdate(Collection<? extends DBRow> listOfRowsToInsert) throws SQLException {
		return wrappedDatabase.insertOrUpdate(listOfRowsToInsert);
	}

	@Override
	public DBActionList insertOrUpdate(DBRow row) throws SQLException {
		return wrappedDatabase.insertOrUpdate(row);
	}

	@Override
	public <A extends DBReport> List<A> get(A report, DBRow... examples) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.get(report, examples);
	}

	@Override
	public List<DBQueryRow> get(DBRow row, DBRow... rows) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.get(row, rows);
	}

	@Override
	public List<DBQueryRow> get(Long expectedNumberOfRows, DBRow row, DBRow... rows) throws SQLException, UnexpectedNumberOfRowsException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.get(expectedNumberOfRows, row, rows);
	}

	@Override
	public <R extends DBRow> List<R> get(Long expectedNumberOfRows, R exampleRow) throws SQLException, UnexpectedNumberOfRowsException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.get(expectedNumberOfRows, exampleRow);
	}

	@Override
	public <R extends DBRow> List<R> get(R exampleRow) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException {
		return wrappedDatabase.get(exampleRow);
	}

	@Override
	public <A extends DBReport> List<A> getAllRows(A report, DBRow... examples) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.getAllRows(report, examples);
	}

	@Override
	public DBActionList delete(Collection<? extends DBRow> list) throws SQLException {
		return wrappedDatabase.delete(list);
	}

	@Override
	public DBActionList delete(DBRow... rows) throws SQLException {
		return wrappedDatabase.delete(rows);
	}

	@Override
	public DBDatabase copy() {
		return new DBDatabaseHandle(wrappedDatabase.copy());
	}

	@Override
	public DBQuery getDBQuery(DBRow example) {
		return wrappedDatabase.getDBQuery(example);
	}

	@Override
	public DBQuery getDBQuery() {
		return wrappedDatabase.getDBQuery();
	}

	@Override
	public boolean supportsDifferenceBetweenNullAndEmptyString() {
		return wrappedDatabase.supportsDifferenceBetweenNullAndEmptyString();
	}

	@Override
	public <V> V doTransaction(DBTransaction<V> dbTransaction) throws SQLException, ExceptionThrownDuringTransaction {
		return wrappedDatabase.doTransaction(dbTransaction);
	}

	@Override
	public <V> V doTransaction(DBTransaction<V> dbTransaction, Boolean commit) throws SQLException, ExceptionThrownDuringTransaction {
		return wrappedDatabase.doTransaction(dbTransaction, commit);
	}

	@Override
	public DBActionList test(DBScript script) throws SQLException, ExceptionThrownDuringTransaction, NoAvailableDatabaseException {
		return wrappedDatabase.test(script);
	}

	@Override
	public void unusedConnection(DBConnection connection) throws SQLException {
		wrappedDatabase.unusedConnection(connection);
	}

	@Override
	public void discardConnection(DBConnection connection) {
		wrappedDatabase.discardConnection(connection);
	}

	@Override
	public DBConnection getConnection() throws UnableToCreateDatabaseConnectionException, UnableToFindJDBCDriver, SQLException {
		return wrappedDatabase.getConnection();
	}

	@Override
	public boolean isPrintSQLBeforeExecuting() {
		return wrappedDatabase.isPrintSQLBeforeExecuting();
	}

	@Override
	public void printSQLIfRequested(String sqlString) {
		wrappedDatabase.printSQLIfRequested(sqlString);
	}

	@Override
	public void printSQLIfRequested(String sqlString, PrintStream out) {
		wrappedDatabase.printSQLIfRequested(sqlString, out);
	}

	@Override
	public void preventDroppingOfDatabases(boolean justLeaveThisAtTrue) {
		wrappedDatabase.preventDroppingOfDatabases(justLeaveThisAtTrue);
	}

	@Override
	public void preventDroppingOfTables(boolean droppingTablesIsAMistake) {
		wrappedDatabase.preventDroppingOfTables(droppingTablesIsAMistake);
	}

	@Override
	public void setBatchSQLStatementsWhenPossible(boolean batchSQLStatementsWhenPossible) {
		wrappedDatabase.setBatchSQLStatementsWhenPossible(batchSQLStatementsWhenPossible);
	}

	@Override
	public boolean getBatchSQLStatementsWhenPossible() {
		return wrappedDatabase.getBatchSQLStatementsWhenPossible();
	}

	@Override
	public boolean batchSQLStatementsWhenPossible() {
		return wrappedDatabase.batchSQLStatementsWhenPossible();
	}

	@Override
	public boolean willCreateBlankQuery(DBRow row) throws NoAvailableDatabaseException {
		return wrappedDatabase.willCreateBlankQuery(row);
	}

	@Override
	public void createIndexesOnAllFields(DBRow newTableRow) throws SQLException {
		wrappedDatabase.createIndexesOnAllFields(newTableRow);
	}

	@Override
	public void removeForeignKeyConstraints(DBRow newTableRow) throws SQLException {
		wrappedDatabase.removeForeignKeyConstraints(newTableRow);
	}

	@Override
	public void createForeignKeyConstraints(DBRow newTableRow) throws SQLException {
		wrappedDatabase.createForeignKeyConstraints(newTableRow);
	}

	@Override
	public void updateTableToMatchDBRow(DBRow table) throws SQLException {
		wrappedDatabase.updateTableToMatchDBRow(table);
	}

	@Override
	public <V> V doReadOnlyTransaction(DBTransaction<V> dbTransaction) throws SQLException, ExceptionThrownDuringTransaction, NoAvailableDatabaseException {
		return wrappedDatabase.doReadOnlyTransaction(dbTransaction);
	}

	@Override
	public DBTransactionStatement getDBTransactionStatement() throws SQLException {
		return wrappedDatabase.getDBTransactionStatement();
	}

	@Override
	public void commitTransaction() throws SQLException {
		wrappedDatabase.commitTransaction();
	}

	@Override
	public void rollbackTransaction() throws SQLException {
		wrappedDatabase.rollbackTransaction();
	}

	@Override
	public <V> IncompleteTransaction<V> doTransactionWithoutCompleting(DBTransaction<V> dbTransaction) throws SQLException, ExceptionThrownDuringTransaction {
		return wrappedDatabase.doTransactionWithoutCompleting(dbTransaction);
	}

	@Override
	public void setQuietExceptionsPreference(boolean b) {
		wrappedDatabase.setQuietExceptionsPreference(b);
	}

	@Override
	public boolean getQuietExceptionsPreference() {
		return wrappedDatabase.getQuietExceptionsPreference();
	}

	@Override
	public String getSQLForDBQuery(DBQueryable query) throws NoAvailableDatabaseException {
		return wrappedDatabase.getSQLForDBQuery(query);
	}

	@Override
	public DBStatement getDBStatement() throws SQLException {
		return wrappedDatabase.getDBStatement();
	}

	@Override
	public void setPrintSQLBeforeExecuting(boolean b) {
		wrappedDatabase.setPrintSQLBeforeExecuting(b);
	}

	@Override
	public String getHost() {
		return wrappedDatabase.getHost();
	}

	@Override
	public String getJdbcURL() {
		return wrappedDatabase.getJdbcURL();
	}

	@Override
	public String getLabel() {
		return wrappedDatabase.getLabel();
	}

	@Override
	public String getPassword() {
		return wrappedDatabase.getPassword();
	}

	@Override
	public String getPort() {
		return wrappedDatabase.getPort();
	}

	@Override
	public String getSchema() {
		return wrappedDatabase.getSchema();
	}

	@Override
	public String getUsername() {
		return wrappedDatabase.getUsername();
	}

	@Override
	public String getDatabaseInstance() {
		return wrappedDatabase.getDatabaseInstance();
	}

	@Override
	public String getDatabaseName() {
		return wrappedDatabase.getDatabaseName();
	}

	@Override
	public String getDriverName() {
		return wrappedDatabase.getDriverName();
	}

	@Override
	public Map<String, String> getExtras() {
		return wrappedDatabase.getExtras();
	}

	@Override
	public DatabaseConnectionSettings getSettings() {
		return wrappedDatabase.getSettings();
	}

	@Override
	public DatabaseConnectionSettings getSettingsFromJDBCURL(String jdbcURL) {
		return wrappedDatabase.getSettingsFromJDBCURL(jdbcURL);
	}

	@Override
	public String getUrlFromSettings(DatabaseConnectionSettings oldSettings) {
		return wrappedDatabase.getUrlFromSettings(oldSettings);
	}

	@Override
	public DataSource getDataSource() {
		return wrappedDatabase.getDataSource();
	}

	public DBDatabaseHandle() {
		super();
		try {
			wrappedDatabase = H2MemoryDB.createANewRandomDatabase();
		} catch (SQLException ex) {
			Logger.getLogger(DBDatabaseHandle.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	public DBDatabaseHandle(SettingsBuilder<?, ?> settings) throws SQLException, Exception {
		wrappedDatabase = settings.getDBDatabase();
	}

	public DBDatabaseHandle(DBDatabase db) {
		wrappedDatabase = db;
	}

	public synchronized DBDatabaseHandle setDatabase(DBDatabase db) {
		wrappedDatabase = db;
		return this;
	}

	/**
	 * Used By Subclasses To Inject Datatypes, Functions, Etc Into the Database.
	 *
	 * @param stmt the statement to use when adding features, DO NOT CLOSE THIS
	 * STATEMENT .
	 * @throws ExceptionDuringDatabaseFeatureSetup database exceptions may occur
	 * @see PostgresDB
	 * @see H2DB
	 * @see SQLiteDB
	 * @see OracleDB
	 * @see MSSQLServerDB
	 * @see MySQLDB
	 *
	 */
	@Override
	public void addDatabaseSpecificFeatures(final Statement stmt) throws ExceptionDuringDatabaseFeatureSetup {
	}

	/**
	 * Clones the DBDatabase
	 *
	 *
	 * @return a clone of the database.
	 * @throws java.lang.CloneNotSupportedException
	 * java.lang.CloneNotSupportedException
	 *
	 */
	@Override
	public DBDatabaseHandle clone() throws CloneNotSupportedException {
		return new DBDatabaseHandle(wrappedDatabase.clone());
	}

	@Override
	public ResponseToException addFeatureToFixException(Exception exp, QueryIntention intent, StatementDetails details) throws Exception {
		return wrappedDatabase.addFeatureToFixException(exp, intent, details);
	}

	@Override
	public boolean isMemoryDatabase() {
		return wrappedDatabase.isMemoryDatabase();
	}

	/**
	 * Returns the port number usually assign to instances of this database.
	 *
	 * <p>
	 * There is no guarantee that the particular database instance uses this port,
	 * check with your DBA.
	 *
	 * @return the port number commonly used by this type of database
	 */
	@Override
	public Integer getDefaultPort() {
		return wrappedDatabase.getDefaultPort();
	}

	@Override
	public SettingsBuilder<?, ?> getURLInterpreter() {
		return wrappedDatabase.getURLInterpreter();
	}

	@Override
	public void setDefinitionBasedOnConnectionMetaData(Properties clientInfo, DatabaseMetaData metaData) {
		wrappedDatabase.setDefinitionBasedOnConnectionMetaData(clientInfo, metaData);
	}

	@Override
	public <TR extends DBRow> void dropTableNoExceptions(TR tableRow) throws AccidentalDroppingOfTableException, AutoCommitActionDuringTransactionException {
		wrappedDatabase.dropTableNoExceptions(tableRow);
	}

	@Override
	public Connection getConnectionFromDriverManager() throws SQLException {
		return wrappedDatabase.getConnectionFromDriverManager();
	}

	@Override
	public boolean supportsMicrosecondPrecision() {
		return wrappedDatabase.supportsMicrosecondPrecision();
	}

	@Override
	public boolean supportsNanosecondPrecision() {
		return wrappedDatabase.supportsNanosecondPrecision();
	}

	@Override
	public void stop() {
		wrappedDatabase.stop();
	}

	@Override
	public boolean tableExists(DBRow table) throws SQLException {
		return wrappedDatabase.tableExists(table);
	}

	@Override
	public List<DBAction> createTable(DBRow newTableRow, boolean includeForeignKeyClauses) throws SQLException, AutoCommitActionDuringTransactionException {
		return wrappedDatabase.createTable(newTableRow, includeForeignKeyClauses);
	}

	@Override
	public DBQueryable executeDBQuery(DBQueryable query) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.executeDBQuery(query);
	}

	@Override
	public DBActionList executeDBAction(DBAction action) throws SQLException, NoAvailableDatabaseException {
		return wrappedDatabase.executeDBAction(action);
	}

	@Override
	public synchronized DBDefinition getDefinition() throws NoAvailableDatabaseException {
		return wrappedDatabase.getDefinition();
	}

	@Override
	public void deleteAllRowsFromTable(DBRow table) throws SQLException {
		wrappedDatabase.deleteAllRowsFromTable(table);
	}

	@Override
	public List<DBAction> dropTable(DBRow newTableRow) throws SQLException, AutoCommitActionDuringTransactionException {
		return wrappedDatabase.dropTable(newTableRow);
	}

	@Override
	public void handleErrorDuringExecutingSQL(DBDatabase suspectDatabase, Throwable sqlException, String sqlString) {
		wrappedDatabase.handleErrorDuringExecutingSQL(suspectDatabase, sqlException, sqlString);
	}

	@Override
	public boolean supportsGeometryTypesFullyInSchema() {
		return wrappedDatabase.supportsGeometryTypesFullyInSchema();
	}

	@Override
	public void print(List<?> rows) {
		wrappedDatabase.print(rows);
	}

	@Override
	public <MAPPER extends DBRow> DBMigration<MAPPER> getDBMigration(MAPPER mapper) {
		return wrappedDatabase.getDBMigration(mapper);
	}

	@Override
	public <A extends DBReport> List<A> getRows(A report, DBRow... examples) throws SQLException, AccidentalCartesianJoinException, AccidentalBlankQueryException, NoAvailableDatabaseException {
		return wrappedDatabase.getRows(report, examples);
	}

	@Override
	public <R extends DBRow> long getCount(R exampleRow) throws SQLException, AccidentalCartesianJoinException {
		return wrappedDatabase.getCount(exampleRow);
	}

	@Override
	public DBDatabaseMetaData getDBDatabaseMetaData(Options options)  throws SQLException{
		return wrappedDatabase.getDBDatabaseMetaData(options);
	}

}