import org.opendaylight.mdsal.binding.dom.adapter.query.QueryBuilderState.BoundMethod;
import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Exists;
-import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.NotExists;
import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.ValueEquals;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@Override
public final ValueMatch<T> isNull() {
- return withPredicate(new NotExists(relativePath()));
+ return withPredicate(new Exists(relativePath()).negate());
}
@Override
import org.opendaylight.mdsal.binding.api.query.QueryFactory;
import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.mdsal.binding.api.query.QueryResult.Item;
-import org.opendaylight.mdsal.binding.api.query.QueryStructureException;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
import org.opendaylight.mdsal.binding.dom.codec.impl.DefaultBindingCodecTreeFactory;
import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
import org.opendaylight.yang.gen.v1.mdsal.query.norev.FooBuilder;
import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.System;
import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.SystemBuilder;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.SystemKey;
import org.opendaylight.yang.gen.v1.mdsal.query.norev.second.grp.Alarms;
import org.opendaylight.yang.gen.v1.mdsal.query.norev.second.grp.AlarmsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsal.test.binding.rev140701.Top;
}
@Test
- public void bar() throws QueryStructureException {
+ public void bar() {
final Stopwatch sw = Stopwatch.createStarted();
final QueryExpression<TopLevelList> query = factory.querySubtree(InstanceIdentifier.create(Top.class))
.extractChild(TopLevelList.class)
}
@Test
- public void testFindCriticalAlarms() throws QueryStructureException {
+ public void testFindCriticalAlarms() {
final Stopwatch sw = Stopwatch.createStarted();
final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
.extractChild(System.class)
}
@Test
- public void testFindNonCriticalAlarms() throws QueryStructureException {
+ public void testFindNonCriticalAlarms() {
final Stopwatch sw = Stopwatch.createStarted();
final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
.extractChild(System.class)
}
@Test
- public void testFindZeroAlarms() throws QueryStructureException {
+ public void testFindZeroAlarms() {
final Stopwatch sw = Stopwatch.createStarted();
final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
.extractChild(System.class)
assertEquals(2, items.size());
}
+ @Test
+ public void testFindSystemFirstAlarmOne() {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<Alarms> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class, new SystemKey("first"))
+ .extractChild(Alarms.class)
+ .matching()
+ .leaf(Alarms::getId).valueEquals(Uint64.ZERO)
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ final List<? extends Item<@NonNull Alarms>> items = execute(query).getItems();
+ assertEquals(1, items.size());
+ }
+
+ @Test
+ public void testFindSystemFirstWithAlarmOne() {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<System> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class, new SystemKey("first"))
+ .matching()
+ .childObject(Alarms.class)
+ .leaf(Alarms::getId).valueEquals(Uint64.ZERO)
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ final List<? extends Item<@NonNull System>> items = execute(query).getItems();
+ assertEquals(1, items.size());
+ }
+
+
private <T extends @NonNull DataObject> QueryResult<T> execute(final QueryExpression<T> query) {
final Stopwatch sw = Stopwatch.createStarted();
final QueryResult<T> result = executor.executeQuery(query);
}
}
- public static final class NotExists extends DOMQueryPredicate {
- public NotExists(final YangInstanceIdentifier relativePath) {
- super(relativePath);
+ public static final class Not extends DOMQueryPredicate {
+ private final DOMQueryPredicate predicate;
+
+ Not(final DOMQueryPredicate predicate) {
+ super(predicate.relativePath);
+ this.predicate = predicate;
+ }
+
+ public @NonNull DOMQueryPredicate predicate() {
+ return predicate;
+ }
+
+ @Override
+ public DOMQueryPredicate negate() {
+ return predicate;
}
@Override
public boolean test(final NormalizedNode<?, ?> data) {
- return data == null;
+ return !predicate.test(data);
}
}
return relativePath;
}
+ @Override
+ public @NonNull DOMQueryPredicate negate() {
+ return new Not(this);
+ }
+
@Override
public abstract boolean test(@Nullable NormalizedNode<?, ?> data);
}
private static DOMQueryResult evalSingle(final DOMQuery query, final NormalizedNode<?, ?> data) {
- return LazyDOMQueryResultIterator.matches(data, query.getPredicates()) ? DOMQueryResult.of()
+ return DOMQueryMatcher.matches(data, query.getPredicates()) ? DOMQueryResult.of()
: DOMQueryResult.of(new SimpleImmutableEntry<>(query.getRoot(), data));
}
}
--- /dev/null
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.dom.spi.query;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+import java.util.Optional;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
+import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate.Not;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
+
+/**
+ * Generalized utility for matching predicates. Split out of {@link LazyDOMQueryResultIterator} for simplicity.
+ */
+final class DOMQueryMatcher {
+ private DOMQueryMatcher() {
+ // Utility class
+ }
+
+ static boolean matches(final NormalizedNode<?, ?> data, final List<? extends DOMQueryPredicate> predicates) {
+ // TODO: it would be nice if predicates were somehow structured -- can we perhaps sort them by their
+ // InstanceIdentifier? If the predicates are sharing a common subpath. Hence if we can guarantee
+ // predicates are in a certain order, we would not end up in subsequent re-lookups of the same node.
+ Deque<PathArgument> pathArgs = null;
+ for (DOMQueryPredicate pred : predicates) {
+ // So now, dealing with implementations: YangInstanceIdentifier.getLastPathArgument() is always cheap.
+ // If its parent is YangInstanceIdentifier.ROOT (i.e. isEmpty() == true), we are dealing with a last-step
+ // lookup -- in which case we forgo iteration:
+ final YangInstanceIdentifier path = pred.getPath();
+ if (path.coerceParent().isEmpty()) {
+ if (pred instanceof Not) {
+ if (matchesChild(((Not) pred).predicate(), data, path.getLastPathArgument())) {
+ return false;
+ }
+ } else if (!matchesChild(pred, data, path.getLastPathArgument())) {
+ return false;
+ }
+ continue;
+ }
+
+ // We are leaking path arguments in a bid for object reuse: we end up reusing same object as needed
+ if (pathArgs == null) {
+ pathArgs = new ArrayDeque<>();
+ }
+ pathArgs.addAll(path.getPathArguments());
+
+ // The stage is set, we now have to deal with potential negation.
+ if (pred instanceof Not) {
+ if (matchesAny(((Not) pred).predicate(), data, pathArgs)) {
+ return false;
+ }
+ } else if (!matchesAny(pred, data, pathArgs)) {
+ return false;
+ }
+
+ pathArgs.clear();
+ }
+ return true;
+ }
+
+ private static boolean matchesAny(final DOMQueryPredicate pred, final NormalizedNode<?, ?> data,
+ final Deque<PathArgument> pathArgs) {
+ // Guaranteed to have at least one item
+ final PathArgument pathArg = pathArgs.pop();
+ // Ultimate item -- reuse lookup & match
+ if (pathArgs.isEmpty()) {
+ pathArgs.push(pathArg);
+ return matchesChild(pred, data, pathArg);
+ }
+
+ final Optional<NormalizedNode<?, ?>> direct = NormalizedNodes.getDirectChild(data, pathArg);
+ if (direct.isPresent()) {
+ final boolean ret = matchesAny(pred, direct.orElseThrow(), pathArgs);
+ pathArgs.push(pathArg);
+ return ret;
+ }
+
+ // We may be dealing with a wildcard here. NodeIdentifier is a final class, hence this is as fast as it gets.
+ if (pathArg instanceof NodeIdentifier && data instanceof MapNode) {
+ for (MapEntryNode child : ((MapNode) data).getValue()) {
+ if (matchesAny(pred, child, pathArgs)) {
+ pathArgs.push(pathArg);
+ return true;
+ }
+ }
+ }
+
+ pathArgs.push(pathArg);
+ return false;
+ }
+
+ private static boolean matchesChild(final DOMQueryPredicate pred, final NormalizedNode<?, ?> data,
+ final PathArgument pathArg) {
+ // Try the direct approach...
+ final Optional<NormalizedNode<?, ?>> direct = NormalizedNodes.getDirectChild(data, pathArg);
+ if (direct.isPresent()) {
+ return pred.test(direct.orElseThrow());
+ }
+
+ // We may be dealing with a wildcard here. NodeIdentifier is a final class, hence this is as fast as it gets.
+ if (pathArg instanceof NodeIdentifier && data instanceof MapNode) {
+ for (MapEntryNode child : ((MapNode) data).getValue()) {
+ if (pred.test(child)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
import org.opendaylight.mdsal.dom.api.query.DOMQuery;
import org.opendaylight.mdsal.dom.api.query.DOMQueryPredicate;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
// This is the ultimate step in lookup, process it without churning the stack by imposing a dedicated
// Frame. In either case we are done with this frame, unwinding it in both cases.
if (matches(child)) {
- final YangInstanceIdentifier childPath = createIdentifier(child);
- unwind(current, next);
- return new SimpleImmutableEntry<>(childPath, child);
+ return unwindAndReturn(current, next, child);
}
current = unwind(current, next);
// Push our state back, it's just a placeholder for 'currentSelect'. Current path points at us and so does
// the saved Frame.
currentPath.addLast(current.data.getIdentifier());
- frames.push(current);
// Now decide what sort of entry to push. For maps we want to start an iterator already, so it gets
// picked up as a continuation.
- current = child instanceof MapNode ? new MapFrame(child, next, ((MapNode) child).getValue().iterator())
- : new Frame(child, next);
+ if (child instanceof MapNode) {
+ final MapNode map = (MapNode) child;
+ final PathArgument target = remainingSelect.peek();
+ if (target instanceof NodeIdentifierWithPredicates) {
+ final Optional<MapEntryNode> optEntry = map.getChild((NodeIdentifierWithPredicates) target);
+ if (optEntry.isPresent()) {
+ final MapEntryNode entry = optEntry.orElseThrow();
+ if (remainingSelect.size() != 1) {
+ // We need to perform further selection push this frame, an empty frame for the map and
+ // finally a frame for the map entry.
+ remainingSelect.pop();
+ frames.push(current);
+ currentPath.addLast(map.getIdentifier());
+ frames.push(new Frame(map, next));
+ current = new Frame(entry, target);
+ continue;
+ }
+
+ // We have selected entry, see it it matches. In any case rewind, potentially returning
+ // the match
+ if (matches(entry)) {
+ return unwindAndReturn(current, next, entry);
+ }
+ }
+
+ // We failed to find a matching entry, unwind
+ current = unwind(current, next);
+ continue;
+ }
+
+ // We have a wildcard, expand it
+ frames.push(current);
+ current = new MapFrame(child, next, map.getValue().iterator());
+ } else {
+ // Next step in iteration, deal with it
+ frames.push(current);
+ current = new Frame(child, next);
+ }
}
// All done, there be entries no more.
return new SimpleImmutableEntry<>(childPath, child);
}
+ // Unwind any leftover frames and return a matching item
+ private Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> unwindAndReturn(final Frame frame,
+ final PathArgument next, final NormalizedNode<?, ?> child) {
+ final YangInstanceIdentifier childPath = createIdentifier(child);
+ unwind(frame, next);
+ return new SimpleImmutableEntry<>(childPath, child);
+ }
+
/**
* Unwind the stack, discarding current frame, and possibly some others. The unwind starts with pushing {@code next}
* to {@link #remainingSelect}, hence we remember to handle it next time around. It then defers to
}
private boolean matches(final NormalizedNode<?, ?> data) {
- return matches(data, predicates);
- }
-
- static boolean matches(final NormalizedNode<?, ?> data, final List<? extends DOMQueryPredicate> predicates) {
- for (DOMQueryPredicate pred : predicates) {
- // Okay, now we need to deal with predicates, but do it in a smart fashion, so we do not end up iterating
- // all over the place. Typically we will be matching just a leaf.
- final YangInstanceIdentifier path = pred.getPath();
- final Optional<NormalizedNode<?, ?>> node;
- if (path.coerceParent().isEmpty()) {
- node = NormalizedNodes.getDirectChild(data, path.getLastPathArgument());
- } else {
- node = NormalizedNodes.findNode(data, path);
- }
-
- if (!pred.test(node.orElse(null))) {
- return false;
- }
- }
- return true;
+ return DOMQueryMatcher.matches(data, predicates);
}
}