--- /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.binding.api;
+
+import com.google.common.util.concurrent.FluentFuture;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+
+/**
+ * Query-like operations supported by {@link ReadTransaction} and {@link ReadWriteTransaction}. This interface defines
+ * the operations without a tie-in with lifecycle management.
+ */
+public interface QueryOperations {
+ /**
+ * Executes a query on the provided logical data store.
+ *
+ * @param store Logical data store from which read should occur.
+ * @param query Query to execute
+ * @return a FluentFuture containing the result of the query. The Future blocks until the operation is complete.
+ * Once complete:
+ * <ul>
+ * <li>The Future returns the result of the query</li>
+ * <li>If the query execution fails, the Future will fail with a {@link ReadFailedException} or
+ * an exception derived from ReadFailedException.
+ * </li>
+ * </ul>
+ * @throws NullPointerException if any of the arguments is null
+ * @throws IllegalArgumentException if the query is not supported
+ */
+ <T extends @NonNull DataObject> @NonNull FluentFuture<QueryResult<T>> execute(@NonNull LogicalDatastoreType store,
+ @NonNull QueryExpression<T> query);
+}
--- /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.binding.api;
+
+/**
+ * A {@link ReadTransaction} which also supports {@link QueryOperations}.
+ */
+public interface QueryReadTransaction extends ReadTransaction, QueryOperations {
+
+}
--- /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.binding.api;
+
+/**
+ * A {@link ReadWriteTransaction} which also supports {@link QueryOperations}.
+ */
+public interface QueryReadWriteTransaction extends ReadWriteTransaction, QueryOperations {
+
+}
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.binding.dom.adapter.query.DefaultQuery;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadOperations;
import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
+import org.opendaylight.mdsal.dom.api.query.DOMQuery;
+import org.opendaylight.mdsal.dom.spi.query.DOMQueryEvaluator;
+import org.opendaylight.mdsal.dom.spi.query.EagerDOMQueryResult;
import org.opendaylight.yangtools.concepts.Delegator;
import org.opendaylight.yangtools.concepts.Identifiable;
import org.opendaylight.yangtools.yang.binding.DataObject;
checkArgument(!path.isWildcarded(), "Invalid exists of wildcarded path %s", path);
return readOps.exists(store, codec.toYangInstanceIdentifierBlocking(path));
}
+
+ protected final <T extends @NonNull DataObject> @NonNull FluentFuture<QueryResult<T>> doExecute(
+ final DOMDataTreeReadOperations readOps, final @NonNull LogicalDatastoreType store,
+ final @NonNull QueryExpression<T> query) {
+ checkArgument(query instanceof DefaultQuery, "Unsupported query type %s", query);
+ final DefaultQuery<T> defaultQuery = (DefaultQuery<T>) query;
+
+ final DOMQuery domQuery = defaultQuery.asDOMQuery();
+ return readOps.read(store, domQuery.getRoot())
+ .transform(node -> node.map(
+ data -> DOMQueryEvaluator.evaluateOn(domQuery, data)).orElse(EagerDOMQueryResult.of()),
+ MoreExecutors.directExecutor())
+ .transform(defaultQuery::toQueryResult, MoreExecutors.directExecutor());
+ }
}
import com.google.common.util.concurrent.FluentFuture;
import java.util.Optional;
-import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.QueryReadTransaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-final class BindingDOMReadTransactionAdapter extends AbstractForwardedTransaction<DOMDataTreeReadTransaction> implements
- ReadTransaction {
+final class BindingDOMReadTransactionAdapter extends AbstractForwardedTransaction<DOMDataTreeReadTransaction>
+ implements QueryReadTransaction {
- protected BindingDOMReadTransactionAdapter(final DOMDataTreeReadTransaction delegate,
+ BindingDOMReadTransactionAdapter(final DOMDataTreeReadTransaction delegate,
final BindingToNormalizedNodeCodec codec) {
super(delegate, codec);
}
return doExists(getDelegate(), store, path);
}
+ @Override
+ public <T extends @NonNull DataObject> FluentFuture<QueryResult<T>> execute(final LogicalDatastoreType store,
+ final QueryExpression<T> query) {
+ return doExecute(getDelegate(), store, query);
+ }
+
@Override
public void close() {
getDelegate().close();
}
-
}
import com.google.common.util.concurrent.FluentFuture;
import java.util.Optional;
-import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.binding.api.QueryReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
class BindingDOMReadWriteTransactionAdapter extends BindingDOMWriteTransactionAdapter<DOMDataTreeReadWriteTransaction>
- implements ReadWriteTransaction {
+ implements QueryReadWriteTransaction {
BindingDOMReadWriteTransactionAdapter(final DOMDataTreeReadWriteTransaction delegateTx,
final BindingToNormalizedNodeCodec codec) {
}
@Override
- public FluentFuture<Boolean> exists(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
+ public final FluentFuture<Boolean> exists(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
return doExists(getDelegate(), store, path);
}
+
+ @Override
+ public final <T extends @NonNull DataObject> FluentFuture<QueryResult<T>> execute(final LogicalDatastoreType store,
+ final QueryExpression<T> query) {
+ return doExecute(getDelegate(), store, query);
+ }
}
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.opendaylight.mdsal.binding.api.query.QueryExecutor;
public class QueryBuilderTest {
private static final Logger LOG = LoggerFactory.getLogger(QueryBuilderTest.class);
private static BindingNormalizedNodeCodecRegistry CODEC;
- private static QueryExecutor EXECUTOR;
private final QueryFactory factory = new DefaultQueryFactory(CODEC);
+ private QueryExecutor executor;
@BeforeClass
public static void beforeClass() {
CODEC = new BindingNormalizedNodeCodecRegistry(
BindingRuntimeContext.create(GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy(), schemaContext));
- EXECUTOR = SimpleQueryExecutor.builder(CODEC.getCodecContext())
+ }
+
+ @AfterClass
+ public static final void afterClass() {
+ CODEC = null;
+ }
+
+ @Before
+ public void before() {
+ executor = SimpleQueryExecutor.builder(CODEC.getCodecContext())
.add(new FooBuilder()
.setSystem(List.of(
new SystemBuilder().setName("first").setAlarms(List.of(
.build();
}
- @AfterClass
- public static void afterClass() {
- CODEC = null;
- EXECUTOR = null;
- }
-
@Test
public void bar() throws QueryStructureException {
final Stopwatch sw = Stopwatch.createStarted();
assertEquals(2, items.size());
}
- private static <T extends @NonNull DataObject> QueryResult<T> execute(final QueryExpression<T> query) {
+ private <T extends @NonNull DataObject> QueryResult<T> execute(final QueryExpression<T> query) {
final Stopwatch sw = Stopwatch.createStarted();
- final QueryResult<T> result = EXECUTOR.executeQuery(query);
+ final QueryResult<T> result = executor.executeQuery(query);
LOG.info("Query executed in {}", sw);
return result;
}
--- /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.binding.dom.adapter.query;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.api.DataBroker;
+import org.opendaylight.mdsal.binding.api.QueryReadTransaction;
+import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.WriteTransaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryFactory;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
+import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractDataBrokerTest;
+import org.opendaylight.mdsal.binding.dom.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.mdsal.binding.generator.impl.GeneratedClassLoadingStrategy;
+import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.mdsal.query.norev.Foo;
+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.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QueryPerformanceTest extends AbstractDataBrokerTest {
+ private static final Logger LOG = LoggerFactory.getLogger(QueryPerformanceTest.class);
+ private static final int SYSTEM_COUNT = 1_000_000;
+ private static Foo FOO;
+
+ private QueryFactory factory;
+
+ @BeforeClass
+ public static void beforeClass() {
+ final Stopwatch sw = Stopwatch.createStarted();
+ final List<System> builder = new ArrayList<>(SYSTEM_COUNT);
+
+ for (int i = 0; i < SYSTEM_COUNT; ++i) {
+ builder.add(new SystemBuilder().setName("name" + i).setAlias("alias" + i).build());
+ }
+
+ FOO = new FooBuilder().setSystem(builder).build();
+ LOG.info("Test data with {} items built in {}", SYSTEM_COUNT, sw);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ FOO = null;
+ }
+
+ @Override
+ protected void setupWithSchema(final SchemaContext schemaContext) {
+ super.setupWithSchema(schemaContext);
+ factory = new DefaultQueryFactory(new BindingNormalizedNodeCodecRegistry(
+ BindingRuntimeContext.create(GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy(), schemaContext)));
+ }
+
+ @Override
+ protected void setupWithDataBroker(final DataBroker dataBroker) {
+ final Stopwatch sw = Stopwatch.createStarted();
+ WriteTransaction wtx = dataBroker.newWriteOnlyTransaction();
+ wtx.put(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Foo.class), FOO);
+ assertCommit(wtx.commit());
+ LOG.info("Test data with {} items populated in {}", SYSTEM_COUNT, sw);
+ }
+
+ @Test
+ public void queryLessThanAlarm() throws InterruptedException, ExecutionException {
+ final String needle = "alias" + (SYSTEM_COUNT - 1);
+
+ final Stopwatch sw = Stopwatch.createStarted();
+ final QueryExpression<System> query = factory.querySubtree(InstanceIdentifier.create(Foo.class))
+ .extractChild(System.class)
+ .matching()
+ .leaf(System::getAlias).valueEquals(needle)
+ .build();
+ LOG.info("Query built in {}", sw);
+
+ sw.reset().start();
+ final FluentFuture<QueryResult<@NonNull System>> future;
+ try (ReadTransaction rtx = getDataBroker().newReadOnlyTransaction()) {
+ assertThat(rtx, instanceOf(QueryReadTransaction.class));
+ future = ((QueryReadTransaction) rtx).execute(LogicalDatastoreType.CONFIGURATION, query);
+ }
+
+ final QueryResult<@NonNull System> result = future.get();
+ LOG.info("Query executed {} in {}", future, sw);
+
+ assertTrue(result.stream().findAny().isPresent());
+ LOG.info("Query result {} in {}", result, sw);
+ }
+
+ @Test
+ public void searchLessThanAlarm() throws InterruptedException, ExecutionException {
+ final String needle = "alias" + (SYSTEM_COUNT - 1);
+
+ final Stopwatch sw = Stopwatch.createStarted();
+ final FluentFuture<Optional<Foo>> future;
+ try (ReadTransaction rtx = getDataBroker().newReadOnlyTransaction()) {
+ future = rtx.read(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Foo.class));
+ }
+
+ final Foo haystack = future.get().orElseThrow();
+ LOG.info("Read executed in {}", sw);
+
+ Object result = null;
+ for (System system : haystack.nonnullSystem()) {
+ if (needle.equals(system.getAlias())) {
+ result = system;
+ break;
+ }
+ }
+
+ LOG.info("Search found {} in {}", result, sw);
+ assertNotNull(result);
+ }
+}
import com.google.common.util.concurrent.FluentFuture;
import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.Transaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
* @return A future providing access to the result of the check, when it’s available, or any error encountered.
*/
FluentFuture<Boolean> exists(InstanceIdentifier<?> path);
+
+ /**
+ * Executes a {@link QueryExpression}.
+ *
+ * @param query Query to execute
+ * @return a FluentFuture containing the result of the query. The Future blocks until the operation is complete.
+ * Once complete:
+ * <ul>
+ * <li>The Future returns the result of the query</li>
+ * <li>If the query execution fails, the Future will fail with a {@link ReadFailedException} or
+ * an exception derived from ReadFailedException.
+ * </li>
+ * </ul>
+ * @throws NullPointerException if any of the arguments is null
+ * @throws IllegalArgumentException if the query is not supported
+ */
+ <T extends @NonNull DataObject> FluentFuture<QueryResult<T>> execute(QueryExpression<T> query);
}
import com.google.common.util.concurrent.FluentFuture;
import java.util.Optional;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
public FluentFuture<Boolean> exists(final InstanceIdentifier<?> path) {
return delegate().exists(getDatastoreType(), path);
}
+
+ @Override
+ public <T extends DataObject> FluentFuture<QueryResult<T>> execute(final QueryExpression<T> query) {
+ return doExecute(query);
+ }
}
import com.google.common.util.concurrent.FluentFuture;
import java.util.Optional;
import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
}
@Override
- public FluentFuture<Boolean> exists(final InstanceIdentifier<?> path) {
+ public final FluentFuture<Boolean> exists(final InstanceIdentifier<?> path) {
return delegate().exists(getDatastoreType(), path);
}
+
+ @Override
+ public final <T extends DataObject> FluentFuture<QueryResult<T>> execute(final QueryExpression<T> query) {
+ return doExecute(query);
+ }
}
*/
package org.opendaylight.mdsal.binding.util;
+import com.google.common.util.concurrent.FluentFuture;
+import org.opendaylight.mdsal.binding.api.QueryOperations;
import org.opendaylight.mdsal.binding.api.Transaction;
+import org.opendaylight.mdsal.binding.api.query.QueryExpression;
+import org.opendaylight.mdsal.binding.api.query.QueryResult;
import org.opendaylight.mdsal.binding.spi.ForwardingTransaction;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
abstract class TypedTransaction<D extends Datastore, X extends Transaction> extends ForwardingTransaction {
private final LogicalDatastoreType datastoreType;
final LogicalDatastoreType getDatastoreType() {
return datastoreType;
}
+
+ final <T extends DataObject> FluentFuture<QueryResult<T>> doExecute(final QueryExpression<T> query) {
+ if (delegate instanceof QueryOperations) {
+ return ((QueryOperations) delegate).execute(datastoreType, query);
+ }
+ throw new UnsupportedOperationException("Query execution requires backend support");
+ }
}
private static DOMQueryResult evalPath(final ArrayDeque<PathArgument> remaining, final NormalizedNode<?, ?> data,
final DOMQuery query) {
+ // FIXME: this is eager evaluation, we should be doing lazy traversal
final List<Entry<YangInstanceIdentifier, NormalizedNode<?, ?>>> result = new ArrayList<>();
evalPath(result, new ArrayDeque<>(query.getRoot().getPathArguments()), remaining, data, query);
return EagerDOMQueryResult.of(result);