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

net.sourceforge.squirrel_sql.fw.sql.SQLUtilities Maven / Gradle / Ivy

/*
 * Copyright (C) 2007 Rob Manning
 * [email protected]
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package net.sourceforge.squirrel_sql.fw.sql;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

import net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DatabaseSpecificEscape;
import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;

/**
 * @author manningr : except where noted
 */
public class SQLUtilities
{

	/** Logger for this class. */
	private final static ILogger s_log = LoggerController.createLogger(SQLUtilities.class);

	/**
	 * Contributed by Thomas Mueller to handle doubling quote characters found in an identifier. In H2 and
	 * other dbs, the following statement creates a table with an embedded quote character: CREATE TABLE
	 * "foo""bar" (someid int); However, what is returned by the driver for table name is: foo"bar The reason
	 * is simple. Just like embedded quotes in SQL strings, such as: select 'I don''t know' from test
	 * Similarly, embedded quote characters can also appear in identifiers such as table names, by doubling (or
	 * quoting, if you will) the quote.
	 * 
	 * @param s
	 *           the string to have embedded quotes expanded.
	 * @return a new string with any embedded quotes doubled, or null if null is passed.
	 * @author Thomas Mueller
	 */
	public static String quoteIdentifier(String s)
	{
		if (s == null)
		{
			return null;
		}
		StringBuilder buff = null;
		buff = new StringBuilder();
		for (int i = 0; i < s.length(); i++)
		{
			char c = s.charAt(i);
			if (c == '"' && i != 0 && i != s.length() - 1)
			{
				buff.append(c);
			}
			buff.append(c);
		}
		String result = buff.toString();
		return result;
	}

	/**
	 * Reverses the insertion order list. Just a convenience method.
	 * 
	 * @param md
	 * @param tables
	 * @return
	 * @throws SQLException
	 */
	public static List getDeletionOrder(List tables, SQLDatabaseMetaData md,
		ProgressCallBack callback) throws SQLException
	{
		List insertionOrder = getInsertionOrder(tables, md, callback);
		Collections.reverse(insertionOrder);
		return insertionOrder;
	}

	/**
	 * Returns the specified list of tables in an order such that insertions into all tables will satisfy any
	 * foreign key constraints. This will not correctly handle recursive constraints. This algorthim was
	 * adapted from SchemaSpy class/method: net.sourceforge.schemaspy.SchemaSpy.sortTablesByRI() unattached -
	 * tables that have no dependencies on other tables parents - tables that only have children children -
	 * tables that only have parents sandwiches - tables that have both parents and children - as in the
	 * "sandwich" generation. The first SQLException encountered while attempting to get FK information on any
	 * table will cause this to bail it's effort to re-order the list and the list will be returned as it came
	 * in - there's no point in spewing exceptions to end up with a flawed result; just give up.
	 * 
	 * @param md
	 * @param tables
	 * @param listener
	 * @return
	 * @throws SQLException
	 */
	public static List getInsertionOrder(List tables, SQLDatabaseMetaData md,
		ProgressCallBack callback) throws SQLException
	{
		List result = new ArrayList();
		// tables that are netiher children nor parents - utility tables
		List unattached = new ArrayList();
		// tables that have at least one parent table
		List children = new ArrayList();
		// tables that have at least one child table
		List parents = new ArrayList();
		// tables that have at least one child table and have a least one parent table
		List sandwiches = new ArrayList();
		ITableInfo lastTable = null;
		try
		{
			for (ITableInfo table : tables)
			{
				lastTable = table;
				callback.currentlyLoading(table.getSimpleName());
				ForeignKeyInfo[] importedKeys = getImportedKeys(table, md);
				ForeignKeyInfo[] exportedKeys = getExportedKeys(table, md);

				if (importedKeys != null && importedKeys.length == 0 && exportedKeys.length == 0)
				{
					unattached.add(table);
					continue;
				}
				if (exportedKeys != null && exportedKeys.length > 0)
				{
					if (importedKeys != null && importedKeys.length > 0)
					{
						sandwiches.add(table);
					} else
					{
						parents.add(table);
					}
					continue;
				}
				if (importedKeys != null && importedKeys.length > 0)
				{
					children.add(table);
				}
			}
			reorderTables(sandwiches);

			for (ITableInfo info : unattached)
			{
				result.add(info);
			}
			for (ITableInfo info : parents)
			{
				result.add(info);
			}
			for (ITableInfo info : sandwiches)
			{
				result.add(info);
			}
			for (ITableInfo info : children)
			{
				result.add(info);
			}
			if (result.size() != tables.size())
			{
				s_log.error("getInsertionOrder(): failed to obtain a result table list " + "(" + result.size()
					+ ") that is the same size as the input table " + "list (" + tables.size()
					+ ") - returning the original unordered " + "list");
				result = tables;
			}
		} catch (Exception e)
		{
			if (lastTable != null)
			{
				String tablename = lastTable.getSimpleName();
				s_log.error("Unexpected exception while getting foreign key info for " + "table " + tablename, e);
			} else
			{
				s_log.error("Unexpected exception while getting foreign key info ", e);
			}
			result = tables;
		}
		return result;
	}

	public static ForeignKeyInfo[] getImportedKeys(ITableInfo ti, SQLDatabaseMetaData md) throws SQLException
	{
		ForeignKeyInfo[] result = ti.getImportedKeys();
		if (result == null)
		{
			result = md.getImportedKeysInfo(ti);
			// Avoid the hit next time
			ti.setImportedKeys(result);
		}
		return result;
	}

	/**
	 * @param ti
	 * @param md
	 * @return
	 * @throws SQLException
	 */
	public static ForeignKeyInfo[] getExportedKeys(ITableInfo ti, SQLDatabaseMetaData md) throws SQLException
	{
		ForeignKeyInfo[] result = ti.getExportedKeys();
		if (result == null)
		{
			result = md.getExportedKeysInfo(ti);
			// Avoid the hit next time
			ti.setExportedKeys(result);
		}
		return result;
	}

	/**
	 * @param sandwiches
	 */
	private static void reorderTables(List sandwiches)
	{
		Collections.sort(sandwiches, new TableComparator());
	}

	/**
	 * Returns a list of table names that have Primary Keys that are referenced by foreign key constraints on
	 * columns in the specified list of tables, that are not also contained in the specified list
	 * 
	 * @param md
	 * @param tables
	 * @return
	 * @throws SQLException
	 */
	public static List getExtFKParents(SQLDatabaseMetaData md, List tables)
		throws SQLException
	{
		List result = new ArrayList();
		HashSet tableNames = new HashSet();

		for (ITableInfo table : tables)
		{
			tableNames.add(table.getSimpleName());
		}

		for (ITableInfo table : tables)
		{
			ForeignKeyInfo[] importedKeys = md.getImportedKeysInfo(table);
			for (int i = 0; i < importedKeys.length; i++)
			{
				ForeignKeyInfo info = importedKeys[i];
				String pkTable = info.getPrimaryKeyTableName();
				if (!tableNames.contains(pkTable))
				{
					result.add(pkTable);
				}
			}
		}
		return result;
	}

	/**
	 * Returns a list of table names that have Foreign keys that reference Primary Keys in the specified List
	 * of tables, but that are not also contained in the list of tables.
	 * 
	 * @param md
	 * @param tables
	 * @return
	 * @throws SQLException
	 */
	public static List getExtFKChildren(SQLDatabaseMetaData md, List tables)
		throws SQLException
	{
		List result = new ArrayList();
		HashSet tableNames = new HashSet();

		for (ITableInfo table : tables)
		{
			tableNames.add(table.getSimpleName());
		}

		for (ITableInfo table : tables)
		{
			ForeignKeyInfo[] exportedKeys = md.getExportedKeysInfo(table);
			for (int i = 0; i < exportedKeys.length; i++)
			{
				ForeignKeyInfo info = exportedKeys[i];
				String fkTable = info.getForeignKeyTableName();
				if (!tableNames.contains(fkTable))
				{
					result.add(fkTable);
				}
			}
		}
		return result;
	}

	/**
	 * Closes the specified ResultSet safely (with no exceptions) and logs a debug message if SQLException is
	 * encountered. This will not close the Statement that created the ResultSet.
	 * 
	 * @param rs
	 *           the ResultSet to close - it can be null.
	 */
	public static void closeResultSet(ResultSet rs)
	{
		closeResultSet(rs, false);
	}

	/**
	 * Closes the specified ResultSet safely (with no exceptions) and logs a debug message if SQLException is
	 * encountered. This will also close the Statement that created the ResultSet if closeStatement boolean is
	 * true.
	 * 
	 * @param rs
	 *           the ResultSet to close - it can be null.
	 * @param closeStatement
	 *           if true, will close the Statement that created this ResultSet; false - will not close the
	 *           Statement.
	 */
	public static void closeResultSet(ResultSet rs, boolean closeStatement)
	{
		if (rs == null)
		{
			return;
		}
		// Close the ResultSet
		try
		{
			rs.close();
		} catch (SQLException e)
		{
			if (s_log.isDebugEnabled())
			{
				s_log.debug("Unexpected exception while closing ResultSet: " + e.getMessage(), e);
			}
		}
		if (closeStatement)
		{
			// Close the ResultSet's Statement if it is non-null. This frees open
			// cursors.

			try
			{
				Statement stmt = rs.getStatement();
				if (stmt != null)
				{
					stmt.close();
				}
			} catch (SQLException e)
			{
				if (s_log.isDebugEnabled())
				{
					s_log.debug("Unexpected exception while closing " + "Statement: " + e.getMessage(), e);
				}
			}
		}
	}

	/**
	 * Closes the specified Statement safely (with no exceptions) and logs a debug message if SQLException is
	 * encountered.
	 * 
	 * @param stmt
	 *           the Statement to close - it can be null.
	 */
	public static void closeStatement(Statement stmt)
	{
		if (stmt == null)
		{
			return;
		}
		try
		{
			stmt.close();
		} catch (SQLException e)
		{
			if (s_log.isDebugEnabled())
			{
				s_log.error("Unexpected exception while closing Statement: " + e.getMessage(), e);
			}
		}
	}

	/**
	 * @author manningr
	 */
	private static class TableComparator implements Comparator, Serializable
	{

		private static final long serialVersionUID = 1L;

		public int compare(ITableInfo t1, ITableInfo t2)
		{
			ForeignKeyInfo[] t1ImportedKeys = t1.getImportedKeys();
			for (int i = 0; i < t1ImportedKeys.length; i++)
			{
				ForeignKeyInfo info = t1ImportedKeys[i];
				if (info.getPrimaryKeyTableName().equals(t2.getSimpleName()))
				{
					// t1 depends on t2
					return 1;
				}
			}
			ForeignKeyInfo[] t2ImportedKeys = t2.getImportedKeys();
			for (int i = 0; i < t2ImportedKeys.length; i++)
			{
				ForeignKeyInfo info = t2ImportedKeys[i];
				if (info.getPrimaryKeyTableName().equals(t1.getSimpleName()))
				{
					// t2 depends on t1
					return -1;
				}
			}
			if (t1.getImportedKeys().length > t2ImportedKeys.length)
			{
				return 1;
			}
			if (t1.getImportedKeys().length < t2ImportedKeys.length)
			{
				return -1;
			}
			return 0;
		}

	}

	/**
	 * When strings are used in the WHERE clause, any single quote characters must be
	 * "escaped" so that they are not confused with the "end of string"
	 * single quote used by SQL.  The escape sequence is that a single quote
	 * is represented by two single quotes in a row.
	 */
	public static String escapeLine(String s, ISQLDatabaseMetaData md) {
		String retvalue = s;
		if (s.indexOf ("'") != -1 ) {
			StringBuffer hold = new StringBuffer();
			char c;
			for(int i=0; i < s.length(); i++ ) {
				if ((c=s.charAt(i)) == '\'' ) {
					hold.append ("''");
				}
				else {
					hold.append(c);
				}
			}
			retvalue = hold.toString();
		}
		return DatabaseSpecificEscape.escapeSQL(retvalue, md);
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy