private Joinable parseGraphPattern( final GraphPattern g, final HashMap<String, String> variableBindings) throws QueryException { LOG.debug("parsing graph pattern " + g); /* First, organize the filters by variable so that we can map them */ HashMap<String, Set<MappableNodeFilter>> filters = new HashMap<String, Set<MappableNodeFilter>>(); /* We're given NodeFilters, but need MappableNodeFilters. Convert. */ Set<MappableNodeFilter<Node>> givenFilters = new HashSet<MappableNodeFilter<Node>>(); for (NodeFilter<Node> filter : g.getFilters()) { givenFilters.add(new MappableNodeFilter<Node>(filter)); } /* * For each given filter, if the value or constraint is a variable, * place it in the filter pool. */ for (MappableNodeFilter f : givenFilters) { if (f.getNode().isVariable()) { if (!filters.containsKey(f.getNode().getVarName())) { LOG.debug("Adding " + f.getNode().getVarName() + " To filter pool..\n"); filters.put(f.getNode().getVarName(), new HashSet<MappableNodeFilter>()); } filters.get(f.getNode().getVarName()).add(f); } if (f.getConstraint().isVariable()) { if (!filters.containsKey(f.getConstraint().getVarName())) { LOG.debug("Adding " + f.getConstraint().getVarName() + " To filter pool..\n"); filters.put(f.getConstraint().getVarName(), new HashSet<MappableNodeFilter>()); } filters.get(f.getConstraint().getVarName()).add(f); } if (!(f.getNode().isVariable() || f.getConstraint().isVariable())) { throw new IllegalArgumentException( "Triple filters must " + "contain a variable. Neither " + f.getNode().getVarName() + " nor " + f.getConstraint().getVarName() + " is a variable!"); } } /* Next, process each triple pattern in this graph pattern */ LinkedList<MappableTriplePattern> steps = new LinkedList<MappableTriplePattern>(); for (TriplePattern p : g.getTriplePatterns()) { steps.add(new MappableTriplePattern(p)); } /* * We create an initial join sequence from the first triple pattern NB: * We require that all triple patterns in a graph pattern can be joined, * so it is OK to do this */ MappableTriplePattern step = null; boolean successfullyBoundFirstStep = false; while (!steps.isEmpty()) { step = steps.removeFirst(); if (!bindPattern(step, variableBindings)) { continue; } else { successfullyBoundFirstStep = true; break; } } if (!successfullyBoundFirstStep) { LOG.info("Pattern is entirely redundant. Ignoring: " + g); return null; } JoinSequence joins = new JoinSequence(new JoinTable(step)); /* We keep track of all tje variables that are available to join on */ Set<MappableNodePattern> joinableVars = joins.joinVars(); /* For all the remaining steps.. */ while (!steps.isEmpty()) { step = getJoinablePattern(steps, variableBindings); if (step == null) { throw new QueryException( "Cannot bind all query steps! \n" + "remaining:\n" + steps + "\nvariables already bound:\n " + variableBindings.keySet() + "\n"); } steps.remove(step); bindPattern(step, variableBindings); JoinTable table = new JoinTable(step); joinableVars.addAll(table.joinVars()); JoinConditions conditions = new JoinConditions(); /* Create all possible joins */ addJoinConditions(conditions, step, variableBindings); /* Add any filter constraints */ addFilterConditions(conditions, filters, joinableVars, variableBindings); /* Fold in any remaining constant bindings */ for (MappableNodePattern var : joinableVars) { if (valueBindings.containsKey(var.boundTable().alias())) { for (String condition : valueBindings.get(var.boundTable().alias())) { LOG.debug( "parseGraphPattern: Adding remaining " + "constant conditions " + condition + "\n"); conditions.addCondition(condition); } valueBindings.remove(var.boundTable().alias()); } } /* Finally, add the join to the sequence */ joins.addJoin(JoinType.INNER_JOIN, table, conditions); } /* * If we still have filters yet unapplied, that is legitimate only if * this pattern has a length of 1 AND it contains a variable that * matches the filter... */ if (filters.values().size() > 0 && g.getTriplePatterns().size() > 1) { throw new QueryException("Filter is unbound"); } /* .. If so, then get that one pattern */ MappableTriplePattern p = new MappableTriplePattern(g.getTriplePatterns().get(0)); /* ... and Process any remaining filters */ for (String varName : filters.keySet()) { for (MappableNodeFilter f : filters.get(varName)) { processFilter(p, varName, f); } } return joins; }
/** * Returns a query in ANSI SQL * * <p>Translates the GraphQuery defined in the constructor, along with any specified orderings , * into a set of SQL statements. The union of all SQL statement results, executed in order, will * represent the entire result set. * * @return list of SQL statements * @throws QueryException if there is some error translating the query to SQL. */ public List<String> getSQL() throws QueryException { this.manager = new MappingManager(tableManager); this.encounteredPatterns = new HashSet<MappableTriplePattern>(); valueBindings = new HashMap<String, Set<String>>(); /* These are bindings of variables in the 'required' query portions */ HashMap<String, String> requiredBindings = new HashMap<String, String>(); /* All variable bindings, including optional */ HashMap<String, String> allBindings = new HashMap<String, String>(); /* This is the main sequence of joins constituting the query */ JoinSequence joinSeq = null; /* Process required elements first */ for (QueryElement e : query.getRequired()) { LOG.debug("Processing required element: " + e); /* Disallow subqueries for the time being */ if (e.getType().equals(QueryElement.Type.GraphQuery)) { throw new QueryException("Currently, we do not support " + "subqueries"); } else if (!e.getType().equals(QueryElement.Type.GraphPattern)) { /* Currently, this will never happen */ throw new QueryException("Unknown query element type " + e.getType()); } /* All required elements of the query are inner joined */ if (joinSeq == null) { joinSeq = new JoinSequence(parseGraphPattern((GraphPattern) e, requiredBindings)); } else { joinSeq.addJoin( JoinType.INNER_JOIN, parseGraphPattern((GraphPattern) e, requiredBindings), requiredBindings); } } allBindings.putAll(requiredBindings); for (QueryElement e : query.getOptional()) { LOG.debug("processing optional path: " + e); HashMap<String, String> optionalBindings = requiredBindings; /* Disallow subqueries for the time being */ if (e.getType().equals(QueryElement.Type.GraphQuery)) { throw new QueryException("Currently, we do not support " + "subqueries"); } else if (!e.getType().equals(QueryElement.Type.GraphPattern)) { /* Currently, this will never happen */ throw new QueryException("Unknown query element type " + e.getType()); } joinSeq.addJoin( JoinType.LEFT_OUTER_JOIN, parseGraphPattern((GraphPattern) e, optionalBindings), requiredBindings); addNewMappings(optionalBindings, allBindings); } StringBuilder sql = new StringBuilder(); if (joinSeq == null) { /* No tables to join means no query parts submitted */ return Arrays.asList("SELECT 1 WHERE 1=0"); } else { sql.append("SELECT " + generateTargets(allBindings) + " FROM " + joinSeq); } /* * If there are any values or constraints that remain to be added to the * query, add them in a WHERE clause. NB: They better not be from an * optional clause: It would probably be wise to either check here, or * prove that an exception would have been thrown already. */ StringBuilder additional = new StringBuilder(); if (valueBindings.size() > 0) { additional.append(" WHERE "); ArrayList<String> valueKeys = new ArrayList<String>(valueBindings.keySet()); for (int i = 0; i < valueKeys.size(); i++) { ArrayList<String> values = new ArrayList<String>(valueBindings.get(valueKeys.get(i))); for (int j = 0; j < values.size(); j++) { if (i > 0 || j > 0) { sql.append(" AND "); } LOG.debug( "Adding remaining unused binding for " + valueKeys.get(i) + ", " + values.get(j) + "\n"); additional.append(values.get(j)); } } } if (!additional.toString().equals(" WHERE ")) { sql.append(additional.toString()); } if (ordering != null) { sql.append(" ORDER BY " + allBindings.get(ordering) + " " + orderingDirection); } ArrayList<String> sqlList = new ArrayList<String>(); sqlList.add(sql.toString()); return sqlList; }