Search in sources :

Example 1 with Call

use of org.immutables.criteria.expression.Call in project immutables by immutables.

the class AggregationQuery method accumulator.

private BsonField accumulator(String field, Expression expression) {
    Preconditions.checkArgument(expression instanceof Call, "not a call %s", expression);
    final Call call = (Call) expression;
    final Operator op = call.operator();
    Preconditions.checkArgument(AggregationOperators.isAggregation(op), "not an aggregation operator: %s", op);
    final String name = "$" + naming.get(extractPath(expression));
    if (op == AggregationOperators.AVG) {
        return Accumulators.avg(field, name);
    } else if (op == AggregationOperators.COUNT) {
        return Accumulators.sum(field, 1);
    } else if (op == AggregationOperators.MAX) {
        return Accumulators.max(field, name);
    } else if (op == AggregationOperators.MIN) {
        return Accumulators.min(field, name);
    } else if (op == AggregationOperators.SUM) {
        return Accumulators.sum(field, name);
    } else {
        throw new IllegalArgumentException(String.format("Unknown aggregation operator %s from %s", op, expression));
    }
}
Also used : Operator(org.immutables.criteria.expression.Operator) Call(org.immutables.criteria.expression.Call) BsonString(org.bson.BsonString)

Example 2 with Call

use of org.immutables.criteria.expression.Call in project immutables by immutables.

the class FindVisitor method visit.

@Override
public Bson visit(Call call) {
    final Operator op = call.operator();
    final List<Expression> args = call.arguments();
    if (op == OptionalOperators.IS_ABSENT || op == OptionalOperators.IS_PRESENT) {
        Preconditions.checkArgument(args.size() == 1, "Size should be 1 for %s but was %s", op, args.size());
        final String field = naming.name(Visitors.toPath(args.get(0)));
        Bson filter;
        if (op == OptionalOperators.IS_PRESENT) {
            filter = Filters.and(Filters.exists(field), Filters.ne(field, null));
        } else {
            // absent fields means null or missing
            filter = Filters.or(Filters.exists(field, false), Filters.eq(field, null));
        }
        return filter;
    }
    if (op == Operators.AND || op == Operators.OR) {
        final List<Bson> list = call.arguments().stream().map(a -> a.accept(this)).collect(Collectors.toList());
        return op == Operators.AND ? Filters.and(list) : Filters.or(list);
    }
    if (op == Operators.NOT) {
        return negate(args.get(0));
    }
    if (op == IterableOperators.IS_EMPTY || op == IterableOperators.NOT_EMPTY) {
        Preconditions.checkArgument(args.size() == 1, "Size should be 1 for %s but was %s", op, args.size());
        final String field = naming.name(Visitors.toPath(args.get(0)));
        return op == IterableOperators.IS_EMPTY ? Filters.eq(field, Collections.emptyList()) : Filters.and(Filters.exists(field), Filters.ne(field, null), Filters.ne(field, Collections.emptyList()));
    }
    if (op.arity() == Operator.Arity.BINARY) {
        return binaryCall(call);
    }
    throw new UnsupportedOperationException(String.format("Not yet supported (%s): %s", call.operator(), call));
}
Also used : Operator(org.immutables.criteria.expression.Operator) Document(org.bson.Document) IterableOperators(org.immutables.criteria.expression.IterableOperators) Path(org.immutables.criteria.expression.Path) PathNaming(org.immutables.criteria.backend.PathNaming) CodecRegistry(org.bson.codecs.configuration.CodecRegistry) BsonString(org.bson.BsonString) Call(org.immutables.criteria.expression.Call) BsonDocument(org.bson.BsonDocument) BsonValue(org.bson.BsonValue) Filters(com.mongodb.client.model.Filters) Bson(org.bson.conversions.Bson) StringOperators(org.immutables.criteria.expression.StringOperators) BsonArray(org.bson.BsonArray) Expressions(org.immutables.criteria.expression.Expressions) Operator(org.immutables.criteria.expression.Operator) ComparableOperators(org.immutables.criteria.expression.ComparableOperators) Constant(org.immutables.criteria.expression.Constant) Operators(org.immutables.criteria.expression.Operators) ImmutableSet(com.google.common.collect.ImmutableSet) BsonNull(org.bson.BsonNull) OptionalOperators(org.immutables.criteria.expression.OptionalOperators) AbstractExpressionVisitor(org.immutables.criteria.expression.AbstractExpressionVisitor) Expression(org.immutables.criteria.expression.Expression) Collection(java.util.Collection) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) Visitors(org.immutables.criteria.expression.Visitors) List(java.util.List) Preconditions(com.google.common.base.Preconditions) Pattern(java.util.regex.Pattern) Collections(java.util.Collections) Expression(org.immutables.criteria.expression.Expression) BsonString(org.bson.BsonString) Bson(org.bson.conversions.Bson)

Example 3 with Call

use of org.immutables.criteria.expression.Call in project immutables by immutables.

the class FindVisitor method binaryCall.

private Bson binaryCall(Call call) {
    Preconditions.checkArgument(call.operator().arity() == Operator.Arity.BINARY, "%s is not binary", call.operator());
    final Operator op = call.operator();
    Expression left = call.arguments().get(0);
    Expression right = call.arguments().get(1);
    if (!(left instanceof Path && right instanceof Constant)) {
        // special case when $expr has to be used
        return call.accept(new MongoExpr(naming, codecRegistry)).asDocument();
    }
    final String field = naming.name(Visitors.toPath(left));
    final Object value = Visitors.toConstant(right).value();
    if (op == Operators.EQUAL || op == Operators.NOT_EQUAL) {
        if ("".equals(value) && op == Operators.NOT_EQUAL) {
            // special case for empty string. string != "" should not return missing strings
            return Filters.and(Filters.nin(field, value, null), Filters.exists(field));
        }
        return op == Operators.EQUAL ? Filters.eq(field, value) : Filters.ne(field, value);
    }
    if (ComparableOperators.isComparable(op)) {
        if (op == ComparableOperators.GREATER_THAN) {
            return Filters.gt(field, value);
        } else if (op == ComparableOperators.GREATER_THAN_OR_EQUAL) {
            return Filters.gte(field, value);
        } else if (op == ComparableOperators.LESS_THAN) {
            return Filters.lt(field, value);
        } else if (op == ComparableOperators.LESS_THAN_OR_EQUAL) {
            return Filters.lte(field, value);
        }
        throw new UnsupportedOperationException("Unknown comparison " + call);
    }
    if (op == Operators.IN || op == Operators.NOT_IN) {
        final Collection<Object> values = ImmutableSet.copyOf(Visitors.toConstant(right).values());
        Preconditions.checkNotNull(values, "not expected to be null for %s", op);
        if (values.size() == 1) {
            // optimization: convert IN, NIN (where argument is a list with single element) into EQ / NE
            Operators newOperator = op == Operators.IN ? Operators.EQUAL : Operators.NOT_EQUAL;
            Call newCall = Expressions.binaryCall(newOperator, left, Expressions.constant(values.iterator().next()));
            return binaryCall(newCall);
        }
        return op == Operators.IN ? Filters.in(field, values) : Filters.nin(field, values);
    }
    if (op == StringOperators.MATCHES || op == StringOperators.CONTAINS) {
        Object newValue = value;
        if (op == StringOperators.CONTAINS) {
            // handle special case for string contains with regexp
            newValue = Pattern.compile(".*" + Pattern.quote(value.toString()) + ".*");
        }
        Preconditions.checkArgument(newValue instanceof Pattern, "%s is not regex pattern", value);
        return Filters.regex(field, (Pattern) newValue);
    }
    if (op == IterableOperators.HAS_SIZE) {
        Preconditions.checkArgument(value instanceof Number, "%s is not a number", value);
        int size = ((Number) value).intValue();
        return Filters.size(field, size);
    }
    if (op == IterableOperators.CONTAINS) {
        return Filters.eq(field, value);
    }
    if (op == StringOperators.HAS_LENGTH) {
        Preconditions.checkArgument(value instanceof Number, "%s is not a number", value);
        final int length = ((Number) value).intValue();
        // use strLenCP function
        // https://docs.mongodb.com/manual/reference/operator/aggregation/strLenCP/#exp._S_strLenCP
        final Bson lengthExpr = Document.parse(String.format("{$expr:{$eq:[{$strLenCP: \"$%s\"}, %d]}}}", field, length));
        // field should exists and not be null
        return Filters.and(Filters.exists(field), Filters.ne(field, null), lengthExpr);
    }
    if (op == StringOperators.STARTS_WITH || op == StringOperators.ENDS_WITH) {
        // regular expression
        final String pattern = String.format("%s%s%s", op == StringOperators.STARTS_WITH ? "^" : "", Pattern.quote(value.toString()), op == StringOperators.ENDS_WITH ? "$" : "");
        return Filters.regex(field, Pattern.compile(pattern));
    }
    throw new UnsupportedOperationException(String.format("Unsupported binary call %s", call));
}
Also used : Operator(org.immutables.criteria.expression.Operator) Path(org.immutables.criteria.expression.Path) Call(org.immutables.criteria.expression.Call) Pattern(java.util.regex.Pattern) IterableOperators(org.immutables.criteria.expression.IterableOperators) StringOperators(org.immutables.criteria.expression.StringOperators) ComparableOperators(org.immutables.criteria.expression.ComparableOperators) Operators(org.immutables.criteria.expression.Operators) OptionalOperators(org.immutables.criteria.expression.OptionalOperators) Constant(org.immutables.criteria.expression.Constant) BsonString(org.bson.BsonString) Bson(org.bson.conversions.Bson) Expression(org.immutables.criteria.expression.Expression)

Example 4 with Call

use of org.immutables.criteria.expression.Call in project immutables by immutables.

the class FindVisitor method negate.

/**
 * see https://docs.mongodb.com/manual/reference/operator/query/not/
 * NOT operator is mongo is a little specific and can't be applied on all levels
 * $not: { $or ... } will not work and should be transformed to $nor
 */
private Bson negate(Expression expression) {
    if (!(expression instanceof Call)) {
        return Filters.not(expression.accept(this));
    }
    Call notCall = (Call) expression;
    Operator notOperator = notCall.operator();
    if (notOperator == Operators.NOT) {
        // NOT NOT a == a
        return notCall.arguments().get(0).accept(this);
    } else if (notOperator == Operators.EQUAL) {
        return newCall(notCall, Operators.NOT_EQUAL);
    } else if (notOperator == Operators.NOT_EQUAL) {
        return newCall(notCall, Operators.EQUAL);
    } else if (notOperator == Operators.IN) {
        return newCall(notCall, Operators.NOT_IN);
    } else if (notOperator == Operators.NOT_IN) {
        return newCall(notCall, Operators.IN);
    } else if (notOperator == Operators.OR) {
        return Filters.nor(notCall.arguments().stream().map(a -> a.accept(this)).collect(Collectors.toList()));
    } else if (notOperator == Operators.AND) {
        // NOT A and B == (NOT A) or (NOT B)
        return Filters.or(notCall.arguments().stream().map(this::negate).collect(Collectors.toList()));
    } else if (notOperator == OptionalOperators.IS_ABSENT || notOperator == OptionalOperators.IS_PRESENT) {
        Operator newOp = notOperator == OptionalOperators.IS_ABSENT ? OptionalOperators.IS_PRESENT : OptionalOperators.IS_ABSENT;
        return newCall(notCall, newOp);
    } else if (notOperator == IterableOperators.IS_EMPTY || notOperator == IterableOperators.NOT_EMPTY) {
        Operator newOp = notOperator == IterableOperators.IS_EMPTY ? IterableOperators.NOT_EMPTY : IterableOperators.IS_EMPTY;
        return newCall(notCall, newOp);
    }
    // don't really know how to negate here
    return Filters.not(notCall.accept(this));
}
Also used : Operator(org.immutables.criteria.expression.Operator) Document(org.bson.Document) IterableOperators(org.immutables.criteria.expression.IterableOperators) Path(org.immutables.criteria.expression.Path) PathNaming(org.immutables.criteria.backend.PathNaming) CodecRegistry(org.bson.codecs.configuration.CodecRegistry) BsonString(org.bson.BsonString) Call(org.immutables.criteria.expression.Call) BsonDocument(org.bson.BsonDocument) BsonValue(org.bson.BsonValue) Filters(com.mongodb.client.model.Filters) Bson(org.bson.conversions.Bson) StringOperators(org.immutables.criteria.expression.StringOperators) BsonArray(org.bson.BsonArray) Expressions(org.immutables.criteria.expression.Expressions) Operator(org.immutables.criteria.expression.Operator) ComparableOperators(org.immutables.criteria.expression.ComparableOperators) Constant(org.immutables.criteria.expression.Constant) Operators(org.immutables.criteria.expression.Operators) ImmutableSet(com.google.common.collect.ImmutableSet) BsonNull(org.bson.BsonNull) OptionalOperators(org.immutables.criteria.expression.OptionalOperators) AbstractExpressionVisitor(org.immutables.criteria.expression.AbstractExpressionVisitor) Expression(org.immutables.criteria.expression.Expression) Collection(java.util.Collection) Collectors(java.util.stream.Collectors) Objects(java.util.Objects) Visitors(org.immutables.criteria.expression.Visitors) List(java.util.List) Preconditions(com.google.common.base.Preconditions) Pattern(java.util.regex.Pattern) Collections(java.util.Collections) Call(org.immutables.criteria.expression.Call)

Example 5 with Call

use of org.immutables.criteria.expression.Call in project immutables by immutables.

the class AggregateQueryBuilder method jsonQuery.

ObjectNode jsonQuery() {
    if (!query.groupBy().isEmpty() && query.offset().isPresent()) {
        String message = "Currently ES doesn't support generic pagination " + "with aggregations. You can still use LIMIT keyword (without OFFSET). " + "For more details see https://github.com/elastic/elasticsearch/issues/4915";
        throw new UnsupportedOperationException(message);
    }
    final ObjectNode json = nodeFactory.objectNode();
    json.put("_source", false);
    json.put("size", 0);
    // avoid fetch phase
    json.put("stored_fields", "_none_");
    query.filter().ifPresent(f -> json.set("query", Elasticsearch.constantScoreQuery(mapper, pathNaming, idPredicate).convert(f)));
    // due to ES aggregation format. fields in "order by" clause should go first
    // if "order by" is missing. order in "group by" is un-important
    final Set<Expression> orderedGroupBy = new LinkedHashSet<>();
    orderedGroupBy.addAll(query.collations().stream().map(Collation::expression).collect(Collectors.toList()));
    orderedGroupBy.addAll(query.groupBy());
    // construct nested aggregations node(s)
    ObjectNode parent = json.with(AGGREGATIONS);
    for (Expression expr : orderedGroupBy) {
        final String name = ((Path) expr).toStringPath();
        final String aggName = naming.apply(expr);
        final ObjectNode section = parent.with(aggName);
        final ObjectNode terms = section.with("terms");
        terms.put("field", name);
        mapping.missingValueFor(name).ifPresent(m -> {
            // expose missing terms. each type has a different missing value
            terms.set("missing", m);
        });
        query.limit().ifPresent(limit -> terms.put("size", limit));
        query.collations().stream().filter(c -> c.path().toStringPath().equals(name)).findAny().ifPresent(col -> terms.with("order").put("_key", col.direction().isAscending() ? "asc" : "desc"));
        parent = section.with(AGGREGATIONS);
    }
    for (Expression expr : query.projections()) {
        if (Visitors.isAggregationCall(expr)) {
            Call call = Visitors.toCall(expr);
            ObjectNode agg = nodeFactory.objectNode();
            String field = ((Path) call.arguments().get(0)).toStringPath();
            agg.with(toElasticAggregate(call)).put("field", field);
            parent.set(naming.apply(call), agg);
        }
    }
    // cleanup json. remove empty "aggregations" element (if empty)
    removeEmptyAggregation(json);
    return json;
}
Also used : LinkedHashSet(java.util.LinkedHashSet) Path(org.immutables.criteria.expression.Path) Call(org.immutables.criteria.expression.Call) ObjectNode(com.fasterxml.jackson.databind.node.ObjectNode) Expression(org.immutables.criteria.expression.Expression) Collation(org.immutables.criteria.expression.Collation)

Aggregations

Call (org.immutables.criteria.expression.Call)9 Expression (org.immutables.criteria.expression.Expression)8 Path (org.immutables.criteria.expression.Path)6 Operator (org.immutables.criteria.expression.Operator)5 Pattern (java.util.regex.Pattern)4 BsonString (org.bson.BsonString)4 ComparableOperators (org.immutables.criteria.expression.ComparableOperators)4 Operators (org.immutables.criteria.expression.Operators)4 OptionalOperators (org.immutables.criteria.expression.OptionalOperators)4 StringOperators (org.immutables.criteria.expression.StringOperators)4 Preconditions (com.google.common.base.Preconditions)3 ImmutableSet (com.google.common.collect.ImmutableSet)3 Collection (java.util.Collection)3 Collections (java.util.Collections)3 List (java.util.List)3 Objects (java.util.Objects)3 Bson (org.bson.conversions.Bson)3 PathNaming (org.immutables.criteria.backend.PathNaming)3 AbstractExpressionVisitor (org.immutables.criteria.expression.AbstractExpressionVisitor)3 Constant (org.immutables.criteria.expression.Constant)3