package org.openbel.framework.api.internal;
import static org.openbel.framework.api.EdgeDirectionType.FORWARD;
import static org.openbel.framework.api.EdgeDirectionType.REVERSE;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.openbel.framework.api.Kam;
import org.openbel.framework.api.Kam.KamEdge;
import org.openbel.framework.api.Kam.KamNode;
import org.openbel.framework.common.InvalidArgument;
import org.openbel.framework.common.enums.RelationshipType;
import org.openbel.framework.core.df.AbstractJdbcDAO;
import org.openbel.framework.core.df.DBConnection;
/**
* KAMUpdateDaoImpl implements {@link Kam} update operations.
*/
public class KAMUpdateDaoImpl extends AbstractJdbcDAO implements KAMUpdateDao {
private static final int MAX_BATCH_COUNT = 500;
private static final String UPDATE_KAM_EDGES_SOURCE = "update @.kam_edge " +
"set kam_source_node_id = ? where kam_edge_id = ?";
private static final String UPDATE_KAM_EDGES_TARGET = "update @.kam_edge " +
"set kam_target_node_id = ? where kam_edge_id = ?";
private static final String UPDATE_TERM = "update @.term set kam_node_id = ? " +
"where kam_node_id = ?";
private static final String SELECTED_ORDERED_EDGES =
"select kam_edge_id, kam_source_node_id, relationship_type_id, " +
"kam_target_node_id from @.kam_edge order by kam_source_node_id, " +
"relationship_type_id, kam_target_node_id";
private static final String SELECT_KAM_EDGE_BY_RELATIONSHIP =
"select kam_edge_id from @.kam_edge " +
"where relationship_type_id = ?";
private static final String UPDATE_KAM_EDGE_STATEMENT =
"update @.kam_edge_statement_map set kam_edge_id = ? " +
"where kam_edge_id = ?";
private static final String DELETE_KAM_NODE_PARAMETER =
"delete from @.kam_node_parameter where kam_node_id = ?";
private static final String DELETE_KAM_NODE =
"delete from @.kam_node where kam_node_id = ?";
private static final String DELETE_EDGE_STATEMENT =
"delete from @.kam_edge_statement_map where kam_edge_id = ?";
private static final String DELETE_KAM_EDGES =
"delete from @.kam_edge where kam_edge_id = ?";
// private static final String DELETE_ORTHOLOGOUS_STATEMENTS =
// "delete from @.statement where relationship_type_id = ?";
/**
* Constructs the dao.
*
* @param c {@link DBConnection}; may not be {@code null}
* @param schema {@link String}; may not be {@code null}
* @throws SQLException when an error occurs checking if {@code c} is open
* @throws InvalidArgument when {@code c} is {@code null}, {@code c} is
* closed, or {@code schema} is {@code null}
*/
public KAMUpdateDaoImpl(DBConnection c, String schema)
throws SQLException {
super(c, schema);
}
/**
* {@inheritDoc}
*/
@Override
public boolean collapseKamNode(KamNode collapsing, KamNode collapseTo)
throws SQLException {
if (collapsing == null)
throw new InvalidArgument("collapsing node is null");
if (collapseTo == null)
throw new InvalidArgument("collapseTo node is null");
if (collapsing.getKam() == null || collapseTo.getKam() == null
|| collapsing.getKam() != collapseTo.getKam())
throw new InvalidArgument("nodes reference invalid kams");
if (collapsing.getId() == null || collapseTo.getId() == null)
throw new InvalidArgument("node id is null");
Kam kam = collapsing.getKam();
PreparedStatement esps = getPreparedStatement(UPDATE_KAM_EDGES_SOURCE);
PreparedStatement etps = getPreparedStatement(UPDATE_KAM_EDGES_TARGET);
remapEdges(collapsing, collapseTo, kam, esps, etps);
PreparedStatement utps = getPreparedStatement(UPDATE_TERM);
remapTerms(collapsing, collapseTo, utps);
PreparedStatement knps = getPreparedStatement(DELETE_KAM_NODE);
PreparedStatement knpps = getPreparedStatement(DELETE_KAM_NODE_PARAMETER);
removeKamNode(collapsing, knps, knpps);
return true;
}
/**
* {@inheritDoc}
*/
@Override
public int removeKamEdges(int[] edgeIds) throws SQLException {
if (edgeIds == null)
throw new InvalidArgument("edgeIds is null");
if (edgeIds.length == 0)
return 0;
int batch = 0;
int deletes = 0;
PreparedStatement kesps = getPreparedStatement(DELETE_EDGE_STATEMENT);
PreparedStatement keps = getPreparedStatement(DELETE_KAM_EDGES);
// add delete command; submit batches per MAX_BATCH_COUNT
for (int e : edgeIds) {
kesps.setInt(1, e);
kesps.addBatch();
keps.setInt(1, e);
keps.addBatch();
batch++;
if (batch == MAX_BATCH_COUNT) {
// remove from kam_edge_statement_map
kesps.executeBatch();
// remove from kam_edge
int[] rowsAffected = keps.executeBatch();
for (int d : rowsAffected) deletes += d;
}
}
// submit batch for anything left over
if (batch > 0) {
// remove from kam_edge_statement_map
kesps.executeBatch();
// remove from kam_edge
int[] rowsAffected = keps.executeBatch();
for (int d : rowsAffected) deletes += d;
}
return deletes;
}
/**
* {@inheritDoc}
*/
@Override
public int removeKamEdges(RelationshipType relationship)
throws SQLException {
if (relationship == null || relationship.getValue() == null)
throw new InvalidArgument("relationship is null");
PreparedStatement ps = getPreparedStatement(SELECT_KAM_EDGE_BY_RELATIONSHIP);
ResultSet rset = null;
try {
// set relationships to remove
int rvalue = relationship.getValue();
ps.setInt(1, rvalue);
// cursor all edge ids
List<Integer> ids = new ArrayList<Integer>();
rset = ps.executeQuery();
while (rset.next()) {
int edgeId = rset.getInt(1);
ids.add(edgeId);
}
// convert to int[]
int sz = ids.size();
int[] edgeIds = new int[sz];
for (int i = 0; i < sz; i++) edgeIds[i] = ids.get(i);
// remove edges for edge id int[]
return removeKamEdges(edgeIds);
} finally {
if (rset != null) {
try {
rset.close();
} catch (Exception e) {
// ignored
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public int coalesceKamEdges() throws SQLException {
PreparedStatement eps = getPreparedStatement(SELECTED_ORDERED_EDGES);
PreparedStatement kesps = getPreparedStatement(UPDATE_KAM_EDGE_STATEMENT);
ResultSet rset = null;
int coalesced = 0;
try {
rset = eps.executeQuery();
if (rset.next()) {
int xSource = rset.getInt(2);
int xRel = rset.getInt(3);
int xTarget = rset.getInt(4);
int xEdgeId = rset.getInt(1);
int[] xTriple = new int[] {xSource, xRel, xTarget};
while (rset.next()) {
int edgeId = rset.getInt(1);
int source = rset.getInt(2);
int rel = rset.getInt(3);
int target = rset.getInt(4);
int[] triple = new int[] {source, rel, target};
if (Arrays.equals(triple, xTriple)) {
// duplicate triple, move over statements
kesps.setInt(1, xEdgeId);
kesps.setInt(2, edgeId);
kesps.executeUpdate();
// remove duplicate
removeKamEdges(new int[] {edgeId});
coalesced++;
} else {
// move to next unseen triple
xTriple = triple;
xEdgeId = edgeId;
}
}
}
} finally {
if (rset != null) {
try {
rset.close();
} catch (Exception e) {
// ignored
}
}
}
return coalesced;
}
/**
* Remaps outgoing and incoming edges to {@code collapseTo}.
*
* @param collapsing {@link KamNode} being collapsed
* @param collapseTo {@link KamNode} collapsing to
* @param kam {@link Kam} to retrieve adjacent edges
* @param esps {@link PreparedStatement} for updating edge source
* @param etps {@link PreparedStatement} for updating edge target
* @return {@code int} update count
* @throws SQLException when a SQL error occurred with update
*/
private static int remapEdges(KamNode collapsing, KamNode collapseTo,
Kam kam, PreparedStatement esps, PreparedStatement etps)
throws SQLException {
int updates = 0;
Set<KamEdge> outgoing = kam.getAdjacentEdges(collapsing, FORWARD);
int collapseToId = collapseTo.getId();
for (KamEdge edge : outgoing) {
esps.setInt(1, collapseToId);
esps.setInt(2, edge.getId());
updates += esps.executeUpdate();
}
Set<KamEdge> incoming = kam.getAdjacentEdges(collapsing, REVERSE);
for (KamEdge edge : incoming) {
etps.setInt(1, collapseToId);
etps.setInt(2, edge.getId());
updates += etps.executeUpdate();
}
return updates;
}
/**
* Remaps terms to {@code collapseTo}.
*
* @param collapsing {@link KamNode} being collapsed
* @param collapseTo {@link KamNode} collapsing to
* @param utps {@link PreparedStatement} for updating term
* @return {@code int} update count
* @throws SQLException when a SQL error occurred with update
*/
private static int remapTerms(KamNode collapsing, KamNode collapseTo,
PreparedStatement utps) throws SQLException {
int collapsingId = collapsing.getId();
int collapseToId = collapseTo.getId();
utps.setInt(1, collapseToId);
utps.setInt(2, collapsingId);
return utps.executeUpdate();
}
/**
* Removes {@code collapsing} node.
*
* @param collapsing {@link KamNode} being collapsed
* @param knps {@link PreparedStatement} for updating kam_node
* @param knpps {@link PreparedStatement} for updating kam_node_parameter
* @return {@code int} update count
* @throws SQLException when a SQL error occurred with delete
*/
private static int removeKamNode(KamNode collapsing,
PreparedStatement knps, PreparedStatement knpps)
throws SQLException {
int collapsingId = collapsing.getId();
int updates = 0;
knpps.setInt(1, collapsingId);
updates += knpps.executeUpdate();
knps.setInt(1, collapsingId);
updates += knps.executeUpdate();
return updates;
}
}