/**
 * Class copyright 2003 by the Ravensfield Digital Resource Group, Ltd, Granville, OH.
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written agreement
 * is hereby granted, provided that the above copyright notice and this
 * paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE RAVENSFIELD DIGITAL RESOURCE GROUP, LTD HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 * (Quick readers will recognize that as the stock BSD license)
 */

package org.postgresql.ers;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.StringTokenizer;

/**
 *  Superclass for many of the eRServer support routines.
 *
 * @author     Andrew Rawnsley
 */
public abstract class ERS {

	/**
	 *  Verbose flag
	 */
	protected static boolean quiet = false;

	boolean schemaSupport = true;
	String ersSchema = null;

	protected String createRelName(String relName) {
		if (ersSchema == null || !schemaSupport) {
			return relName;
		}

		return ersSchema+"."+relName;
	}

	/**
	 *  Create a connection
	 *
	 * @param  connectionString  Connection string
	 * @param  user              User ID
	 * @param  passwd            Password
	 * @return                   New connection, or null on exception
	 */
	public static Connection getConnection(String connectionString, String user, String passwd) {

		try {
			Class.forName("org.postgresql.Driver");
			return DriverManager.getConnection(connectionString, user, passwd);
		} catch (ClassNotFoundException ex) {
			System.err.println(connectionString);
			ex.printStackTrace();
			return null;
		} catch (SQLException sx) {
			System.err.println(connectionString);
			sx.printStackTrace();
			return null;
		}

	}

	/**
	 *  Splits a property into a name/value pair
	 *
	 * @param  s  A property string (x=y)
	 * @return    The property value
	 */
	public static String[] getProperty(String s) {
		int pos;
		String[] prop = new String[2];

		if ((pos = s.indexOf("=")) >= 0) {
			prop[0] = s.substring(0, pos);
			prop[1] = s.substring(pos + 1);
			return prop;
		}
		return null;
	}

	/**
	 *  Sets the quiet attribute
	 *
	 * @param  b  The new quiet value
	 */
	public static void setQuiet(boolean b) {
		quiet = b;
	}

	/**
	 * Take a delimited list as a string and tokenize it into an array
	 *
	 * @param  s      String to split
	 * @param  delim  Delimiter
	 * @return        Array of tokens
	 */
	public static String[] split(String s, String delim) {
		if (s == null) {
			return new String[0];
		}
		StringTokenizer tok = new StringTokenizer(s, delim);
		String[] list = new String[tok.countTokens()];
		for (int i = 0; i < list.length; i++) {
			list[i] = tok.nextToken();
		}

		return list;
	}

	/**
	 *  Copy a table from primary to replicant
	 *
	 * @param  table       Table to copy
	 * @param  primary     Connection to the primary
	 * @param  replicant   Connection to the replicant
	 * @param  uniqColumn  Name of the unique column
	 */
	protected void copyTable(String table, String uniqColumn, Connection primary, Connection replicant) {

		Statement stmnt = null;
		Statement seqStmnt = null;
		ResultSet rs;
		DatabaseMetaData metaData = null;
		String columnName;
		String sequenceName;
		String columnType;
		String defaultClause;
		int columnSize;
		int columnDec;
		int nullable;
		int pos1;
		int pos2;
		int pos;
		String indexName;
		String schema = null;
		String tableName = null;

		String keySchema;
		String keyTable;
		String keyColumn;
		short updateRule;
		short deleteRule;
		String keyName;

		if (schemaSupport) {
			// Separate the table name to schema and table, if necessary
			if ((pos = table.indexOf(".")) >= 0) {
				schema = table.substring(0, pos);
				tableName = table.substring(pos + 1);
			} else {
				tableName = table;
				schema = "public";
				table = "public." + table;
			}
		} else {
			tableName = table;
			schema = null;
		}

		try {

			// Prepare the statement to add sequences to the replicant
			seqStmnt = replicant.createStatement();

			// Check for existance of the schema
			if (schema != null) {
				if (!schemaExists(schema, replicant)) {
					seqStmnt.execute("CREATE SCHEMA " + schema);
				}
			}

			// Get the column definitions for the table in question
			metaData = primary.getMetaData();
			rs = metaData.getColumns(null, schema, tableName, "%");

			// Start the CREATE TABLE statement
			StringBuffer sb = new StringBuffer("CREATE TABLE ").append(table).append(" (");

			boolean first = true;

			if (!quiet) {
				System.out.println("Creating table on replicant...");
			}
			// Step through each column
			while (rs.next()) {

				if (!first) {
					sb.append(", ");
				} else {
					first = false;
				}

				columnName = rs.getString(4);
				columnType = rs.getString(6);
				columnSize = rs.getInt(7);
				columnDec = rs.getInt(9);
				nullable = rs.getInt(11);
				defaultClause = rs.getString(13);

				if (columnType.equals("bpchar")) {
					columnType = "char";
				}

				sb.append(columnName).append(' ').append(columnType);

				// Deal with size/precision
				if (columnType.equals("varchar") || columnType.equals("char")) {
					sb.append('(').append(columnSize).append(") ");
				} else if (columnType.equals("numeric")) {
					sb.append('(').append(columnSize).append(',').append(columnDec).append(") ");
				} else {
					sb.append(' ');
				}

				// nullable check
				if (nullable == 0) {
					sb.append(" NOT NULL ");
				}

				// Check the default clause to see if its a sequence. If so, isolate the sequence name and create the sequence
				if (defaultClause != null) {
					if (defaultClause.toLowerCase().indexOf("nextval") >= 0) {
						pos1 = defaultClause.indexOf("'");
						pos2 = defaultClause.lastIndexOf("'");
						if (pos1 != -1 && pos2 != pos1) {
							sequenceName = defaultClause.substring(pos1 + 1, pos2).replace('"', ' ').trim();
							if (!sequenceExists(sequenceName, replicant)) {
								seqStmnt.execute("CREATE SEQUENCE " + sequenceName);
							}
						}
					}

					sb.append(" DEFAULT ").append(defaultClause);

				}
			}

			// Add the primary key portion, if necessary
			first = true;
			rs = metaData.getPrimaryKeys(null, schema, tableName);

			while (rs.next()) {

				if (first) {
					sb.append(", PRIMARY KEY (");
					first = false;
				} else {
					sb.append(',');
				}

				sb.append(rs.getString(4));

			}

			// if there were no primary keys, first won't be set to false, and we don't need to add an ending ')'
			if (!first) {
				sb.append(')');
			}

			// end the statement
			sb.append(')');

			// Create the table
			stmnt = replicant.createStatement();
			stmnt.execute(sb.toString());
			stmnt.close();

			// Copy data from one to the other using pg_dump through a temp file
			if (!quiet) {
				System.out.println("Copying data...");
			}

			copyTableData(primary, replicant, table);

			// Get foreign key references. Add them to the _rserv_failover_constraints_ table.
			rs = metaData.getImportedKeys(null, schema, tableName);

			stmnt = replicant.createStatement();

			while (rs.next()) {
				keySchema = rs.getString(2);
				keyTable = rs.getString(3);
				keyColumn = rs.getString(4);
				columnName = rs.getString(8);
				updateRule = rs.getShort(10);
				deleteRule = rs.getShort(11);
				keyName = rs.getString(12);
				if (keyName == null) {
					keyName = keyTable + "_fkey_" + columnName;
				}

				sb = new StringBuffer("INSERT INTO "+createRelName("_rserv_failover_constraints_")+" VALUES ('CREATE CONSTRAINT ").append(keyTable).append("_fkey_").append(columnName).append(" FOREIGN KEY (")
						.append(columnName).append(") REFERENCES ");

				if (schemaSupport) {
					sb.append(keySchema).append('.').append(keyTable).append('(').append(keyColumn).append(')');
				} else {
					sb.append(keyTable).append('(').append(keyColumn).append(')');
				}

				switch (updateRule) {
								case DatabaseMetaData.importedKeyCascade:
									sb.append(" ON UPDATE CASCADE ");
									break;
								case DatabaseMetaData.importedKeySetNull:
									sb.append(" ON UPDATE SET NULL ");
									break;
								case DatabaseMetaData.importedKeySetDefault:
									sb.append(" ON UPDATE SET DEFAULT ");
									break;
				}

				switch (deleteRule) {
								case DatabaseMetaData.importedKeyCascade:
									sb.append(" ON DELETE CASCADE ");
									break;
								case DatabaseMetaData.importedKeySetNull:
									sb.append(" ON DELETE SET NULL ");
									break;
								case DatabaseMetaData.importedKeySetDefault:
									sb.append(" ON DELETE SET DEFAULT ");
									break;
				}
				sb.append("')");
				stmnt.execute(sb.toString());
				stmnt.close();

			}

			// Recreate indexes
			seqStmnt = primary.createStatement();
			stmnt = replicant.createStatement();
			if (schemaSupport) {
				rs = seqStmnt.executeQuery("SELECT * FROM pg_indexes WHERE schemaname='" + (schema == null ? "public" : schema) +
						"' AND tablename='" + tableName + "'");
			} else {
				rs = seqStmnt.executeQuery("SELECT * FROM pg_indexes WHERE tablename='" + tableName + "'");
			}

			while (rs.next()) {
				indexName = rs.getString(3);
				if (!indexName.endsWith("_pkey")) {
					// skip primary keys
					stmnt.execute(rs.getString(4));
				}
			}

			rs.close();

			// Insert the table/column into the control tables
			stmnt = replicant.createStatement();
			String q;

			q = "INSERT INTO "+createRelName("_rserv_slave_tables_")+" (tname, cname, reloid, key) " +
						"SELECT relname, attname, oid, attnum  " +
						"FROM "+createRelName("_ers_class_attr") +
						" WHERE relname = '" + table + "' " +
						((schemaSupport) ? " AND schemaname = '"+schema+"' " : " ") +
						" AND attname = '" + uniqColumn + "'";


			stmnt.executeUpdate(q);
			stmnt.close();

		} catch (SQLException sx) {
			sx.printStackTrace();
		} finally {

			// Close the statements, if necessary
			try {
				if (stmnt != null) {
					stmnt.close();
				}
			} catch (SQLException ex) {

			}
			try {
				if (seqStmnt != null) {
					seqStmnt.close();
				}
			} catch (SQLException ex) {

			}
		}

	}

	/**
	 *  Description of the Method
	 *
	 * @param  src    Parameter
	 * @param  dest   Parameter
	 * @param  table  Parameter
	 */
	protected void copyTableData(Connection src, Connection dest, String table) {

		PreparedStatement pstmnt = null;
		Statement stmnt = null;
		ResultSetMetaData rsmd;
		ResultSet rs;
		int index;
		int i;
		double d;
		boolean bit;

		try {
			stmnt = src.createStatement();
			rs = stmnt.executeQuery("SELECT * FROM " + table);
			rsmd = rs.getMetaData();
			StringBuffer sb = new StringBuffer("INSERT INTO ").append(table).append(" (");
			for (i = 0; i < rsmd.getColumnCount(); i++) {
				if (i != 0) {
					sb.append(',');
				}
				sb.append(rsmd.getColumnName(i + 1));
			}

			sb.append(") VALUES (");

			for (i = 0; i < rsmd.getColumnCount(); i++) {
				if (i != 0) {
					sb.append(',');
				}
				sb.append('?');
			}
			sb.append(')');

			pstmnt = dest.prepareStatement(sb.toString());

			while (rs.next()) {

				for (index = 1; index <= rsmd.getColumnCount(); index++) {
					switch (rsmd.getColumnType(index)) {

									case java.sql.Types.LONGVARCHAR:
										byte[] b = rs.getBytes(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setString(index, new String(b));
										}
										break;
									case java.sql.Types.VARCHAR:
									case java.sql.Types.CHAR:
										pstmnt.setString(index, rs.getString(index));
										break;
									case java.sql.Types.FLOAT:
									case java.sql.Types.REAL:
									case java.sql.Types.DOUBLE:
									case java.sql.Types.NUMERIC:
									case java.sql.Types.DECIMAL:
										d = rs.getDouble(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setDouble(index, d);
										}
										break;
									case java.sql.Types.BIT:
										bit = rs.getBoolean(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setBoolean(index, bit);
										}
										break;
									case java.sql.Types.TINYINT:
									case java.sql.Types.SMALLINT:
									case java.sql.Types.BIGINT:
									case java.sql.Types.INTEGER:
										i = rs.getInt(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setInt(index, i);
										}
										break;
									case java.sql.Types.NULL:
										pstmnt.setNull(index, Types.NULL);
										break;
									case java.sql.Types.TIME:
										java.sql.Time tm = rs.getTime(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setTime(index, tm);
										}
										break;
									case java.sql.Types.DATE:
										java.sql.Date date = rs.getDate(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setDate(index, date);
										}
										break;
									case java.sql.Types.TIMESTAMP:
										java.sql.Timestamp ts = rs.getTimestamp(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setTimestamp(index, ts);
										}
										break;
									case java.sql.Types.BLOB:
										java.sql.Blob blob = rs.getBlob(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setBlob(index, blob);
										}
										break;
									case java.sql.Types.CLOB:
										java.sql.Clob clob = rs.getClob(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setClob(index, clob);
										}
										break;
									case java.sql.Types.LONGVARBINARY:
									case java.sql.Types.BINARY:
									case java.sql.Types.VARBINARY:
										byte[] buffer = rs.getBytes(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setBytes(index, buffer);
										}
										break;
									case java.sql.Types.OTHER:
									default:
										Object obj = rs.getObject(index);
										if (rs.wasNull()) {
											pstmnt.setNull(index, rsmd.getColumnType(index));
										} else {
											pstmnt.setObject(index, obj);
										}
										break;
					}

				}

				pstmnt.execute();
			}

		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			try {
				if (stmnt != null) {
					stmnt.close();
				}
			} catch (Exception ex) {}
			try {
				if (pstmnt != null) {
					pstmnt.close();
				}
			} catch (Exception ex) {}
		}

	}

	/**
	 *  Create the _ers_class_attr view
	 *
	 * @param  c                 Connection to use
	 * @exception  SQLException  Description of the Exception
	 */
	protected void createView(Connection c) throws SQLException {
		// This is a convenience view to deal with getting the oid/attnum when adding tables with schema support. Its basically
		// the pg_tables view with the oid thrown in
		Statement stmnt = null;
		try {
			stmnt = c.createStatement();
			if (schemaSupport) {
				stmnt.execute("CREATE VIEW "+createRelName("_ers_class_attr")+" AS SELECT n.nspname AS schemaname, c.oid , c.relname AS tablename, n.nspname || '.' || c.relname AS relname, b.attnum, b.attname, b.atttypid " +
						" FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),pg_attribute b WHERE ((c.relkind = 'r'::\"char\") OR (c.relkind = 's'::\"char\")) AND c.oid = b.attrelid");
			} else {
				//stmnt.execute("CREATE VIEW _ers_class_attr AS SELECT *, relname AS tablename FROM pg_class");
				stmnt.execute("CREATE VIEW _ers_class_attr AS select a.oid, b.attnum, a.relname, a.relname AS tablename, b.attname, b.atttypid from pg_class a, pg_attribute b where a.oid = b.attrelid");
			}
		} finally {
			stmnt.close();
		}
	}

	/**
	 *  Check for the existance of the eRServer unique ID
	 *
	 * @param  table       Table to check
	 * @param  c           Connection to use
	 * @param  columnName  Description of the Parameter
	 * @return             Yes/no
	 */
	protected boolean hasERSUniq(String table, String columnName, Connection c) {

		Statement stmnt = null;
		String tableName;
		String schema;
		int pos;

		if (schemaSupport) {
			if ((pos = table.indexOf(".")) >= 0) {
				schema = table.substring(0, pos);
				tableName = table.substring(pos + 1);
			} else {
				tableName = table;
				schema = "public";
			}
		} else {
			tableName = table;
			schema = null;
		}

		try {
			ResultSet rs;
			stmnt = c.createStatement();
			rs = stmnt.executeQuery("SELECT * from "+createRelName("_ers_class_attr")+
					" WHERE tablename = '" + tableName + "' " +
					" AND attname = '" + columnName + "'" +
					((schemaSupport) ? " AND schemaName = '" + schema + "' " : ""));
			return rs.next();
		} catch (SQLException ex) {
			ex.printStackTrace();
			return false;
		} finally {
			try {
				stmnt.close();
			} catch (SQLException sx) {}
		}

	}

	/**
	 *  Description of the Method
	 *
	 * @return    Return Value
	 */
	protected boolean hasSchemaSupport() {
		return schemaSupport;
	}

	/**
	 *  Description of the Method
	 *
	 * @param  c             Parameter
	 * @param  language      Parameter
	 * @return               Return Value
	 */
	protected boolean languageExists(String language, Connection c) {
		Statement stmnt = null;
		ResultSet rs;

		try {
			stmnt = c.createStatement();
			rs = stmnt.executeQuery("SELECT * from pg_language where lanname = '" + language + "'");

			return rs.next();
		} catch (SQLException ex) {
			return false;
		} finally {
			try {
				if (stmnt != null) {
					stmnt.close();
				}
			} catch (SQLException sx) {}
		}
	}

	/**
	 *  Check to see if a relation exists
	 *
	 * @param  table  Table to check
	 * @param  c      Connection to use
	 * @return        True/false
	 */
	protected boolean relationExists(String table, Connection c) {

		Statement stmnt = null;

		try {
			stmnt = c.createStatement();
			ResultSet rs = stmnt.executeQuery("SELECT relname FROM pg_class WHERE relname = '" + table + "'");
			return rs.next();
		} catch (SQLException ex) {
			ex.printStackTrace();
			return false;
		} finally {
			try {
				stmnt.close();
			} catch (SQLException sx) {}
		}

	}

	/**
	 *  Description of the Method
	 *
	 * @param  replicant         Parameter
	 * @exception  SQLException  Description of the Exception
	 */
	protected void removeReplicant(Connection replicant) throws SQLException {
		Statement stmnt = null;
		try {
			replicant.setAutoCommit(false);
			stmnt = replicant.createStatement();
			stmnt.execute("set transaction isolation level serializable");
			stmnt.execute("DROP TABLE "+createRelName("_rserv_slave_tables_"));
			stmnt.execute("DROP TABLE "+createRelName("_rserv_slave_sync_"));
			stmnt.execute("DROP VIEW "+createRelName("_ers_class_attr"));
			if (relationExists(createRelName("_rserv_failover_constraints_"), replicant)) {
				stmnt.execute("DROP TABLE "+createRelName("_rserv_failover_constraints_"));
			}

			replicant.commit();
		} finally {
			try {
				if (stmnt != null) {
					stmnt.close();
				}
			} catch (Exception ex) {}
		}

	}

	/**
	 *  Check to see if a relation exists
	 *
	 * @param  c       Connection to use
	 * @param  schema  Parameter
	 * @return         True/false
	 */
	protected boolean schemaExists(String schema, Connection c) {

		Statement stmnt = null;

		try {
			stmnt = c.createStatement();
			ResultSet rs = stmnt.executeQuery("SELECT * FROM pg_namespace WHERE nspname = '" + schema + "'");
			return rs.next();
		} catch (SQLException ex) {
			ex.printStackTrace();
			return false;
		} finally {
			try {
				stmnt.close();
			} catch (SQLException sx) {}
		}

	}

	/**
	 *  Check to see if a relation exists
	 *
	 * @param  c    Connection to use
	 * @param  seq  Parameter
	 * @return      True/false
	 */
	protected boolean sequenceExists(String seq, Connection c) {

		Statement stmnt = null;

		try {
			stmnt = c.createStatement();
			ResultSet rs = stmnt.executeQuery("SELECT * FROM " + seq);
			return rs.next();
		} catch (SQLException ex) {
			return false;
		} finally {
			try {
				stmnt.close();
			} catch (SQLException sx) {}
		}

	}

	/**
	 *  Sets the schemaSupport attribute
	 *
	 * @param  b  The new schemaSupport value
	 */
	protected void setSchemaSupport(boolean b) {
		schemaSupport = b;
	}


	/**
	 *  Create the necessary tables/views on a new replicated server
	 *
	 * @param  replicant         Connection to use
	 * @exception  SQLException
	 */
	protected void setupReplicant(Connection replicant) throws SQLException {
		Statement stmnt = null;
		try {
			stmnt = replicant.createStatement();
			stmnt.execute("CREATE TABLE "+createRelName("_rserv_slave_tables_")+" (tname name, cname name, reloid oid, key int4)");
			stmnt.execute("CREATE TABLE "+createRelName("_rserv_slave_sync_")+" (syncid int4, synctime timestamp)");
			updateReplicant(replicant);
		} finally {
			try {
				if (stmnt != null) {
					stmnt.close();
				}
			} catch (SQLException sx) {}
		}
	}

	/**
	 *  Check to see if a table exists. This is different than relationExists in that it supports schemas.
	 *
	 * @param  table  Table to check
	 * @param  c      Connection to use
	 * @return        True/false
	 */
	protected boolean tableExists(String table, Connection c) {

		Statement stmnt = null;
		String schema;
		String tableName;
		int pos;
		String query;

		// Separate the table name to schema and table, if necessary
		if (schemaSupport) {
			if ((pos = table.indexOf(".")) >= 0) {
				schema = table.substring(0, pos);
				tableName = table.substring(pos + 1);
			} else {
				tableName = table;
				schema = "public";
			}
			query = "SELECT tablename FROM pg_tables WHERE schemaname = '" + schema + "' AND tablename = '" + tableName + "'";
		} else {
			tableName = table;
			schema = null;
			query = "SELECT tablename FROM pg_tables WHERE tablename = '" + tableName + "'";
		}

		try {
			stmnt = c.createStatement();
			ResultSet rs = stmnt.executeQuery(query);
			return rs.next();
		} catch (SQLException ex) {
			ex.printStackTrace();
			return false;
		} finally {
			try {
				stmnt.close();
			} catch (SQLException sx) {}
		}

	}

	/**
	 *  Update the slave with the appropriate additional tables/views/etc.
	 *
	 * @param  c                 Connection to use
	 * @exception  SQLException  Description of the Exception
	 */
	protected void updateReplicant(Connection c) throws SQLException {
		Statement stmnt = null;
		try {
			stmnt = c.createStatement();
			// Table to hold fk constraints on the slave for failover
			stmnt.execute("CREATE TABLE "+createRelName("_rserv_failover_constraints_")+" (constraint_clause text)");
			// Failover script
			if (languageExists("plpgsql", c)) {
				stmnt.execute("CREATE OR REPLACE FUNCTION "+createRelName("_rserv_failover_()")+" RETURNS INTEGER AS '" +
						"DECLARE" +
						"	clause RECORD; " +
						"BEGIN " +
						"FOR clause IN select * from _rserv_failover_constraints_ LOOP " +
						"EXECUTE clause.constraint_clause; " +
						"END LOOP; " +
						"RETURN 1; " +
						"end; " +
						"' LANGUAGE 'plpgsql';");
			}
			createView(c);
		} finally {
			stmnt.close();
		}
	}

	protected String getErsSchema() {
		return ersSchema;
	}

	protected void setErsSchema(String schema) {
//		ersSchema = schema;
	}

	/**
	 * Remove a table from the replication system.
	 * @param table 	Table name
	 * @param primary 	Primary connection
	 * @param replicants Replicant connections
	 * @throws SQLException
	 */
	protected void removeTable(String table, Connection primary, Connection[] replicants) throws SQLException {

		Statement stmnt = primary.createStatement();

		if (!quiet) {
			System.out.println("Dropping trigger");
		}

		stmnt.execute("DROP TRIGGER _RSERV_TRIGGER_T_  on " + table + " CASCADE");

		if (!quiet) {
			System.out.println("Removing from _rserv_tables_");
		}

		stmnt.execute("DELETE FROM " + createRelName("_rserv_tables_") + " WHERE tname = '" + table + "'");
		stmnt.close();


		// for each defined replicant, create the table (if necessary) and add to control tables
		String deleteSlaveTable = "DELETE FROM " + createRelName("_rserv_slave_tables_") + " WHERE tname='" + table + "'";

		for (int i = 0; i < replicants.length; i++) {
			if (!quiet) {
				System.out.println("Removing from _rserv_slave_tables_ on replicant " + i);
			}
			stmnt.execute(deleteSlaveTable);
			stmnt.close();
		}

	}
}

