From ff7968eb9c85ebebdf106d5913c41a81b9a29f19 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Fri, 20 Mar 2020 17:22:09 +0100 Subject: [PATCH] Add a simple Binding query language Offloading some amount of data matching towards the storage engine can give use two-fold improvement: 1) skip instantiation of Binding DTOs, as the query can be realized in terms of DOM matches 2) potentially skip transfer of large amount of data For Binding layer we want to have something type-safe, where we are in control of the flow the language. This provides a first cut at the structure, which may end up being further evolved. Change-Id: Ica52158c889c0df9a6004227c0b64092590f67af Signed-off-by: Robert Varga --- binding/mdsal-binding-api/pom.xml | 4 + .../api/query/ComparableMatchBuilder.java | 62 +++++ .../api/query/DescendantQueryBuilder.java | 59 +++++ .../binding/api/query/MatchBuilderPath.java | 242 ++++++++++++++++++ .../binding/api/query/QueryExpression.java | 90 +++++++ .../mdsal/binding/api/query/QueryFactory.java | 33 +++ .../mdsal/binding/api/query/QueryResult.java | 57 +++++ .../api/query/QueryStructureException.java | 27 ++ .../binding/api/query/StringMatchBuilder.java | 60 +++++ .../binding/api/query/StructuralBuilder.java | 21 ++ .../mdsal/binding/api/query/ValueMatch.java | 28 ++ .../binding/api/query/ValueMatchBuilder.java | 37 +++ .../mdsal/binding/api/query/package-info.java | 11 + .../api/query/QueryBuilderExamples.java | 134 ++++++++++ .../src/main/yang/mdsal-query.yang | 55 ++++ 15 files changed, 920 insertions(+) create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ComparableMatchBuilder.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/DescendantQueryBuilder.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/MatchBuilderPath.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExpression.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryFactory.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryResult.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryStructureException.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StringMatchBuilder.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StructuralBuilder.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatch.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatchBuilder.java create mode 100644 binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/package-info.java create mode 100644 binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/query/QueryBuilderExamples.java create mode 100644 binding/mdsal-binding-test-model/src/main/yang/mdsal-query.yang diff --git a/binding/mdsal-binding-api/pom.xml b/binding/mdsal-binding-api/pom.xml index fb2f15e038..4d301c5b6c 100644 --- a/binding/mdsal-binding-api/pom.xml +++ b/binding/mdsal-binding-api/pom.xml @@ -45,6 +45,10 @@ yang-data-api + + org.opendaylight.mdsal + mdsal-binding-test-model + org.apache.commons commons-lang3 diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ComparableMatchBuilder.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ComparableMatchBuilder.java new file mode 100644 index 0000000000..c7b28a76c0 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ComparableMatchBuilder.java @@ -0,0 +1,62 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Builder for a match of a leaf value which define a total ordering by implementing the {@link Comparable} interface. + * + *

+ * Note that value comparison is preconditioned on target leaf existence. If the leaf does not exist any total ordering + * checks will not match it -- thus a non-existent leaf does not match {@link #greaterThan(Comparable)} and at the same + * time it does not match {@link #lessThanOrEqual(Comparable)}. + * + * @param query result type + * @param value type + */ +@Beta +public interface ComparableMatchBuilder> extends ValueMatchBuilder { + /** + * Match if the leaf exists and its value is less than the specified value. + * + * @param value value to check against + * @return A ValueMatch + * @throws NullPointerException if value is null + */ + @NonNull ValueMatch lessThan(V value); + + /** + * Match if the leaf exists and its value is less than, or equal to, the specified value. + * + * @param value value to check against + * @return A ValueMatch + * @throws NullPointerException if value is null + */ + @NonNull ValueMatch lessThanOrEqual(V value); + + /** + * Match if the leaf exists and its value is greater than the specified value. + * + * @param value value to check against + * @return A ValueMatch + * @throws NullPointerException if value is null + */ + @NonNull ValueMatch greaterThan(V value); + + /** + * Match if the leaf exists and its value is greater than, or equal to the specified value. + * + * @param value value to check against + * @return A ValueMatch + * @throws NullPointerException if value is null + */ + @NonNull ValueMatch greaterThanOrEqual(V value); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/DescendantQueryBuilder.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/DescendantQueryBuilder.java new file mode 100644 index 0000000000..a42ad16980 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/DescendantQueryBuilder.java @@ -0,0 +1,59 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.ChoiceIn; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Intermediate Query builder stage, which allows the specification of the query result type to be built up via + * {@link #extractChild(Class)} and {@link #extractChild(Class, Class)} methods. Once completed, use either + * {@link #build()} to create a simple query, or {@link #matching()} to transition to specify predicates. + * + * @param Query result type + */ +@Beta +public interface DescendantQueryBuilder extends StructuralBuilder> { + /** + * Add a child path component to the specification of what needs to be extracted. This method, along with its + * alternatives, can be used to specify which object type to select from the root path. + * + * @param Container type + * @param childClass child container class + * @return This builder + * @throws NullPointerException if childClass is null + */ + > @NonNull DescendantQueryBuilder extractChild(Class childClass); + + /** + * Add a child path component to the specification of what needs to be extracted. This method, along with its + * alternatives, can be used to specify which object type to select from the root path. + * + * @param Case type + * @param Container type + * @param caseClass child case class + * @param childClass child container class + * @return This builder + * @throws NullPointerException if any argument is null + */ + & DataObject, N extends ChildOf> + @NonNull DescendantQueryBuilder extractChild(Class caseClass, Class childClass); + + /** + * Start specifying type match predicates. + * + * @return A predicate match builder based on current result type + */ + @NonNull MatchBuilderPath matching(); + + @Override + QueryExpression build(); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/MatchBuilderPath.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/MatchBuilderPath.java new file mode 100644 index 0000000000..66c2f5ac66 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/MatchBuilderPath.java @@ -0,0 +1,242 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import java.io.Serializable; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.concepts.Mutable; +import org.opendaylight.yangtools.yang.binding.BaseIdentity; +import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.ChoiceIn; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.TypeObject; +import org.opendaylight.yangtools.yang.common.Empty; +import org.opendaylight.yangtools.yang.common.Uint16; +import org.opendaylight.yangtools.yang.common.Uint32; +import org.opendaylight.yangtools.yang.common.Uint64; +import org.opendaylight.yangtools.yang.common.Uint8; + +@Beta +public interface MatchBuilderPath extends Mutable { + /** + * Descend match into a child container. + * + * @param Child container type + * @param childClass Child container type class + * @return This builder + * @throws NullPointerException if childClass is null + */ + > @NonNull MatchBuilderPath childObject(Class childClass); + + /** + * Descend match into a child container in a particular case. + * + * @param Case type + * @param Child container type + * @param childClass Child container type class + * @return This builder + * @throws NullPointerException if any argument is null + */ + & DataObject, N extends ChildOf> + @NonNull MatchBuilderPath extractChild(Class caseClass, Class childClass); + + /** + * Match an {@code empty} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ValueMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ValueMatchBuilder leaf(EmptyLeafReference methodRef); + + /** + * Match an {@code string} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link StringMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull StringMatchBuilder leaf(StringLeafReference methodRef); + + /** + * Match an {@code int8} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Int8LeafReference methodRef); + + /** + * Match an {@code int16} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Int16LeafReference methodRef); + + /** + * Match an {@code int32} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Int32LeafReference methodRef); + + /** + * Match an {@code int64} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Int64LeafReference methodRef); + + /** + * Match an {@code uint8} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Uint8LeafReference methodRef); + + /** + * Match an {@code uint16} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Uint16LeafReference methodRef); + + /** + * Match an {@code uint32} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Uint32LeafReference methodRef); + + /** + * Match an {@code uint64} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ComparableMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ComparableMatchBuilder leaf(Uint64LeafReference methodRef); + + /** + * Match an {@code identityref} leaf's value. + * + * @param methodRef method reference to the getter method + * @return A {@link ValueMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ValueMatchBuilder leaf(IdentityLeafReference methodRef); + + /** + * Match a generic leaf value. + * + * @param methodRef method reference to the getter method + * @return A {@link ValueMatchBuilder} + * @throws NullPointerException if methodRef is null + */ + @NonNull ValueMatchBuilder leaf(TypeObjectLeafReference methodRef); + + /** + * Base interface for capturing binding getter method references through lambda expressions. This interface should + * never be used directly, but rather through one of its specializations. + * + *

+ * This interface uncharacteristically extends {@link Serializable} for the purposes of making the resulting lambda + * also Serializable. This part is critical for the process of introspection into the lambda and identifying the + * method being invoked. + * + * @param

Parent type + * @param Child type + */ + @FunctionalInterface + public interface LeafReference extends Serializable { + /** + * Dummy method to express the method signature of a typical getter. This method + * + * @param parent Parent object + * @return Leaf value + * @deprecated This method is present only for technical realization of taking the method reference and should + * never be involved directly. + */ + @Deprecated(forRemoval = true) + C dummyMethod(P parent); + } + + @FunctionalInterface + public interface EmptyLeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface StringLeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Int8LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Int16LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Int32LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Int64LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Uint8LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Uint16LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Uint32LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface Uint64LeafReference

extends LeafReference { + + } + + @FunctionalInterface + public interface IdentityLeafReference extends LeafReference { + + } + + @FunctionalInterface + public interface TypeObjectLeafReference extends LeafReference { + + } +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExpression.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExpression.java new file mode 100644 index 0000000000..a9c68349f3 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryExpression.java @@ -0,0 +1,90 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.opendaylight.yangtools.concepts.Immutable; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * An opaque query expression. A query execution results in a {@link QueryResult}, which is composed of zero or more + * objects of the same type. Implementations of this interface are expected to be effectively-immutable and therefore + * thread-safe and reusable. + * + *

+ * While this interface does not expose any useful methods, it represents a well-defined concept, which is composed of + * three distinct parts: + *

    + *
  • root path, which defines the subtree on which the expression is executed
  • + *
  • select path, which is a strict subset of the root path and defines which objects should be selected
  • + *
  • a set of predicates, which are to be evaluated on selected objects
  • + *
+ * When the expression is evaluated, its QueryResult will contain only those selected objects which also match all + * predicates in the expression. + * + *

+ * For the purposes of illustration of how these three parts work together, let's imagine the following simple model: + * + *

+ *   module foo {
+ *     list foo {
+ *       key name;
+ *
+ *       leaf name {
+ *         type string;
+ *       }
+ *
+ *       leaf alias {
+ *         type string;
+ *       }
+ *
+ *       container bar {
+ *         list baz {
+ *           key id;
+ *
+ *           leaf id {
+ *             type uint64;
+ *           }
+ *
+ *           leaf value {
+ *             type string;
+ *           }
+ *         }
+ *       }
+ *     }
+ *   }
+ * 
+ * + *

+ * We have two nested lists, each having two leaves -- one addressable as a key, one a plain property. There is a number + * of different queries we could perform on such a model: + *

    + *
  1. select all {@code baz}es which have {@code value="foo"}
  2. + *
  3. select all {@code baz}es under {@code foo[name="xyzzy"]}, which have {@code value="foo"}
  4. + *
  5. select all {@code foo}s which have {@code alias="xyzzy"}
  6. + *
  7. select all {@code foo}s which have {@code alias="xyzzy"} and contain a {@code baz[value="foo"]}
  8. + *
+ * + *

+ * Note how the first and second options differ in what is being searched: + *

    + *
  • search for all {@code baz} entries needs to traverse all {@code foo} entries
  • + *
  • search for all {@code baz} entries for {@code foo[name="xyzzy"]} needs to traverse only a single + * directly-addressable entry.
  • + *
+ * The distinction here is made by selecting different root paths: the first will specify the entire {@code foo} list, + * while the second will select a specific {@code foo} entry. + * + * + * + * @param Result object type + */ +@Beta +public interface QueryExpression extends Immutable { + +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryFactory.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryFactory.java new file mode 100644 index 0000000000..31bbb187e8 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryFactory.java @@ -0,0 +1,33 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Primary entry point to creating {@link QueryExpression} instances. + */ +@Beta +public interface QueryFactory { + /** + * Create a new {@link DescendantQueryBuilder} for a specified root path. Root path must be a non-wildcard + * InstanceIdentifier in general sense. If the target type represents a list, the last path argument may be a + * wildcard, in which case the path is interpreted to search the specified list. Inner path elements have to be + * always non-wildcarded. + * + * @param Target object type + * @param rootPath Subtree root + * @return a subtree query instance + * @throws IllegalArgumentException if rootPath is incorrect + * @throws NullPointerException if rootPath is null + */ + @NonNull DescendantQueryBuilder querySubtree(InstanceIdentifier rootPath); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryResult.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryResult.java new file mode 100644 index 0000000000..55b3703d5f --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryResult.java @@ -0,0 +1,57 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import java.util.List; +import java.util.Spliterator; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Result of executing a {@link QueryExpression}. It is composed of one or more result values, which can be accessed via + * {@link #spliterator()}, {@link #stream()} and {@link #getValues()} methods. + * + * @param Result object type + */ +@Beta +public interface QueryResult { + /** + * Returns a spliterator over values of the result. + * + * @return Returns the a spliterator which visits query results. + */ + // TODO: @throws IllegalStateException if values have been already been consumed? + // FIXME: we really may want to wrap each entry in a CheckedValue, so that we can communicate fetch problems + @NonNull Spliterator spliterator(); + + /** + * Returns a sequential {@link Stream} of values from the result. + * + * @return A stream of non-null values. + */ + default @NonNull Stream stream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Returns a parallel {@link Stream} of values from the result. + * + * @return A stream of non-null values. + */ + default @NonNull Stream parallelStream() { + return StreamSupport.stream(spliterator(), true); + } + + default @NonNull List getValues() { + return stream().collect(Collectors.toUnmodifiableList()); + } +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryStructureException.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryStructureException.java new file mode 100644 index 0000000000..ab8ccbbf32 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/QueryStructureException.java @@ -0,0 +1,27 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; + +/** + * Exception reported when the proposed query has a structural problem. This may be either a mismatch with underlying + * schema, a value type problem, or a general DTO relationship issue. + */ +@Beta +public class QueryStructureException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + public QueryStructureException(final String message) { + super(message); + } + + public QueryStructureException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StringMatchBuilder.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StringMatchBuilder.java new file mode 100644 index 0000000000..5e7ed3cd3c --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StringMatchBuilder.java @@ -0,0 +1,60 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Builder for a match of a String leaf value. + * + * @param query result type + */ +@Beta +public interface StringMatchBuilder extends ValueMatchBuilder { + /** + * Match if the leaf exists and its value starts with specified string, i.e. its {@link String#startsWith(String)} + * would return true. + * + * @param str string to match as prefix + * @return A ValueMatch + * @throws NullPointerException if str is null + */ + @NonNull ValueMatch startsWith(String str); + + /** + * Match if the leaf exists and its value ends with specified string. i.e. its {@link String#endsWith(String)} + * would return true. + * + * @param str string to match as suffix + * @return A ValueMatch + * @throws NullPointerException if str is null + */ + @NonNull ValueMatch endsWith(String str); + + /** + * Match if the leaf exists and its value contains specified string, i.e. its {@link String#contains(CharSequence)} + * would return true. + * + * @param str the string to search for + * @return A ValueMatch + * @throws NullPointerException if str is null + */ + @NonNull ValueMatch contains(String str); + + /** + * Match if the leaf exists and its value matches with specified pattern. + * + * @param pattern pattern to check against + * @return A ValueMatch + * @throws NullPointerException if pattern is null + */ + @NonNull ValueMatch matchesPattern(Pattern pattern); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StructuralBuilder.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StructuralBuilder.java new file mode 100644 index 0000000000..eb62b2329d --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/StructuralBuilder.java @@ -0,0 +1,21 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.opendaylight.yangtools.concepts.CheckedBuilder; + +/** + * A specialization of {@link CheckedBuilder}, whose {@code build()} method throws a {@link QueryStructureException}. + * + * @param

Product of builder + */ +@Beta +public interface StructuralBuilder

extends CheckedBuilder { + +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatch.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatch.java new file mode 100644 index 0000000000..7977fea9fe --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatch.java @@ -0,0 +1,28 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * A value-based match executed from some point in the data tree. + * + * @param query result type + */ +@Beta +public interface ValueMatch extends StructuralBuilder> { + /** + * Start chaining an additional match for the query. Query results are guaranteed both this match and that + * additional match at the same time. + * + * @return A {@link MatchBuilderPath} + */ + @NonNull MatchBuilderPath and(); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatchBuilder.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatchBuilder.java new file mode 100644 index 0000000000..edc650b27f --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/ValueMatchBuilder.java @@ -0,0 +1,37 @@ +/* + * 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.query; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Basic builder for a match of a leaf value. + * + * @param query result type + * @param value type + */ +@Beta +public interface ValueMatchBuilder { + /** + * Match any existing value. + * + * @return A ValueMatch + */ + @NonNull ValueMatch nonNull(); + + /** + * Match exact value. + * + * @param value value to match + * @return A ValueMatch + * @throws NullPointerException if value is null + */ + @NonNull ValueMatch valueEquals(@NonNull V value); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/package-info.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/package-info.java new file mode 100644 index 0000000000..113ab40249 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/query/package-info.java @@ -0,0 +1,11 @@ +/* + * 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 + */ +/** + * Simple type-safe query language based on YANG concepts as manifested by its Java Bindings. + */ +package org.opendaylight.mdsal.binding.api.query; diff --git a/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/query/QueryBuilderExamples.java b/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/query/QueryBuilderExamples.java new file mode 100644 index 0000000000..5dd8da5556 --- /dev/null +++ b/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/query/QueryBuilderExamples.java @@ -0,0 +1,134 @@ +/* + * 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.query; + +import org.mockito.Mock; +import org.opendaylight.yang.gen.v1.mdsal.query.norev.Foo; +import org.opendaylight.yang.gen.v1.mdsal.query.norev.first.grp.System; +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.third.grp.AffectedUsers; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.common.Uint64; + +public class QueryBuilderExamples { + @Mock + private QueryFactory factory; + + /* + * Return all of /foo. Equivalent to a read() of the same identifier. + */ + public QueryExpression selectFoo() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .build(); + } + + /* + * Read all of /foo/system[name="some"]. Equivalent to a read() of the same identifier. + */ + public QueryExpression selectFooSystemSome() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class).child(System.class, new SystemKey("some"))) + .build(); + } + + /* + * Read all entries in /foo/system. Equivalent to a read(/foo).get().nonnullSystem(). + */ + public QueryExpression selectFooSystem() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .build(); + } + + /* + * Read all entries in /foo/system, which have 'alias' set to 'some'. + */ + public QueryExpression selectFooSystemAliasSome() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .matching() + .leaf(System::getAlias) + .valueEquals("some") + .build(); + } + + /* + * Read all entries in /foo/system, which have 'alias' containing the string 'needle'. + */ + public QueryExpression selectFooSystemAliasWithNeedle() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .matching() + .leaf(System::getAlias) + .contains("needle") + .build(); + } + + /* + * Read all entries in /foo/system/alarms, which have 'critical' leaf present. + */ + public QueryExpression selectFooSystemAlarmsCritical() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .extractChild(Alarms.class) + .matching() + .leaf(Alarms::getCritical) + .nonNull() + .build(); + } + + /* + * Read all entries in /foo/system/alarms, which have 'critical' leaf present and have an entry in 'affected-users' + * with 'uid' larger than 10. + * + * Note this is the same expression as selectFooSystemCriticalUid(), but selects Alarms objects. + */ + public QueryExpression selectFooSystemAlarmsCriticalUid() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .extractChild(Alarms.class) + .matching() + .leaf(Alarms::getCritical) + .nonNull() + .and() + .childObject(AffectedUsers.class) + .leaf(AffectedUsers::getUid) + .greaterThan(Uint64.TEN) + .build(); + } + + + /* + * Read all entries in /foo/system, which have 'critical' leaf present and have an entry in 'affected-users' + * with 'uid' larger than 10. + * + * Note this is the same expression as selectFooSystemAlarmsCriticalUid(), but selects System objects. + */ + public QueryExpression selectFooSystemCriticalUid() { + return factory + .querySubtree(InstanceIdentifier.create(Foo.class)) + .extractChild(System.class) + .matching() + .childObject(Alarms.class) + .leaf(Alarms::getCritical) + .nonNull() + .and() + .childObject(Alarms.class) + .childObject(AffectedUsers.class) + .leaf(AffectedUsers::getUid) + .greaterThan(Uint64.TEN) + .build(); + } +} diff --git a/binding/mdsal-binding-test-model/src/main/yang/mdsal-query.yang b/binding/mdsal-binding-test-model/src/main/yang/mdsal-query.yang new file mode 100644 index 0000000000..a5b36bd11e --- /dev/null +++ b/binding/mdsal-binding-test-model/src/main/yang/mdsal-query.yang @@ -0,0 +1,55 @@ +module mdsal-query { + namespace "mdsal-query"; + prefix mq; + + grouping first-grp { + list system { + key name; + + leaf name { + type string; + } + + leaf alias { + type string; + } + + uses second-grp; + } + } + + grouping second-grp { + list alarms { + key id; + + leaf id { + type uint64; + } + + leaf critical { + type empty; + } + + uses third-grp; + } + } + + grouping third-grp { + list affected-users { + key uid; + + leaf uid { + type uint64; + } + + leaf attr { + type uint8; + } + } + } + + container foo { + uses first-grp; + } +} + -- 2.36.6