@Override
  public void meet(ValueConstant node) throws RuntimeException {
    String val = node.getValue().stringValue();

    switch (optypes.peek()) {
      case STRING:
      case URI:
        builder.append("'").append(val).append("'");
        break;
      case INT:
        builder.append(Integer.parseInt(val));
        break;
      case DECIMAL:
      case DOUBLE:
        builder.append(Double.parseDouble(val));
        break;
      case BOOL:
        builder.append(Boolean.parseBoolean(val));
        break;
      case DATE:
        builder.append("'").append(sqlDateFormat.format(DateUtils.parseDate(val))).append("'");
        break;

        // in this case we should return a node ID and also need to make sure it actually exists
      case TERM:
      case NODE:
        KiWiNode n = parent.getConverter().convert(node.getValue());
        builder.append(n.getId());
        break;

      default:
        throw new IllegalArgumentException("unsupported value type: " + optypes.peek());
    }
  }
  @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(Var node) throws RuntimeException {
    // distinguish between the case where the variable is plain and the variable is bound
    SQLVariable sv = parent.getVariables().get(node.getName());

    if (sv == null) {
      builder.append("NULL");
    } else if (sv.getBindings().size() > 0) {
      // in case the variable is actually an alias for an expression, we evaluate that expression
      // instead, effectively replacing the
      // variable occurrence with its value
      sv.getBindings().get(0).visit(this);
    } else {
      String var = sv.getAlias();

      if (sv.getProjectionType() != ValueType.NODE && sv.getProjectionType() != ValueType.NONE) {
        // in case the variable represents a constructed or bound value instead of a node, we need
        // to
        // use the SQL expression as value; SQL should take care of proper casting...
        // TODO: explicit casting needed?
        builder.append(sv.getExpressions().get(0));
      } else {
        // in case the variable represents an entry from the NODES table (i.e. has been bound to a
        // node
        // in the database, we take the NODES alias and resolve to the correct column according to
        // the
        // operator type
        switch (optypes.peek()) {
          case STRING:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".svalue");
            break;
          case INT:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".ivalue");
            break;
          case DECIMAL:
          case DOUBLE:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".dvalue");
            break;
          case BOOL:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".bvalue");
            break;
          case DATE:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".tvalue");
            break;
          case TZDATE:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(parent.getDialect().getDateTimeTZ(var));
            break;
          case URI:
            Preconditions.checkState(var != null, "no alias available for variable");
            builder.append(var).append(".svalue");
            break;
          case TERM:
          case NODE:
            if (sv.getExpressions().size() > 0) {
              // this allows us to avoid joins with the nodes table for simple expressions that only
              // need the ID
              builder.append(sv.getExpressions().get(0));
            } else {
              Preconditions.checkState(var != null, "no alias available for variable");
              builder.append(var).append(".id");
            }
            break;
        }
      }
    }
  }