@Override
  public void meet(FunctionCall fc) throws RuntimeException {
    // special optimizations for frequent cases with variables
    if ((XMLSchema.DOUBLE.toString().equals(fc.getURI())
            || XMLSchema.FLOAT.toString().equals(fc.getURI()))
        && fc.getArgs().size() == 1) {
      optypes.push(ValueType.DOUBLE);
      fc.getArgs().get(0).visit(this);
      optypes.pop();
    } else if ((XMLSchema.INTEGER.toString().equals(fc.getURI())
            || XMLSchema.INT.toString().equals(fc.getURI()))
        && fc.getArgs().size() == 1) {
      optypes.push(ValueType.INT);
      fc.getArgs().get(0).visit(this);
      optypes.pop();
    } else if (XMLSchema.BOOLEAN.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
      optypes.push(ValueType.BOOL);
      fc.getArgs().get(0).visit(this);
      optypes.pop();
    } else if (XMLSchema.DATE.toString().equals(fc.getURI()) && fc.getArgs().size() == 1) {
      optypes.push(ValueType.DATE);
      fc.getArgs().get(0).visit(this);
      optypes.pop();
    } else {

      String fnUri = fc.getURI();

      String[] args = new String[fc.getArgs().size()];

      NativeFunction nf = functionRegistry.get(fnUri);

      if (nf != null && nf.isSupported(parent.getDialect())) {

        for (int i = 0; i < args.length; i++) {
          args[i] =
              new ValueExpressionEvaluator(fc.getArgs().get(i), parent, nf.getArgumentType(i))
                  .build();
        }

        if (optypes.peek() != nf.getReturnType()) {
          builder.append(castExpression(nf.getNative(parent.getDialect(), args), optypes.peek()));
        } else {
          builder.append(nf.getNative(parent.getDialect(), args));
        }
      } else {
        throw new IllegalArgumentException(
            "the function " + fnUri + " is not supported by the SQL translation");
      }
    }
  }
  @Override
  public void meet(MathExpr expr) throws RuntimeException {
    ValueType ot = new OPTypeFinder(expr).coerce();

    if (ot == ValueType.STRING) {
      if (expr.getOperator() == MathExpr.MathOp.PLUS) {
        builder.append(
            functionRegistry
                .get(FN.CONCAT.stringValue())
                .getNative(
                    parent.getDialect(),
                    new ValueExpressionEvaluator(expr.getLeftArg(), parent, ot).build(),
                    new ValueExpressionEvaluator(expr.getRightArg(), parent, ot).build()));
      } else {
        throw new IllegalArgumentException(
            "operation " + expr.getOperator() + " is not supported on strings");
      }
    } else {
      if (ot == ValueType.NODE || ot == ValueType.TERM) {
        ot = ValueType.DOUBLE;
      }

      optypes.push(ot);
      expr.getLeftArg().visit(this);
      builder.append(getSQLOperator(expr.getOperator()));
      expr.getRightArg().visit(this);
      optypes.pop();
    }
  }
  @Override
  public void meet(If node) throws RuntimeException {
    builder.append("CASE WHEN ");

    optypes.push(ValueType.BOOL);
    node.getCondition().visit(this);
    optypes.pop();

    optypes.push(new OPTypeFinder(node).coerce());
    builder.append(" THEN ");
    node.getResult().visit(this);
    builder.append(" ELSE ");
    node.getAlternative().visit(this);
    builder.append(" END");
    optypes.pop();
  }
  @Override
  public void meet(Count node) throws RuntimeException {
    builder.append("COUNT(");

    if (node.isDistinct()) {
      builder.append("DISTINCT ");
    }

    if (node.getArg() == null) {
      // this is a weird special case where we need to expand to all variables selected in the query
      // wrapped
      // by the group; we cannot simply use "*" because the concept of variables is a different one
      // in SQL,
      // so instead we construct an ARRAY of the bindings of all variables

      List<String> countVariables = new ArrayList<>();
      for (SQLVariable v : parent.getVariables().values()) {
        if (v.getProjectionType() == ValueType.NONE) {
          Preconditions.checkState(
              v.getExpressions().size() > 0, "no expressions available for variable");

          countVariables.add(v.getExpressions().get(0));
        }
      }
      builder.append("ARRAY[");
      Joiner.on(',').appendTo(builder, countVariables);
      builder.append("]");

    } else {
      optypes.push(ValueType.NODE);
      node.getArg().visit(this);
      optypes.pop();
    }
    builder.append(")");
  }
 @Override
 public void meet(Sum node) throws RuntimeException {
   builder.append("SUM(");
   optypes.push(ValueType.DOUBLE);
   node.getArg().visit(this);
   optypes.pop();
   builder.append(")");
 }
 @Override
 public void meet(Compare cmp) throws RuntimeException {
   optypes.push(new OPTypeFinder(cmp).coerce());
   cmp.getLeftArg().visit(this);
   builder.append(getSQLOperator(cmp.getOperator()));
   cmp.getRightArg().visit(this);
   optypes.pop();
 }
 @Override
 public void meet(SameTerm cmp) throws RuntimeException {
   // covered by value binding in variables
   optypes.push(ValueType.TERM);
   cmp.getLeftArg().visit(this);
   builder.append(" = ");
   cmp.getRightArg().visit(this);
   optypes.pop();
 }
  public ValueExpressionEvaluator(ValueExpr expr, SQLBuilder parent, ValueType optype) {
    this.parent = parent;

    optypes.push(optype);

    if (log.isTraceEnabled()) {
      long start = System.currentTimeMillis();
      expr.visit(this);
      log.trace("expression evaluated in {} ms", (System.currentTimeMillis() - start));
    } else {
      expr.visit(this);
    }
  }
  @Override
  public void meet(Bound node) throws RuntimeException {
    ValueExpr arg = node.getArg();

    if (arg instanceof ValueConstant) {
      builder.append(Boolean.toString(true));
    } else if (arg instanceof Var) {
      builder.append("(");
      optypes.push(ValueType.NODE);
      arg.visit(this);
      optypes.pop();
      builder.append(" IS NOT NULL)");
    }
  }
 @Override
 public void meet(BNodeGenerator gen) throws RuntimeException {
   if (gen.getNodeIdExpr() != null) {
     // get value of argument and express it as string
     optypes.push(ValueType.STRING);
     gen.getNodeIdExpr().visit(this);
     optypes.pop();
   } else {
     builder
         .append("'")
         .append(
             Long.toHexString(System.currentTimeMillis())
                 + Integer.toHexString(anonIdGenerator.nextInt(1000)))
         .append("'");
   }
 }
  @Override
  public void meet(Like node) throws RuntimeException {
    if (node.isCaseSensitive()) {
      optypes.push(ValueType.STRING);
      node.getArg().visit(this);
      optypes.pop();

      builder.append(" LIKE ");
      node.getPattern();
    } else {
      builder.append(
          parent
              .getDialect()
              .getILike(
                  new ValueExpressionEvaluator(node.getArg(), parent, ValueType.STRING).build(),
                  node.getOpPattern()));
    }
  }
  @Override
  public void meet(IRIFunction fun) throws RuntimeException {
    if (fun.getBaseURI() != null) {

      String ex = new ValueExpressionEvaluator(fun.getArg(), parent, ValueType.STRING).build();

      builder
          .append("CASE WHEN position(':' IN ")
          .append(ex)
          .append(") > 0 THEN ")
          .append(ex)
          .append(" ELSE ")
          .append(
              functionRegistry
                  .get(FN.CONCAT.stringValue())
                  .getNative(parent.getDialect(), "'" + fun.getBaseURI() + "'", ex))
          .append(" END ");
    } else {
      // get value of argument and express it as string
      optypes.push(ValueType.STRING);
      fun.getArg().visit(this);
      optypes.pop();
    }
  }
 @Override
 public void meet(Str node) throws RuntimeException {
   optypes.push(ValueType.STRING);
   node.getArg().visit(this);
   optypes.pop();
 }