All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.numdata.oss.db.DatabaseTableUpdater Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Numdata nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NUMDATA BV BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.numdata.oss.db;

import java.sql.*;
import java.util.*;
import javax.sql.*;

import com.numdata.oss.*;
import org.jetbrains.annotations.*;

/**
 * Utility class to modify tables in an existing database to match the Java
 * definition.
 *
 * @author Peter S. Heijnen
 */
@SuppressWarnings ( "JDBCExecuteWithNonConstantString" )
public class DatabaseTableUpdater
{
	/**
	 * Character set to use for all tables.
	 */
	private static final String CHARACTER_SET = "utf8";

	/**
	 * Update database structure of the specified tables.
	 *
	 * @param verbose           Be verbose about updates.
	 * @param realUpdates       Perform real updates vs. just print queries.
	 * @param createIfNecessary Create table if it does not exist.
	 * @param force             Perform updates that (may) cause data loss.
	 * @param dataSource        Database source to connect to database.
	 * @param dbName            Name of database ({@code null} if unknown).
	 * @param tableClasses      Tables to be updated.
	 *
	 * @throws Exception if the update fails.
	 */
	public static void updateTables( final boolean verbose, final boolean realUpdates, final boolean createIfNecessary, final boolean force, @NotNull final DataSource dataSource, @Nullable final String dbName, @NotNull final Iterable> tableClasses )
	throws Exception
	{
		for ( final Class tableClass : tableClasses )
		{
			try
			{
				updateTable( verbose, realUpdates, createIfNecessary, force, dataSource, dbName, tableClass );
			}
			catch ( final SQLException e )
			{
				throw new SQLException( "Failed to update table " + tableClass, e );
			}
		}
	}

	/**
	 * Update table structure.
	 *
	 * @param verbose           Be verbose about updates.
	 * @param realUpdates       Perform real updates vs. just print queries.
	 * @param createIfNecessary Create table if it does not exist.
	 * @param force             Perform updates that (may) cause data loss.
	 * @param dataSource        Database source to connect to database.
	 * @param dbName            Name of database ({@code null} if unknown).
	 * @param tableClass        Database table record class.
	 *
	 * @throws SQLException if there was a problem accessing the database.
	 * @throws IllegalArgumentException if something bad was given as argument.
	 */
	public static void updateTable( final boolean verbose, final boolean realUpdates, final boolean createIfNecessary, final boolean force, @NotNull final DataSource dataSource, @Nullable final String dbName, @NotNull final Class tableClass )
	throws SQLException
	{
		final DbServices db = new DbServices( dataSource );

		final String tableName = db.getTableName( tableClass );
		final ClassHandler classHandler = getClassHandler( tableClass );
		final String javaCreateStatement = classHandler.getCreateStatement();

		final String tableReference = '`' + ( ( dbName != null ) ? dbName + "`.`" : "" ) + tableName + '`';

		final Connection connection = dataSource.getConnection();
		try
		{
			final Statement statement = connection.createStatement();
			try
			{
				boolean tableExists;
				try
				{
					final ResultSet resultSet = statement.executeQuery( "SELECT 1 FROM " + tableName + " WHERE 0=1" );
					resultSet.close();
					tableExists = true;
				}
				catch ( final Exception ignored )
				{
					tableExists = false;
				}

				if ( tableExists )
				{
					if ( verbose )
					{
						System.out.println( "Updating structure of " + tableReference );
					}

					String dbCreateStatement = getCreateTable( tableName, statement );
					if ( !dbCreateStatement.contains( "DEFAULT CHARSET=" + CHARACTER_SET ) )
					{
						statement.executeUpdate( "alter table " + tableName + " character set " + CHARACTER_SET );
						dbCreateStatement = getCreateTable( tableName, statement );
					}

					final List javaCreateLines = getCreateLines( javaCreateStatement );
					final List dbCreateLines = getCreateLines( dbCreateStatement );

					if ( !javaCreateLines.equals( dbCreateLines ) )
					{
						for ( final String dbCreateLine : dbCreateLines )
						{
							if ( !javaCreateLines.contains( dbCreateLine ) )
							{
								final String id = getFirstID( dbCreateLine );
								if ( id == null )
								{
									throw new RuntimeException( "No column name in create line in DB: " + dbCreateLine + " from create statement: " + dbCreateStatement );
								}

								if ( isKeyDefinition( dbCreateLine ) )
								{
									final String javaCreateLine = getKeyDefinition( javaCreateLines, id );
									if ( javaCreateLine == null )
									{
										dropKey( realUpdates, dataSource, tableReference, id );
									}
								}
							}
						}

						for ( final String dbCreateLine : dbCreateLines )
						{
							if ( !javaCreateLines.contains( dbCreateLine ) )
							{
								final String id = getFirstID( dbCreateLine );
								if ( id == null )
								{
									throw new RuntimeException( "No column name in create line in DB: " + dbCreateLine + " from create statement: " + dbCreateStatement );
								}

								if ( isColumnDefinition( dbCreateLine ) )
								{
									final String javaCreateLine = getColumnDefinition( javaCreateLines, id );
									if ( javaCreateLine != null )
									{
										modifyColumn( realUpdates, force, dataSource, tableReference, id, dbCreateLine, javaCreateLine );
									}
									else
									{
										if ( realUpdates && !force )
										{
											System.out.print( "     ALTER TABLE " + tableReference + " DROP `" + id + "`;" );
											throw new RuntimeException( "Refusing to drop " + tableReference + ".`" + id + "` column" );
										}

										dropColumn( realUpdates, dataSource, tableReference, id );
									}
								}
								else if ( isKeyDefinition( dbCreateLine ) )
								{
									final String javaCreateLine = getKeyDefinition( javaCreateLines, id );
									if ( javaCreateLine != null )
									{
										modifyKey( realUpdates, dataSource, tableReference, id, javaCreateLine );
									}
								}
								else if ( !isPrimaryKeyDefinition( dbCreateLine ) )
								{
									throw new RuntimeException( "Unrecognized create line in DB: " + dbCreateLine + " from create statement: " + dbCreateStatement );
								}
							}
						}

						String lastColumn = null;

						for ( final String javaCreateLine : javaCreateLines )
						{
							final String id = getFirstID( javaCreateLine );
							if ( id == null )
							{
								throw new RuntimeException( "No column name in create line in Java: " + javaCreateLine + " from create statement: " + javaCreateStatement );
							}

							if ( !dbCreateLines.contains( javaCreateLine ) )
							{
								if ( isColumnDefinition( javaCreateLine ) )
								{
									final String dbCreateLine = getColumnDefinition( dbCreateLines, id );
									if ( dbCreateLine == null )
									{
										addColumn( realUpdates, dataSource, tableReference, javaCreateLine, lastColumn );
									}
								}
								else if ( isKeyDefinition( javaCreateLine ) || isPrimaryKeyDefinition( javaCreateLine ) )
								{
									final String dbCreateLine = getKeyDefinition( dbCreateLines, id );
									if ( dbCreateLine == null )
									{
										addKey( realUpdates, dataSource, tableReference, javaCreateLine );
									}
								}
								else
								{
									throw new RuntimeException( "Unrecognized create line in DB: " + javaCreateLine + " from create statement: " + javaCreateStatement );
								}
							}

							if ( isColumnDefinition( javaCreateLine ) )
							{
								lastColumn = id;
							}
						}
					}
				}
				else // table does not exist
				{
					if ( createIfNecessary )
					{
						if ( verbose )
						{
							System.out.println( "Creating " + tableReference );
						}

						statement.executeUpdate( javaCreateStatement.replaceAll( "\\);$", ") character set " + CHARACTER_SET + ';' ) );
					}
				}
			}
			finally
			{
				statement.close();
			}
		}
		finally
		{
			connection.close();
		}
	}

	/**
	 * Returns the 'CREATE TABLE' statement for the given table.
	 *
	 * @param tableName Name of the table.
	 * @param statement Statement to use.
	 *
	 * @return 'CREATE TABLE' statement.
	 *
	 * @throws SQLException if an error occurs while accessing the database.
	 */
	@NotNull
	private static String getCreateTable( @NotNull final String tableName, @NotNull final Statement statement )
	throws SQLException
	{
		final String result;
		final String query = "show create table ";

		final ResultSet tableDescriptionResultSet = statement.executeQuery( query + tableName );
		try
		{
			if ( !tableDescriptionResultSet.next() )
			{
				throw new RuntimeException( "No result from: " + query );
			}

			result = tableDescriptionResultSet.getString( "Create Table" );
		}
		finally
		{
			tableDescriptionResultSet.close();
		}
		return result;
	}

	/**
	 * Get {@link ClassHandler} for a table class.
	 *
	 * @param tableClass Table class to get handler for.
	 *
	 * @return {@link ClassHandler}.
	 */
	@NotNull
	protected static ClassHandler getClassHandler( final Class tableClass )
	{
		return DbServices.getClassHandler( tableClass );
	}

	/**
	 * Get CREATE lines from CREATE statement.
	 *
	 * @param createStatement CREATE statement.
	 *
	 * @return CREATE lines.
	 */
	private static List getCreateLines( final String createStatement )
	{
		final List result = TextTools.tokenize( createStatement.replaceAll( "[\\t ]+", " " ), '\n', true );

		int i = 0;
		while ( i < result.size() )
		{
			final String line = result.get( i );

			if ( line.startsWith( "CREATE " ) ||
			     TextTools.startsWith( line, ')' ) )
			{
				result.remove( i );
			}
			else
			{
				final StringBuilder sb = new StringBuilder( line );
				int length = sb.length();

				/* remove '\s*,' suffix */
				if ( TextTools.endsWith( sb, ',' ) )
				{
					length--;
					while ( ( length > 0 ) && Character.isWhitespace( sb.charAt( length - 1 ) ) )
					{
						length--;
					}
					sb.setLength( length );
				}

				/* convert all unquoted parts of the string to upper case */
				char quote = '\0';
				for ( int pos = 0; pos < length; pos++ )
				{
					final char ch = sb.charAt( pos );
					if ( quote != '\0' )
					{
						if ( ch == quote )
						{
							quote = '\0';
						}
					}
					else if ( ch == '\'' || ch == '`' || ch == '"' )
					{
						quote = ch;
					}
					else
					{
						sb.setCharAt( pos, Character.toUpperCase( ch ) );
					}
				}

				result.set( i++, sb.toString() );
			}
		}

		return result;
	}

	/**
	 * Test if the specified CREATE line is a column definition.
	 *
	 * @param createLine CREATE line.
	 *
	 * @return {@code true} if the CREATE line is a column definition; {@code
	 * false} otherwise.
	 */
	private static boolean isColumnDefinition( final CharSequence createLine )
	{
		return TextTools.startsWith( createLine, '`' );
	}

	/**
	 * Get definition for the specified column.
	 *
	 * @param createLines CREATE lines.
	 * @param columnName  Name of column.
	 *
	 * @return CREATE line with column definition; {@code null} if not found.
	 */
	@Nullable
	private static String getColumnDefinition( final Iterable createLines, final String columnName )
	{
		String result = null;

		for ( final String createLine : createLines )
		{
			if ( isColumnDefinition( createLine ) && columnName.equalsIgnoreCase( getFirstID( createLine ) ) )
			{
				result = createLine;
				break;
			}
		}

		return result;
	}

	/**
	 * Test if the specified CREATE line is a key definition.
	 *
	 * @param createLine CREATE line.
	 *
	 * @return {@code true} if the CREATE line is a key definition; {@code
	 * false} otherwise.
	 */
	private static boolean isKeyDefinition( final String createLine )
	{
		return createLine.startsWith( "KEY " ) || createLine.startsWith( "UNIQUE KEY " );
	}

	/**
	 * Test if the specified CREATE line is a primary key definition.
	 *
	 * @param createLine CREATE line.
	 *
	 * @return {@code true} if the CREATE line is a primary key definition;
	 * {@code false} otherwise.
	 */
	private static boolean isPrimaryKeyDefinition( final String createLine )
	{
		return createLine.startsWith( "PRIMARY KEY " );
	}

	/**
	 * Get definition for the specified key.
	 *
	 * @param createLines CREATE lines.
	 * @param keyName     Name of key.
	 *
	 * @return CREATE line with key definition; {@code null} if not found.
	 */
	@Nullable
	private static String getKeyDefinition( final Iterable createLines, final String keyName )
	{
		String result = null;

		for ( final String createLine : createLines )
		{
			if ( isKeyDefinition( createLine ) && keyName.equalsIgnoreCase( getFirstID( createLine ) ) )
			{
				result = createLine;
				break;
			}
		}

		return result;
	}

	/**
	 * Get first back-quoted identifier in CREATE line.
	 *
	 * @param createLine CREATE line for key.
	 *
	 * @return First encountered back-quoted identifier.
	 */
	@Nullable
	private static String getFirstID( final String createLine )
	{
		final int start = createLine.indexOf( (int)'`' ) + 1;
		final int end = ( start > 0 ) ? createLine.indexOf( (int)'`', start ) : -1;
		return ( end > start ) ? createLine.substring( start, end ) : null;
	}

	/**
	 * Add column to table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param createLine     CREATE line for column.
	 * @param after          Name of column after which to add the column.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void addColumn( final boolean realUpdates, final DataSource dataSource, final String tableReference, final String createLine, final String after )
	throws SQLException
	{
		executeUpdate( realUpdates, dataSource, "ALTER TABLE " + tableReference + " ADD " + createLine + ( ( after != null ) ? " AFTER `" + after + '`' : "" ) + ';' );
	}

	/**
	 * Modify column of table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param force             Perform updates that (may) cause data loss.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param columnName     Name of column.
	 * @param oldCreateLine  Old CREATE line for column (from database).
	 * @param newCreateLine  New CREATE line for key (from specification).
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void modifyColumn( final boolean realUpdates, final boolean force, final DataSource dataSource, final String tableReference, final String columnName, final String oldCreateLine, final String newCreateLine )
	throws SQLException
	{
		final List oldTokens = TextTools.tokenize( oldCreateLine, ' ', true );
		final String oldType = oldTokens.get( 1 );
		final String oldBaseType = oldType.replaceAll( "\\(.*", "" );
		final List newTokens = TextTools.tokenize( newCreateLine, ' ', true );
		final String newType = newTokens.get( 1 );
		final String newBaseType = newType.replaceAll( "\\(.*", "" );

		final String updatePrefix = "ALTER TABLE " + tableReference + " MODIFY ";
		System.out.println( "     (" + TextTools.getFixed( "old definition:", updatePrefix.length() - 2, true, '.' ) + ' ' + oldCreateLine + ')' );

		if ( oldType.equalsIgnoreCase( newType ) ||
		     ( "bigint".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "blob".equalsIgnoreCase( oldBaseType ) && "mediumblob".equalsIgnoreCase( newBaseType ) ) ||
		     ( "char".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "decimal".equalsIgnoreCase( oldBaseType ) && "double".equalsIgnoreCase( newBaseType ) ) ||
		     ( "decimal".equalsIgnoreCase( oldBaseType ) && "float".equalsIgnoreCase( newBaseType ) ) ||
		     ( "double".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "double".equalsIgnoreCase( oldBaseType ) && "float".equalsIgnoreCase( newBaseType ) ) ||
		     ( "double".equalsIgnoreCase( oldBaseType ) && "decimal".equalsIgnoreCase( newBaseType ) ) ||
		     ( "enum".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "float".equalsIgnoreCase( oldBaseType ) && "double".equalsIgnoreCase( newBaseType ) ) ||
		     ( "float".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "float".equalsIgnoreCase( oldBaseType ) && "decimal".equalsIgnoreCase( newBaseType ) ) ||
		     ( "int".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "int".equalsIgnoreCase( oldBaseType ) && "double".equalsIgnoreCase( newBaseType ) ) ||
		     ( "int".equalsIgnoreCase( oldBaseType ) && "float".equalsIgnoreCase( newBaseType ) ) ||
		     ( "smallint".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "tinyint".equalsIgnoreCase( oldBaseType ) && "int".equalsIgnoreCase( newBaseType ) ) ||
		     ( "text".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "text".equalsIgnoreCase( oldBaseType ) && "mediumtext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "text".equalsIgnoreCase( oldBaseType ) && "tinytext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "mediumtext".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "mediumtext".equalsIgnoreCase( oldBaseType ) && "text".equalsIgnoreCase( newBaseType ) ) ||
		     ( "mediumtext".equalsIgnoreCase( oldBaseType ) && "tinytext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "tinytext".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "tinytext".equalsIgnoreCase( oldBaseType ) && "text".equalsIgnoreCase( newBaseType ) ) ||
		     ( "tinytext".equalsIgnoreCase( oldBaseType ) && "mediumtext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "longtext".equalsIgnoreCase( oldBaseType ) && "text".equalsIgnoreCase( newBaseType ) ) ||
		     ( "longtext".equalsIgnoreCase( oldBaseType ) && "mediumtext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "char".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "varchar".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "enum".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "text".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "mediumtext".equalsIgnoreCase( newBaseType ) ) ||
		     ( "varchar".equalsIgnoreCase( oldBaseType ) && "tinytext".equalsIgnoreCase( newBaseType ) ) )
		{
			executeUpdate( realUpdates, dataSource, updatePrefix + newCreateLine + ';' );
		}
		else if ( "enum('true','false')".equalsIgnoreCase( oldType ) && "tinyint(1)".equalsIgnoreCase( newType ) )
		{
			executeUpdate( realUpdates, dataSource, updatePrefix + newCreateLine + ';' );
			executeUpdate( realUpdates, dataSource, "UPDATE " + tableReference + " SET `" + columnName + "`=(`" + columnName + "`=1);" );
		}
		else if ( "enum('false','true')".equalsIgnoreCase( oldType ) && "tinyint(1)".equalsIgnoreCase( newType ) )
		{
			executeUpdate( realUpdates, dataSource, updatePrefix + newCreateLine + ';' );
			executeUpdate( realUpdates, dataSource, "UPDATE " + tableReference + " SET `" + columnName + "`=(`" + columnName + "`=2);" );
		}
		else if ( "enum".equalsIgnoreCase( oldBaseType ) && "enum".equalsIgnoreCase( newBaseType ) )
		{
			if ( !force )
			{
				final Set oldValues = getEnumValues( oldType );
				final Set newValues = getEnumValues( newType );

				if ( !newValues.containsAll( oldValues ) )
				{
					throw new RuntimeException( "Possible data loss due to modified enumeration type\nOld create line: " + oldCreateLine + "\nNew create line: " + newCreateLine + "\nTable reference: " + tableReference );
				}
			}

			executeUpdate( realUpdates, dataSource, updatePrefix + newCreateLine + ';' );
		}
		else if ( ( "tinytext".equalsIgnoreCase( oldBaseType ) && "tinyblob".equalsIgnoreCase( newBaseType ) ) ||
		          ( "text".equalsIgnoreCase( oldBaseType ) && "blob".equalsIgnoreCase( newBaseType ) ) ||
		          ( "mediumtext".equalsIgnoreCase( oldBaseType ) && "mediumblob".equalsIgnoreCase( newBaseType ) ) )
		{
			if ( oldCreateLine.contains( "CHARACTER SET" ) )
			{
				// Convert to default character set first.
				executeUpdate( realUpdates, dataSource, updatePrefix + '`' + columnName + "` " + oldBaseType + ';' );
			}
			executeUpdate( realUpdates, dataSource, updatePrefix + newCreateLine + ';' );
		}
		else
		{
			throw new RuntimeException( "Don't know how to convert '" + oldType + "' to '" + newType + "'\nOld create line: " + oldCreateLine + "\nNew create line: " + newCreateLine + "\nTable reference: " + tableReference );
		}
	}

	/**
	 * Returns the enumeration values from an SQL enum type definition.
	 *
	 * @param type Enumeration type definition.
	 *
	 * @return Enumeration values.
	 */
	private static Set getEnumValues( final String type )
	{
		final String valueList = type.substring( type.indexOf( '(' ) + 1, type.indexOf( ')' ) );
		final String[] values = valueList.split( "," );
		// TODO: Remove quotes, maybe.
		return ( values == null ) ? Collections.emptySet() : new HashSet( Arrays.asList( values ) );
	}

	/**
	 * Drop column of table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param columnName     Name of column.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void dropColumn( final boolean realUpdates, final DataSource dataSource, final String tableReference, final String columnName )
	throws SQLException
	{
		executeUpdate( realUpdates, dataSource, "ALTER TABLE " + tableReference + " DROP `" + columnName + "`;" );
	}

	/**
	 * Add key to table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param newCreateLine  CREATE line for key.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void addKey( final boolean realUpdates, final DataSource dataSource, final String tableReference, final String newCreateLine )
	throws SQLException
	{
		executeUpdate( realUpdates, dataSource, "ALTER TABLE " + tableReference + " ADD " + newCreateLine + ';' );
	}

	/**
	 * Modify key of table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param keyName        Name of key.
	 * @param newCreateLine  CREATE line for key.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void modifyKey( final boolean realUpdates, final DataSource dataSource, final String tableReference, final String keyName, final String newCreateLine )
	throws SQLException
	{
		dropKey( realUpdates, dataSource, tableReference, keyName );
		addKey( realUpdates, dataSource, tableReference, newCreateLine );
	}

	/**
	 * Drop key of table.
	 *
	 * @param realUpdates    Perform real updates vs. just print queries.
	 * @param dataSource     Database source to connect to database.
	 * @param tableReference SQL reference to table.
	 * @param keyName        Name of key.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void dropKey( final boolean realUpdates, final DataSource dataSource, final String tableReference, final String keyName )
	throws SQLException
	{
		executeUpdate( realUpdates, dataSource, "ALTER TABLE " + tableReference + " DROP KEY `" + keyName + "`;" );
	}

	/**
	 * Execute update query.
	 *
	 * @param realUpdates Perform real updates vs. just print queries.
	 * @param dataSource  Database source to connect to database.
	 * @param query       Update query to execute.
	 *
	 * @throws SQLException if an error occurred while accessing the database.
	 */
	private static void executeUpdate( final boolean realUpdates, final DataSource dataSource, final String query )
	throws SQLException
	{
		if ( realUpdates )
		{
			System.out.print( "     " );
			System.out.println( query );

			final Connection connection = dataSource.getConnection();
			try
			{
				final Statement statement = connection.createStatement();
				try
				{
					statement.executeUpdate( query );
				}
				finally
				{
					statement.close();
				}
			}
			finally
			{
				connection.close();
			}
		}
		else
		{
			System.out.print( "###  " );
			System.out.println( query );
		}
	}

	/**
	 * Utility/Application class is not supposed to be instantiated.
	 */
	private DatabaseTableUpdater()
	{
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy