package edu.stanford.nlp.sempre.freebase; import java.util.*; import com.google.common.collect.Lists; import fig.basic.*; import edu.stanford.nlp.sempre.*; /** * Utilities for constructing SPARQL expressions. * Note that we represent a subset of the SPARQL. * @author Percy Liang */ public interface SparqlExpr { } // Example: { <expr> ... <expr> } class SparqlBlock implements SparqlExpr { public final List<SparqlExpr> children = Lists.newArrayList(); public SparqlBlock add(SparqlExpr expr) { if (expr instanceof SparqlBlock) this.children.addAll(((SparqlBlock) expr).children); else this.children.add(expr); return this; } private String getId(PrimitiveFormula formula) { if (!(formula instanceof ValueFormula)) return null; Value value = ((ValueFormula) formula).value; if (!(value instanceof NameValue)) return null; return ((NameValue) value).id; } private boolean isPrimitiveType(String id) { if (FreebaseInfo.BOOLEAN.equals(id)) return true; if (FreebaseInfo.INT.equals(id)) return true; if (FreebaseInfo.FLOAT.equals(id)) return true; if (FreebaseInfo.DATE.equals(id)) return true; if (FreebaseInfo.TEXT.equals(id)) return true; return false; } void addStatement(PrimitiveFormula arg1, String property, PrimitiveFormula arg2, boolean optional) { if (!property.startsWith("fb:") && !SparqlStatement.isOperator(property) && !SparqlStatement.isSpecialFunction(property) && !SparqlStatement.isIndependent(property)) throw new RuntimeException("Invalid SPARQL property: " + property); // Ignore statements like: // ?x fb:type.object.type fb:type.datetime // because we should have already captured the semantics using other formulas that involve ?x. if (property.equals(FreebaseInfo.TYPE)) { String id = getId(arg2); if (isPrimitiveType(id) || FreebaseInfo.ANY.equals(id)) return; } if (SparqlStatement.isIndependent(property)) return; // Nothing connecting arg1 and arg2 add(new SparqlStatement(arg1, property, arg2, optional)); } @Override public String toString() { List<String> strings = Lists.newArrayList(); for (SparqlExpr expr : children) { if (expr instanceof SparqlSelect) strings.add("{ " + expr + " }"); else strings.add(expr.toString()); } return "{ " + StrUtils.join(strings, " . ") + " }"; } } // Example: SELECT ?x ?y WHERE { ... } ORDER BY ?x ?y LIMIT 10 OFFSET 3 class SparqlSelect implements SparqlExpr { static class Var { public final VariableFormula var; public final String asValue; // for COUNT(?x3) as ?x2 public final String unit; // Specifies the types of the variable (used to parse back the results) public final boolean isAuxiliary; // Whether this is supporting information (e.g., names) public final String description; // Human-friendly for display public Var(VariableFormula var, String asValue, String unit, boolean isAuxiliary, String description) { this.var = var; this.asValue = asValue; this.unit = unit; this.isAuxiliary = isAuxiliary; this.description = description; } @Override public String toString() { if (asValue == null) return var.name; return "(" + asValue + " AS " + var.name + ")"; } } public final List<Var> selectVars = Lists.newArrayList(); public SparqlBlock where; public final List<VariableFormula> sortVars = Lists.newArrayList(); public int offset = 0; // Start at this point when returning results public int limit = -1; // Number of results to return @Override public String toString() { StringBuilder out = new StringBuilder(); out.append("SELECT DISTINCT"); for (Var var : selectVars) out.append(" " + var.toString()); out.append(" WHERE " + where); if (sortVars.size() > 0) { out.append(" ORDER BY"); for (PrimitiveFormula sortVar : sortVars) out.append(" " + SparqlUtils.plainStr(sortVar)); } if (limit != -1) out.append(" LIMIT " + limit); if (offset != 0) out.append(" OFFSET " + offset); return out.toString(); } } // Example: { ... } UNION { ... } UNION { ... } class SparqlUnion implements SparqlExpr { public final List<SparqlBlock> children = Lists.newArrayList(); public SparqlUnion add(SparqlBlock block) { this.children.add(block); return this; } @Override public String toString() { return "{ " + StrUtils.join(children, " UNION ") + " }"; } } // Example: FILTER NOT EXISTS { ... } class SparqlNot implements SparqlExpr { public final SparqlBlock block; public SparqlNot(SparqlBlock block) { this.block = block; } @Override public String toString() { return "FILTER NOT EXISTS " + block; } } class SparqlStatement implements SparqlExpr { public final PrimitiveFormula arg1; public final String relation; public final PrimitiveFormula arg2; public boolean optional; public String options; public SparqlStatement(PrimitiveFormula arg1, String relation, PrimitiveFormula arg2, boolean optional) { this.arg1 = arg1; this.relation = relation; this.arg2 = arg2; this.optional = optional; this.options = null; } public SparqlStatement(PrimitiveFormula arg1, String relation, PrimitiveFormula arg2, boolean optional, String options) { this(arg1, relation, arg2, optional); this.options = options; } public static boolean isIndependent(String relation) { return relation.equals(":"); } public static boolean isOperator(String relation) { return relation.equals("=") || relation.equals("!=") || relation.equals("<") || relation.equals(">") || relation.equals("<=") || relation.equals(">="); } public static boolean isSpecialFunction(String relation) { return relation.equals("STRSTARTS") || relation.equals("STRENDS"); } public String simpleString() { // Workaround for annoying dates: // http://answers.semanticweb.com/questions/947/dbpedia-sparql-endpoint-xsddate-comparison-weirdness if (arg2 instanceof ValueFormula && ((ValueFormula) arg2).value instanceof DateValue) { if (isOperator(relation)) { if (relation.equals("=")) { // (= (date 2000 -1 -1)) really means (>= (2000 -1 -1)) and (< (2001 -1 -1)) DateValue startDate = (DateValue) ((ValueFormula) arg2).value; DateValue endDate = advance(startDate); return SparqlUtils.dateTimeStr(arg1) + " >= " + SparqlUtils.dateTimeStr(new ValueFormula<DateValue>(startDate)) + ") . FILTER (" + SparqlUtils.dateTimeStr(arg1) + " < " + SparqlUtils.dateTimeStr(new ValueFormula<DateValue>(endDate)); } if (relation.equals("<=")) { // (<= (date 2000 -1 -1)) really means (< (date 2001 -1 -1)) DateValue startDate = (DateValue) ((ValueFormula) arg2).value; DateValue endDate = advance(startDate); return SparqlUtils.dateTimeStr(arg1) + " < " + SparqlUtils.dateTimeStr(new ValueFormula<DateValue>(endDate)); } if (relation.equals(">")) { // (> (date 2000 -1 -1)) really means >= (date 2001 -1 -1) DateValue startDate = (DateValue) ((ValueFormula) arg2).value; DateValue endDate = advance(startDate); return SparqlUtils.dateTimeStr(arg1) + " >= " + SparqlUtils.dateTimeStr(new ValueFormula<DateValue>(endDate)); } if (relation.equals("<") || relation.equals(">=")) return SparqlUtils.dateTimeStr(arg1) + " " + relation + " " + SparqlUtils.dateTimeStr(arg2); // Note: != is not treated specially } } return SparqlUtils.plainStr(arg1) + " " + relation + " " + SparqlUtils.plainStr(arg2); } private DateValue advance(DateValue date) { // TODO(pliang): deal with carrying over if (date.day != -1) return new DateValue(date.year, date.month, date.day + 1); if (date.month != -1) return new DateValue(date.year, date.month + 1, -1); return new DateValue(date.year + 1, -1, -1); } public String toString() { String result; if (isSpecialFunction(relation)) { // Special functions result = "FILTER (" + relation + "(" + SparqlUtils.plainStr(arg1) + "," + SparqlUtils.plainStr(arg2) + "))"; } else if (isOperator(relation)) { result = "FILTER (" + simpleString() + ")"; } else if (optional) { result = "OPTIONAL { " + simpleString() + " }"; } else { result = simpleString(); } if (this.options != null) result += " " + this.options; return result; } } final class SparqlUtils { private SparqlUtils() { } public static String dateTimeStr(PrimitiveFormula formula) { return "xsd:datetime(" + plainStr(formula) + ")"; } public static String plainStr(PrimitiveFormula formula) { if (formula instanceof VariableFormula) return ((VariableFormula) formula).name; Value value = ((ValueFormula) formula).value; if (value instanceof StringValue) { String s = ((StringValue) value).value; return "\"" + s.replaceAll("\"", "\\\\\"") + "\"" + (s.equals("en") ? "" : "@en"); } if (value instanceof NameValue) return ((NameValue) value).id; if (value instanceof NumberValue) return ((NumberValue) value).value + ""; if (value instanceof DateValue) { DateValue date = (DateValue) value; if (date.month == -1) return "\"" + date.year + "\"" + "^^xsd:datetime"; if (date.day == -1) return "\"" + date.year + "-" + date.month + "\"" + "^^xsd:datetime"; return "\"" + date.year + "-" + date.month + "-" + date.day + "\"" + "^^xsd:datetime"; } throw new RuntimeException("Unhandled primitive: " + value); } }