pdbench/census/Chase/src/Chaser.java

707 lines
22 KiB
Java

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
/**
* @author Lublena
*
*/
public class Chaser
{
private Connection dbConnection;
private Statement sqlStatement;
/**
* Name of the template relation
*/
private String templateRelName;
/**
* Name of the mapping relation
*/
private String mappingRelName;
/**
* Name of the components relation
*/
private String compRelName;
/**
* Name of the world relation
*/
private String worldRelName;
/**
* Number of pairs of components that were merged during the chasing phase.
*/
public int merged;
/**
* Constructs a new chaser and initializes database connection.
* @param aDatabase Name of the database
* @param aUser Username
* @param aPassword Pasword
* @param aTemplateRelName Name of the template relation
* @param aMappingRelName Name of the mapping relation
* @param aCompRelName Name of the components relation
* @param aWorldRelName Name of the world relation
*/
public Chaser(String aDatabase, String aUser, String aPassword,
String aTemplateRelName, String aMappingRelName, String aCompRelName,
String aWorldRelName)
{
init(aDatabase, aUser, aPassword);
templateRelName = aTemplateRelName;
mappingRelName = aMappingRelName;
compRelName = aCompRelName;
worldRelName = aWorldRelName;
merged = 0;
}
/**
* Initializes database connection.
* @param aDatabase Name of the database to connect to
* @param aUser Username
* @param aPassword Password
*/
private void init(String aDatabase, String aUser, String aPassword)
{
try
{
Class.forName("org.postgresql.Driver"); //load the driver
dbConnection = DriverManager.getConnection("jdbc:postgresql:" + aDatabase,
aUser, aPassword);
sqlStatement = dbConnection.createStatement();
}
catch (SQLException e)
{
System.out.println("Could not connect to the database!");
e.printStackTrace();
return;
}
catch (ClassNotFoundException e)
{
System.out.println("Could not load database driver!");
return;
}
}
/**
* Closes connection to the database.
*
*/
public void closeConnection()
{
try
{
sqlStatement.close();
dbConnection.close();
}
catch (SQLException e)
{
// Ignore
return;
}
}
private boolean applyFD(FDependency aFD)
{
// TODO: Implement
return true;
}
/**
* Enforces an equality-generating dependency on the database by deleting invalid
* worlds.
* @param aEQD An equality-generating dependency to enforce
* @return True if the dependency was successfully enforced,
* false if the database was inconsistent.
*/
private boolean applyEQD(EQDependency aEQD)
{
// Find tuples that do not satisfy the dependency
//ArrayList<Term> terms = aEQD.getTerms();
//ArrayList<Term> rterms = aEQD.getRTerms();
//ArrayList terms = aEQD.getTerms();
ArrayList rterms = aEQD.getRTerms();
Term t = (Term) rterms.get(0);
StringBuffer leftArrayBuilder = new StringBuffer("'{\"" + t.left + "\"");
StringBuffer opArrayBuilder = new StringBuffer("'{\"" + t.op + "\"");
StringBuffer rightArrayBuilder = new StringBuffer("'{\"" + t.right + "\"");
// Construct the left, op and right arrays needed for the call to project_select
for (int i = 1; i < rterms.size(); ++i)
{
Term term = (Term) rterms.get(i);
leftArrayBuilder.append(",\"" + term.left + "\"");
opArrayBuilder.append(",\"" + term.op + "\"");
rightArrayBuilder.append(",\"" + term.right + "\"");
}
leftArrayBuilder.append("}'");
opArrayBuilder.append("}'");
rightArrayBuilder.append("}'");
// Construct command for retrieving the rows not satisfying the given dependency
StringBuffer commandBuilder = new StringBuffer("select project_select(");
commandBuilder.append("'" + templateRelName + "','" + mappingRelName + "','" +
compRelName + "','" + worldRelName + "',");
commandBuilder.append("'eqRel', 'eqMap', 'eqComp', 'eqWorld', ");
commandBuilder.append(leftArrayBuilder.toString() + "," + leftArrayBuilder.toString() + ",");
commandBuilder.append(opArrayBuilder.toString() + "," + rightArrayBuilder.toString() + ")");
String command = commandBuilder.toString();
//String command = constructStatement(rterms);
try
{
//System.out.println("Executing query " + commandBuilder.toString());
sqlStatement.execute(command);
StringBuffer sql = new StringBuffer("SELECT cid, hid FROM " + mappingRelName);
sql.append(" WHERE tid = ? AND Col = ?;");
PreparedStatement holePropertiesStatement = dbConnection.prepareStatement(sql.toString());
//Statement statement = dbConnection.createStatement();
ResultSet rs = sqlStatement.executeQuery("SELECT * FROM eqRel");
//ResultSet rs = sqlStatement.executeQuery(command);
// Prepare ArrayList that will hold information for the bulk operations
ArrayList bulkDeleteInfo = new ArrayList();
ArrayList bulkMergeInfo = new ArrayList();
int s = 0;
while (rs.next())
{
s++;
/*ArrayList<String> compIDs = new ArrayList<String>();
ArrayList<String> holeIDs = new ArrayList<String>();
ArrayList<Integer> inds = new ArrayList<Integer>(); // indexes of terms that are not true in all worlds
*/
ArrayList compIDs = new ArrayList();
ArrayList holeIDs = new ArrayList();
ArrayList inds = new ArrayList(); // indexes of terms that are not true in all worlds
ArrayList invalidTerms = new ArrayList();
ArrayList columns = aEQD.getColumns();
for (int i = 0; i < columns.size(); ++i)
{
String col = columns.get(i).toString();
// The current column contains a hole
String val = rs.getString(col);
if (val == null)
{
continue;
}
if (val.equals("-1"))
{
// Retrieve the cid and hid for the hole
holePropertiesStatement.setInt(1, rs.getInt("tid"));
holePropertiesStatement.setString(2, col);
ResultSet cidhid = holePropertiesStatement.executeQuery();
if (cidhid.next())
{
String cid = cidhid.getString("cid");
String hid = cidhid.getString("hid");
compIDs.add(cid);
holeIDs.add(hid);
inds.add(new Integer(i));
invalidTerms.add(rterms.get(i));
}
else
{
// System.out.println(holePropertiesStatement.toString());
System.out.println("Warning: No info found for the current hole (" + rs.getInt("tid")
+ ", " + col + ").");
//return false;
}
} // end if (rs.getObject(col) == null)
} // end for(int i = 0; i < columns.size; ++i)
// Database is inconsistent
if (compIDs.isEmpty())
{
System.out.println("Incosistent database. Aborting...");
return false;
}
if (compIDs.size() == 1)
{
bulkDeleteInfo.add(new InvalidInfo(compIDs.get(0),
holeIDs.get(0), invalidTerms.get(0)));
}
else // More than one component involved - merge
{
// !!!Dirty fix - there are exactly two components to merge,
// which are independent of the components in the rest of the tuples
// TODO: Figure out what to do when this is not the case
String c1 = compIDs.get(0).toString();
String c2 = compIDs.get(1).toString();
String h1 = holeIDs.get(0).toString();
String h2 = holeIDs.get(1).toString();
Term t1 = (Term) invalidTerms.get(0);
Term t2 = (Term) invalidTerms.get(1);
String c = c1 + "x" + c2;
bulkMergeInfo.add(new InvalidInfoPair(c,c1,c2,h1,h2,t1,t2));
}
} // end while(rs.next)
System.out.println("Size of intermediate results: " + s);
// Perform bulk operations
Statement bulkStatement = dbConnection.createStatement();
if (!bulkDeleteInfo.isEmpty())
{
bulkDelete(bulkStatement, bulkDeleteInfo);
}
if (!bulkMergeInfo.isEmpty())
{
bulkMerge(bulkStatement, bulkMergeInfo);
}
// Drop intermediate results
try
{
sqlStatement.executeUpdate("DROP TABLE eqRel; DROP TABLE eqMap; DROP TABLE eqComp;");
sqlStatement.executeUpdate("DROP TABLE eqWorld;");
}
catch (SQLException e)
{
// Ignore
}
}
catch (SQLException e)
{
System.out.println("Error occured when chasing dependency " + aEQD.toString());
e.printStackTrace();
return false;
}
return true;
}
/**
* Updates the entries in the mapping and components tables after the components
* in the given ArrayList have been merged.
* @param statement The Statement used to perform the updates.
* @param oldCompIDs ArrayList of component IDs that have been merged.
* @param newCompID Name of the new component that replaced the old ones.
*/
private void updateTables(Statement statement, ArrayList oldCompIDs, String newCompID)
throws SQLException
{
StringBuffer sb = new StringBuffer("('" + oldCompIDs.get(0) + "'");
for (int i = 1; i < oldCompIDs.size(); ++i)
{
sb.append(",'" + oldCompIDs.get(i).toString() + "'");
}
sb.append(")");
String st = "DELETE FROM " + compRelName + " WHERE cid IN " + sb.toString();
// System.out.println(st);
statement.executeUpdate(st);
st = "UPDATE " + mappingRelName + " SET cid = '" + newCompID + "' WHERE cid IN " + sb.toString();
statement.executeUpdate(st);
}
/**
* Deletes all worlds in a component that do not satisfy the given conjunctive formula.
* @param statement The Statement used to perform the updates.
* @param comp The name of the component to filter.
* @param rterms A conjunction of terms.
* @param holeIDs IDs of the holes that corespond to the column names in the formula.
* @param inds Indices of the terms that should be taken into account when filtering.
* @throws SQLException
*/
private void deleteInvalid(Statement statement, String comp, ArrayList rterms, ArrayList holeIDs, ArrayList inds)
throws SQLException
{
StringBuffer delSQL = new StringBuffer();
if (holeIDs.size() == 1)
{
delSQL.append("CREATE TABLE INVALID AS SELECT cid, wid FROM " + compRelName);
delSQL.append(" WHERE cid = '" + comp + "'");
delSQL.append(" AND hid = '" + holeIDs.get(0).toString() + "'");
int i = (new Integer(inds.get(0).toString())).intValue();
String op = ((Term) rterms.get(i)).op;
String c = ((Term) rterms.get(i)).right;
// System.out.println("Deleting Values " + op + " " + c);
delSQL.append(" AND Value " + op + " " + c);
statement.executeUpdate(delSQL.toString());
delSQL.delete(0, delSQL.length());
delSQL.append("DELETE FROM " + compRelName + " WHERE oid IN (SELECT c.oid FROM INVALID NATURAL JOIN ");
delSQL.append(compRelName + " AS c)");
statement.executeUpdate(delSQL.toString());
}
else
{
StringBuffer fromClause = new StringBuffer(compRelName + " C0");
StringBuffer cond1 = new StringBuffer("C0.cid = '" + comp +
"' AND C0.hid = '" + holeIDs.get(0) + "'");
int ind = (new Integer(inds.get(0).toString())).intValue();
Term t = (Term) rterms.get(ind);
StringBuffer cond2 = new StringBuffer("C0.Value " + t.op + " " +
t.right);
StringBuffer cond3 = new StringBuffer();
for (int i = 1; i < holeIDs.size(); ++i)
{
fromClause.append("," + compRelName + " C" + i);
cond1.append(" AND C" + i + ".cid = '" + comp +
"' AND C" + i + ".hid = '" + holeIDs.get(i) + "'");
ind = (new Integer(inds.get(i).toString())).intValue();
t = (Term) rterms.get(ind);
cond2.append(" AND C" + i + ".Value " + t.op + " " +
t.right);
cond3.append(" AND C" + (i-1) + ".wid = C" + i + ".wid");
}
String st = "CREATE TABLE INVALID AS SELECT c0.cid,c0.wid FROM " + fromClause.toString() +
" WHERE " + cond1.toString() + " AND " + cond2.toString() + cond3.toString();
statement.executeUpdate(st);
st = "DELETE FROM " + compRelName + " WHERE oid in (SELECT c.oid FROM INVALID NATURAL JOIN " +
compRelName + " AS c);";
// System.out.println(st);
statement.executeUpdate(st);
} // end else
// Delete intermediate results
statement.executeUpdate("DROP TABLE INVALID;");
}
/**
* Performs a bulk delete to remove inconsistencies from the database
* @param invalid ArrayList of InvalidInfo items, denoting which worlds in which components
* @param statement Statement to execute the SQL commands with
* should be deleted.
*/
private void bulkDelete(Statement statement, ArrayList invalid) throws SQLException
{
StringBuffer sb = new StringBuffer();
int size = invalid.size();
int j;
int n;
int i = 0;
while (i < size)
{
j = i;
n = i + 250;
if (n > size)
{
n = size;
}
for (; j < n; ++j)
{
InvalidInfo info = (InvalidInfo) invalid.get(j);
if (j > i)
{
sb.append(" OR ");
}
sb.append("(cid = '" + info.cid + "' AND hid = '" + info.hid + "'");
sb.append(" AND Value " + info.t.op + " '" + info.t.right + "')");
}
// Create table of invalid worlds
String createInvalid = "CREATE TABLE Invalid AS SELECT cid,wid FROM " + compRelName
+ " WHERE " + sb.toString();
statement.executeUpdate(createInvalid);
String delete = "DELETE FROM " + compRelName
+ " WHERE oid IN (SELECT c.oid FROM Invalid NATURAL JOIN " + compRelName + " AS c);";
statement.executeUpdate(delete);
// Drop table of invalid worlds
statement.executeUpdate("DROP TABLE Invalid;");
i += (n-i);
sb.delete(0, sb.length());
}
}
private String generateDelete(InvalidInfoPair info)
{
StringBuffer sb = new StringBuffer();
sb.append("(c1.cid = '" + info.c + "'" + " AND c1.hid ='" + info.h1 + "'");
sb.append(" AND c1.Value " + info.t1.op + " '" + info.t1.right + "'");
sb.append(" AND c2.hid = '" + info.h2 + "'");
sb.append(" AND c2.Value " + info.t2.op + " '" + info.t2.right + "')");
return sb.toString();
}
/**
* Performs a bulk merge of given pairs of components.
* @param statement
* @param mergeInfo
* @throws SQLException
*/
private void bulkMerge(Statement statement, ArrayList mergeInfo) throws SQLException
{
int size = mergeInfo.size();
int i = 0;
int n;
int j;
while (i < size)
{
StringBuffer sb1 = new StringBuffer();
sb1.append("CREATE TABLE Invalid AS SELECT c1.cid,c1.wid FROM ");
sb1.append(compRelName + " AS c1 JOIN " + compRelName
+ " AS c2 ON(c1.cid = c2.cid AND c1.wid = c2.wid)");
sb1.append(" WHERE ");
n = i + 250;
if (n > size)
{
n = size;
}
j = i;
for (;j < n; ++j)
{
InvalidInfoPair info = (InvalidInfoPair) mergeInfo.get(j);
// The components were already merged
if (info.c1.equals(info.c2))
{
info.c = info.c1;
}
else
{
System.out.println("Merging " + info.c1 + " and " + info.c2);
// Merge components
StringBuffer sb2 = new StringBuffer("INSERT INTO " + compRelName + " ");
sb2.append("SELECT '" + info.c + "',c1.hid,c1.wid || c2.wid,c1.Value FROM " + compRelName + " c1," + compRelName + " c2 ");
sb2.append("WHERE c1.cid = '" + info.c1 + "' AND c2.cid = '" + info.c2 +
"' AND c2.hid = '" + info.h2 + "'");
statement.addBatch(sb2.toString());
sb2.delete(0, sb2.length());
sb2.append("INSERT INTO " + compRelName + " ");
sb2.append("SELECT '" + info.c + "',c2.hid,c1.wid || c2.wid,c2.Value FROM " + compRelName + " c1," + compRelName + " c2 ");
sb2.append("WHERE c1.cid = '" + info.c1 + "' AND c2.cid = '" + info.c2 + "' AND c1.hid = '" + info.h1 + "'");
statement.addBatch(sb2.toString());
// Delete old component entries
statement.addBatch("DELETE FROM " + compRelName + " WHERE cid IN ('" + info.c1 + "','" + info.c2 + "')");
// Update mapping relation
statement.addBatch("UPDATE " + mappingRelName + " SET cid = '" + info.c
+ "' WHERE cid IN ('" + info.c1 + "','" + info.c2 + "')");
merged ++;
}
// Delete worlds not satisfying the dependency
if (j > i)
{
sb1.append(" OR ");
}
sb1.append(generateDelete(info));
}
// Perform merging of components
statement.executeBatch();
// Create table of invalid worlds
statement.clearBatch();
//System.out.println(sb1.toString());
statement.executeUpdate(sb1.toString());
String delete = "DELETE FROM " + compRelName
+ " WHERE oid IN (SELECT c.oid FROM Invalid NATURAL JOIN " + compRelName + " AS c);";
statement.executeUpdate(delete);
// Drop table of invalid worlds
statement.executeUpdate("DROP TABLE Invalid;");
i += (n - i);
}
}
/**
* Merges two given components into one and returns the name of the new component.
* @param statement The statement used to perform the updates.
* @param cid1 Name of the first component to merge.
* @param cid2 Name of the second component to merge.
* @param hid1 ID of a hole in the first component.
* @param hid2 ID of a hole in the second component.
* @return Name of the new component.
*/
private String mergeComponents(Statement statement, String cid1, String cid2, String hid1, String hid2)
throws SQLException
{
System.out.println("Merging " + cid1 + " and " + cid2);
// c1 and c2 were already merged or are the same component
if (cid1.indexOf(cid2) != -1 || cid2.indexOf(cid1) != -1)
{
System.out.println("Already merged!");
return cid1;
}
String comp = cid1 + "x" + cid2;
//System.out.println("done");
StringBuffer sb = new StringBuffer("INSERT INTO " + compRelName + " ");
sb.append("SELECT '" + comp + "',c1.hid,c1.wid || c2.wid,c1.Value FROM " + compRelName + " c1," + compRelName + " c2 ");
sb.append("WHERE c1.cid = '" + cid1 + "' AND c2.cid = '" + cid2 + "' AND c2.hid = '" + hid2 + "'");
statement.executeUpdate(sb.toString());
sb.delete(0, sb.length());
sb.append("INSERT INTO " + compRelName + " ");
sb.append("SELECT '" + comp + "',c2.hid,c1.wid || c2.wid,c2.Value FROM " + compRelName + " c1," + compRelName + " c2 ");
sb.append("WHERE c1.cid = '" + cid1 + "' AND c2.cid = '" + cid2 + "' AND c1.hid = '" + hid1 + "'");
statement.executeUpdate(sb.toString());
// Update mapping and components tables
//ArrayList<String> oldCompIDs = new ArrayList<String>();
ArrayList oldCompIDs = new ArrayList();
oldCompIDs.add(cid1);
oldCompIDs.add(cid2);
updateTables(statement, oldCompIDs, comp);
merged++;
return comp;
}
/**
*
* @param terms
* @return
*/
private String constructStatement(ArrayList terms)
{
String result;
StringBuffer selClause = new StringBuffer("SELECT TID, ");
StringBuffer cond = new StringBuffer(" WHERE ");
for (int i = 0; i < terms.size(); ++i)
{
Term t = (Term) terms.get(i);
if (i > 0)
{
selClause.append(",");
cond.append(" AND ");
}
selClause.append(t.left);
cond.append("(" + t.left + " " + t.op + " " + t.right + " OR ");
cond.append("(" + t.left + " IS NULL AND EXISTS (");
cond.append(" SELECT '1' FROM " + mappingRelName + " f," + compRelName + " c ");
cond.append(" WHERE r.tid = f.tid AND f.col = '" + t.left + "' AND f.hid = c.hid");
cond.append(" AND c.value " + t.op + " " + t.right);
cond.append(")))");
}
result = selClause.toString() + " FROM " + templateRelName + " r " + cond;
return result;
}
public boolean chase(ArrayList aDependencies)
{
while(true)
{
for (int i = 0; i < aDependencies.size(); ++i)
{
Dependency d = (Dependency) aDependencies.get(i);
System.out.println("Chasing " + d.toString());
// Functional dependency
if (d.type == 0)
{
if (applyFD((FDependency) d) == false)
{
return false;
}
}
// Equlaity-generating dependency
else if (d.type == 1)
{
if (applyEQD((EQDependency) d) == false)
{
return false;
}
}
}
// TODO: Only break if nothing was changed in the last iteration
break;
}
return true;
}
/**
* Retrieves the IDs of all component and for each component
* one of the holes defined in it.
* @param statement Statement used to perform the query.
* @return HashMap of (component ID, hole ID) pairs.
* @throws SQLException
*/
private HashMap getCompIDs(Statement statement) throws SQLException
{
HashMap result = new HashMap();
String sql = "SELECT cid,hid FROM " + mappingRelName + " WHERE Rel = '"
+ templateRelName + "';";
ResultSet rs = statement.executeQuery(sql);
while (rs.next())
{
result.put(rs.getString("cid"), rs.getString("hid"));
}
return result;
}
/**
* Encapsulates the information needed to delete invalid worlds, such as component ID,
* hole ID and a condition that should be fulfilled in all worlds.
* @author Lublena
*
*/
class InvalidInfo
{
public String cid;
public String hid;
public Term t;
public InvalidInfo(String aCid, String aHid, Term aTerm)
{
cid = aCid;
hid = aHid;
t = aTerm;
}
public InvalidInfo(Object aCid, Object aHid, Object aTerm)
{
cid = aCid.toString();
hid = aHid.toString();
t = (Term) aTerm;
}
}
/**
* Encapsulates the information needed to merge two components and
* delete invalid worlds with respect to a conjunctive formula of two conditions.
* @author Lublena
*/
class InvalidInfoPair
{
String c;
String c1;
String c2;
String h1;
String h2;
Term t1;
Term t2;
public InvalidInfoPair(String c, String c1, String c2, String h1, String h2, Term t1, Term t2)
{
this.c = c;
this.c1 = c1;
this.c2 = c2;
this.h1 = h1;
this.h2 = h2;
this.t1 = t1;
this.t2 = t2;
}
}
}