/*
* (c) Copyright 2006 Hewlett-Packard Development Company, LP
* All rights reserved.
* [See end of file]
*/
package com.hp.hpl.squirrelrdf.rdb;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.query.core.Binding;
import com.hp.hpl.jena.query.engine1.ExecutionContext;
import com.hp.hpl.squirrelrdf.querymap.QueryMapEngine;
import com.hp.hpl.squirrelrdf.querymap.exceptions.InconsistentException;
/**
* ProcessedPattern
*
* A triple pattern after processing. There is one pattern per database.
*
* @author Damian Steer <pldms@mac.com>
*/
public class ProcessedPattern implements Comparable, QueryMapEngine
{
final static Log log = LogFactory.getLog(ProcessedPattern.class);
private Database db;
private List<ProcessedTriple> triples;
private boolean tied;
private Set<String> variables;
private Map<Node, Table> subjToTable;
/**
* Create a processed pattern associated with a database
*
* @param db
* The database this pattern is concerned with
*/
public ProcessedPattern(Database db)
{
this.db = db;
this.triples = new ArrayList<ProcessedTriple>();
this.tied = false;
this.subjToTable = new HashMap<Node, Table>();
this.variables = new HashSet<String>();
}
public Database getDatabase()
{
return db;
}
public Set<String> getVariables()
{
return variables;
}
/**
* Add a triple to this pattern.
*
* @param subject
* The subject
* @param col
* The column corresponding to the predicate
* @param object
* The object
*/
public void add(Node subject, Col col, Node object)
{
if (!db.equals(col.getDatabase()))
{
log.error("Mismatching column added");
return;
}
ProcessedTriple triple = new ProcessedTriple(subject, col, object);
triples.add(triple);
// Does this pattern give a value for any column?
if (!object.isVariable() || !subject.isVariable())
this.tied = true;
else
variables.add(object.getName());
subjToTable.put(subject, col.getTable());
}
/**
* Add a subject (with corresponding table) -- used for type queries
*
* @param subject
* The subject
* @param table
* The table (class) of this subject
*/
public void add(Node subject, Table table)
{
subjToTable.put(subject, table);
}
/**
* Get the subjects in this pattern.
*
* @return The subjects
*/
public Set<Node> getSubjects()
{
return subjToTable.keySet();
}
/**
* Get table for subject
*
* @param subject The subject node
*
* @return The corresponding table
*/
public Table getTableForSubject(Node subject)
{
return subjToTable.get(subject);
}
/**
* @return True if this pattern gives values for any objects
*/
public boolean tied()
{
return this.tied;
}
/**
* Patterns which give values for keys > patterns which give any values >
* other patterns
*
* @param o
* @return
*/
public int compareTo(Object o)
{
ProcessedPattern other = (ProcessedPattern) o;
if (this.tied() && !other.tied())
return 1;
if (!this.tied() && other.tied())
return -1;
return 0;
}
/**
* Transform this query to an SQL query
*
* @param binding Existing binding
* @param context Execution context
*
* @return An SQL query corresponding to this pattern
* @throws InconsistentException
*/
public String toSql(Binding binding, ExecutionContext context) throws InconsistentException
{
Collection<String> select = new ArrayList<String>();
Collection<String> where = new ArrayList<String>();
Collection<String> from = new HashSet<String>();
// This is for object vars appearing more than once
Map<Node, String> objToCol = new HashMap<Node, String>();
for (Node subject: getSubjects())
{
Table table = getTableForSubject(subject);
from.add(table.getName() + " AS " + name(subject));
// Prebind subj
Node subj = null;
if (subject.isConcrete() || binding.contains(subject.getName())) // subj given, or bound?
subj = subject.isConcrete() ? subject : binding.get(subject.getName());
if (table.hasPrimaryKeys()) // deal with primary keys
{
// Get primary key values
for (Col col: getTableForSubject(subject).getPrimaryCols())
{
select.add(name(subject) + "." + col.getName() + " AS " +
name(subject) + "$" + col.getName() + "$prim");
}
// Subject given (concrete) or bound -- find that row again
if (subj != null)
{
if (!table.isValidSubject(subj))
{
log.warn("Table: " + table.getName());
throw new InconsistentException("Invalid subject for table: " + subj);
}
Map<String, Object> attvals = table.nodeToKeys(subj);
for (String col: attvals.keySet())
{
Object val = attvals.get(col);
where.add(name(subject) + "." + col + " = '" + val + "'");
}
}
}
else if (subj != null)
{
throw new InconsistentException("Concrete subject given for table with no primary keys: " + subj);
}
}
for (Iterator i = triples.iterator(); i.hasNext();)
{
ProcessedTriple triple = (ProcessedTriple) i.next();
triple.addSelect(select, objToCol);
triple.addWhere(where, objToCol, binding);
}
StringBuffer query = new StringBuffer();
if (select.isEmpty()) // No select. Can happen with ASK, for example
select.add("*");
query.append("SELECT ");
query.append(join(" , ", select));
query.append("\n");
if (from.isEmpty())
{
log.warn("FROM is empty!");
}
query.append("FROM ");
query.append(join(" , ", from));
query.append("\n");
if (!where.isEmpty())
{
query.append("WHERE\n");
query.append(join(" AND\n", where));
}
return query.toString();
}
private StringBuffer join(String sep, Collection<String> coll)
{
StringBuffer joined = new StringBuffer();
boolean rest = false;
for (Iterator i = coll.iterator(); i.hasNext();)
{
if (rest)
joined.append(sep);
joined.append(i.next());
rest = true;
}
return joined;
}
/**
* Execute this query given an existing binding and a context
*
* @param binding Existing binding
* @param context The execution context
* @return Iterator over results for continuing binding
*/
public Iterator<Map<String, Node>> execute(Binding binding, ExecutionContext context)
{
try
{
String sqlQuery = toSql(binding, context);
log.debug("Query: [" + db.getName() + "]\n" + sqlQuery + "\n");
Connection connection = db.getConnection();
Statement sm = connection.createStatement();
ResultSet results = sm.executeQuery(sqlQuery);
return new ResultSetIterator(results, this, context);
}
catch (SQLException e)
{
log.error("Problem executing SQL query", e);
}
catch (InconsistentException e)
{
log.warn("Failure due to inconsistency: " + e.getMessage());
}
catch (ClassNotFoundException e)
{
log.error("Driver not found?", e);
}
List<Map<String, Node>> l = Collections.emptyList();
return l.iterator();
}
/* *
* Give an alias for a subject node.
* Easy for variables, tricky for concrete (but only used in this class not outside).
*
* @param node A (subject) node we want a useful name for.
*/
private String name(Node node)
{
if (node.isVariable()) return node.getName();
return subjToTable.get(node).getName() + "$" + node.hashCode();
}
/**
* A processed triple. Has some idea about turning itself into SQL.
*/
class ProcessedTriple
{
private Node subject;
private Col col;
private Node object;
public ProcessedTriple(Node subject, Col col, Node object)
{
this.subject = subject;
this.col = col;
this.object = object;
}
public Node getSubject()
{
return subject;
}
public Col getCol()
{
return col;
}
public Node getObject()
{
return object;
}
public void addSelect(Collection<String> select, Map<Node, String> objToCol)
{
if (object.isConcrete())
return;
// Already SELECTed, so skip...
if (objToCol.containsKey(object))
return;
String selectBit = name(subject) + "." + col.getName() + " AS "
+ object.getName();
select.add(selectBit);
objToCol.put(object, name(subject) + "." + col.getName());
}
public void addWhere(Collection<String> where, Map<Node, String> objToCol, Binding binding)
{
Node theObject = object;
if (object.isVariable() && binding.contains(object.getName())) // Check for existing value
theObject = binding.get(object.getName());
String thing = name(subject) + "." + col.getName();
// Phew. This is a test for whether an (object) variable is already
// bound
// by another expression in this query.
if (theObject.isVariable() && objToCol.containsKey(theObject)
&& !thing.equals(objToCol.get(theObject)))
{
String otherThing = objToCol.get(theObject);
String identCond = name(subject) + "." + col.getName() + "="
+ otherThing;
where.add(identCond);
}
// No NULLs -- stick with RDB dream
if (theObject.isVariable())
{
String notNullCond = name(subject) + "." + col.getName() + " IS NOT NULL";
where.add(notNullCond);
return;
}
else
// Constraint on value
{
String whereBit = name(subject) + "." + col.getName() + "='"
+ theObject.getLiteral().getValue() + "'";
where.add(whereBit);
}
}
}
}
/*
* (c) Copyright 2006 Hewlett-Packard Development Company, LP All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. 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. 3. The name of the author may not
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
*/