/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Northrop Grumman Corporation. All Rights Reserved. * * This file is an original work and contains no Original Code. It was * developed by Netymon Pty Ltd under contract to the Australian * Commonwealth Government, Defense Science and Technology Organisation * under contract #4500507038 and is contributed back to the Kowari/Mulgara * Project as per clauses 4.1.3 and 4.1.4 of the above contract. * * Contributor(s): N/A. * * Copyright: * The copyright on this file is held by: * The Australian Commonwealth Government * Department of Defense * Developed by Netymon Pty Ltd * Copyright (C) 2006 * The Australian Commonwealth Government * Department of Defense * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.resolver.relational; 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.log4j.Logger; import org.jrdf.graph.URIReference; import org.jrdf.graph.Literal; import org.mulgara.query.TuplesException; import org.mulgara.query.Variable; /** * Represents a relational query. */ // Notes on possible gaps: // We do not handle repeated object variables properly ie. $s <p1> $o and $s <p2> $o. // This should map to a single column entry + a restriction. // // public class RelationalQuery { /** Logger */ private static final Logger logger = Logger.getLogger(RelationalQuery.class); private Set<String> tableSet; private List<String> columnList; private Set<String> restrictionSet; private Map<Variable,VariableDesc> variableMap; private Set<Variable> variableSet; private boolean distinct = false; private Map<LiteralDesc,List<UnionCase>> unionCases; public RelationalQuery() { tableSet = new HashSet<String>(); columnList = new ArrayList<String>(); restrictionSet = new HashSet<String>(); variableMap = new HashMap<Variable,VariableDesc>(); unionCases = new HashMap<LiteralDesc,List<UnionCase>>(); variableSet = new HashSet<Variable>(); } public void addTable(String table) { tableSet.add(table); } public void addTables(Set<String> tables) { tableSet.addAll(tables); } public int addColumn(String column) { int index = columnList.indexOf(column); if (index == -1) { columnList.add(column); index = columnList.size() - 1; } return index; } public void addVariable(Variable v, VariableDesc desc) { Object old = variableMap.put(v, desc); if (old != null) { if (logger.isInfoEnabled()) { logger.info("Multiple descriptions for variable $" + v + " old: " + old + " new: " + desc); } } variableSet.add(v); } public void addUnionCase(LiteralDesc predVariable, UnionCase unionCase) { // !!FIXME: We need to consider what happens if multiple constraints use the same // variable predicate. This would result in multiple restrictions/variables associated // with each union case. To handle that we probably would need to move the map generation // out into the Resolution. List<UnionCase> unionCaseList = unionCases.get(predVariable); if (unionCaseList == null) { unionCaseList = new ArrayList<UnionCase>(); unionCases.put(predVariable, unionCaseList); } unionCaseList.add(unionCase); // Allow for the existence of variables in the object position that do not participate // elsewhere in the constraint. if (unionCase.obj instanceof Variable) { variableSet.add((Variable)unionCase.obj); } } public void addRestriction(String restriction) { restrictionSet.add(restriction); } public void makeDistinct() { this.distinct = true; } public List<String> getQuery() { if (unionCases.size() == 0) { String distinctStr = distinct ? "DISTINCT " : ""; String sql = "SELECT " + distinctStr + toList(columnList, ", ") + " FROM " + toList(tableSet, ", "); if (restrictionSet.size() > 0) { sql += " WHERE " + toList(restrictionSet, " AND "); } return Collections.singletonList(sql); } else { // Too many side-effects here. There must be a better way of doing this. List<LiteralDesc> indexList = assignIndicies(); List<String> selectList = generateSelectQueries(new ArrayList<String>(), new SubQuery(), indexList, 0); if (!variableSet.equals(variableMap.keySet())) { throw new IllegalStateException("Post query generated variableMap must match variableSet. map=" + variableMap + ", set=" + variableSet); } return selectList; } } private List<LiteralDesc> assignIndicies() { List<LiteralDesc> indexList = new ArrayList<LiteralDesc>(); for (LiteralDesc desc: unionCases.keySet()) { indexList.add(desc); } return indexList; } private class SubQuery implements Cloneable { public ArrayList<String> columnList = new ArrayList<String>(); public ArrayList<String> objColumnList = new ArrayList<String>(); public HashSet<String> restrictionSet = new HashSet<String>(); public HashSet<String> tableSet = new HashSet<String>(); @SuppressWarnings("unchecked") public Object clone() { try { SubQuery c = (SubQuery)super.clone(); c.columnList = (ArrayList<String>)columnList.clone(); c.objColumnList = (ArrayList<String>)objColumnList.clone(); c.restrictionSet = (HashSet<String>)restrictionSet.clone(); c.tableSet = (HashSet<String>)tableSet.clone(); return c; } catch (CloneNotSupportedException ec) { throw new IllegalStateException("Clone not supported on Cloneable"); } } public String toString() { return "sq:(" + "columnList=" + columnList + ", " + "objColumnList=" + objColumnList + ", " + "restrictionSet=" + restrictionSet + ", " + "tableSet=" + tableSet + ")"; } } private String generateSelectQuery(SubQuery sq) { String sql = "SELECT " + toList(columnList, ", "); if (sq.columnList.size() > 0) { sql += ", " + toList(sq.columnList, ", "); } if (sq.objColumnList.size() > 0) { sql += ", " + toList(sq.objColumnList, ", "); } sql += " FROM " + toList(tableSet, ", "); // Remove duplicates. sq.tableSet.removeAll(tableSet); if (sq.tableSet.size() > 0) { sql += ", " + toList(sq.tableSet, ", "); } if (restrictionSet.size() > 0 || sq.restrictionSet.size() > 0) { sql += " WHERE "; } if (restrictionSet.size() > 0) { sql += toList(restrictionSet, " AND "); } if (sq.restrictionSet.size() > 0) { sql += toList(sq.restrictionSet, " AND "); } return sql; } /** * @return The accumulator is returned having accumulated the required select queries. */ private List<String> generateSelectQueries(List<String> accum, SubQuery subQuery, List<LiteralDesc> indexList, int index) { if (index >= indexList.size()) { accum.add(generateSelectQuery(subQuery)); } else { LiteralDesc desc = indexList.get(index); List<UnionCase> cases = unionCases.get(desc); for (UnionCase cse: cases) { SubQuery sq = (SubQuery)subQuery.clone(); sq.columnList.add("'" + cse.pred + "'"); // We need to do the assignment here (which will be highly redundant) because // this is the only place we can compensate for the possible insertion of object variables // into the query. desc.assignColumnIndex(null, columnList.size() + sq.columnList.size() - 1); if (cse.obj instanceof Variable) { // Variable object. // Obtain a redirect descriptor for variable. VariableDesc od = variableMap.get(cse.obj); RedirectDesc rdesc; if (od == null) { rdesc = new RedirectDesc(desc); // We don't subtract 1 here because we have not yet added rdesc to sq.columnList int redirectIndex = columnList.size() + sq.columnList.size(); rdesc.assignColumnIndex(null, redirectIndex); } else if (od instanceof RedirectDesc) { rdesc = (RedirectDesc)od; } else { throw new IllegalStateException("Pre-existing variable descriptor for variable object with variable predicate not supported: v=" + cse.obj + " desc=" + od); } // Handle the Variable - uses redirect to handle different types returning from union. this.addVariable((Variable)cse.obj, rdesc); // Handle Tables. Note that duplicates with rq.tableSet are removed in generateSelectQuery sq.tableSet.addAll(cse.desc.getTables()); // Handle conditions and joins. cut-n-paste from includePropertyBridge - refactor required for (String join: cse.desc.getJoin()) { sq.tableSet.addAll(RelationalResolver.extractTablesFromJoin(join)); sq.restrictionSet.add(join); } sq.restrictionSet.addAll(cse.desc.getCondition()); // Register case descriptor with redirect and insert redirect index as literal into query sq.columnList.add(Integer.toString(rdesc.addVariableDesc(cse.desc))); // Handle Columns for (String c: cse.desc.getColumns()) { int newIndex = columnList.indexOf(c); if (newIndex == -1) { newIndex = sq.columnList.indexOf(c); if (newIndex == -1) { sq.columnList.add(c); // Allow for the fact that we just increased the size of sq.columnList by 1. newIndex = columnList.size() + sq.columnList.size() - 1; } else { newIndex += columnList.size(); } } cse.desc.assignColumnIndex(c, newIndex); } } else { // Literal object // Handle Tables. See note above regarding duplicates sq.tableSet.addAll(cse.desc.getTables()); // Handle conditions and joins. cut-n-paste from includePropertyBridge - refactor required for (String join: cse.desc.getJoin()) { sq.tableSet.addAll(RelationalResolver.extractTablesFromJoin(join)); sq.restrictionSet.add(join); } sq.restrictionSet.addAll(cse.desc.getCondition()); // Handle Restriction if (cse.obj instanceof URIReference) { sq.restrictionSet.add(cse.desc.restrict(cse.obj.toString())); } else if (cse.obj instanceof Literal) { sq.restrictionSet.add(cse.desc.restrict(((Literal)cse.obj).getLexicalForm())); } else { throw new IllegalArgumentException("Unsupported object type: " + cse.obj); } } // Recurse on indexList. // By recursing inside the for-loop we expand the combinations of union cases. generateSelectQueries(accum, sq, indexList, index + 1); } } return accum; } public List<Variable> getVariables() { return new ArrayList<Variable>(variableSet); } public VariableDesc getVariableDesc(Variable var) throws TuplesException { VariableDesc desc = variableMap.get(var); if (desc == null) { throw new TuplesException("Variable not found: " + var); } return desc; } public static String toList(Collection<String> strings, String delim) { StringBuffer result; Iterator<String> i = strings.iterator(); if (!i.hasNext()) { return ""; } else { String s = i.next(); result = new StringBuffer(s); } while (i.hasNext()) result.append(delim + i.next().toString()); return result.toString(); } public String toString() { return "RelationalQuery (sql = " + getQuery() + ", varMap = " + variableMap + ")"; } }