This is an automated email from the ASF dual-hosted git repository.
smiklosovic pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/trunk by this push:
new 9d5cef7f8c Allow custom constraints to be loaded via SPI
9d5cef7f8c is described below
commit 9d5cef7f8c6c749d3f2b71fc1f6fae1ee086c2ae
Author: Stefan Miklosovic <[email protected]>
AuthorDate: Fri Aug 8 11:23:43 2025 +0200
Allow custom constraints to be loaded via SPI
patch by Stefan Miklosovic; reviewed by Bernardo Botella, Saranya
Krishnakumar for CASSANDRA-20824
---
CHANGES.txt | 1 +
.../pages/developing/cql/constraints.adoc | 15 ++
examples/constraints/README.adoc | 46 ++++++
examples/constraints/build.xml | 68 +++++++++
.../constraints/CustomConstraintsProvider.java | 100 +++++++++++++
...e.cassandra.cql3.constraints.ConstraintProvider | 16 ++
.../constraints/AbstractFunctionConstraint.java | 13 --
.../cql3/constraints/ColumnConstraint.java | 4 +-
.../cql3/constraints/ConstraintProvider.java | 63 ++++++++
.../cql3/constraints/ConstraintResolver.java | 162 +++++++++++++++++++++
.../cql3/constraints/FunctionColumnConstraint.java | 25 +---
.../cassandra/cql3/constraints/JsonConstraint.java | 7 +-
.../cql3/constraints/LengthConstraint.java | 7 +-
.../cql3/constraints/UnaryConstraintFunction.java | 3 +-
.../constraints/UnaryFunctionColumnConstraint.java | 29 +---
.../constraints/ConstraintsProviderTest.java | 144 ++++++++++++++++++
16 files changed, 639 insertions(+), 64 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 2a7162fb4c..8a17ee639a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
5.1
+ * Allow custom constraints to be loaded via SPI (CASSANDRA-20824)
* Optimize DataPlacement lookup by ReplicationParams (CASSANDRA-20804)
* Fix ShortPaxosSimulationTest and AccordSimulationRunner do not execute from
the cli (CASSANDRA-20805)
* Allow overriding arbitrary settings via environment variables
(CASSANDRA-20749)
diff --git a/doc/modules/cassandra/pages/developing/cql/constraints.adoc
b/doc/modules/cassandra/pages/developing/cql/constraints.adoc
index 2e729db01d..c352ce207f 100644
--- a/doc/modules/cassandra/pages/developing/cql/constraints.adoc
+++ b/doc/modules/cassandra/pages/developing/cql/constraints.adoc
@@ -47,6 +47,21 @@ ALTER TABLE [IF EXISTS] <table> ALTER [IF EXISTS] <column>
DROP CHECK;
There is no way how to drop individual check when multiple checks are
specified on a column. After dropping checks, you
are required to re-define all necessary checks again.
+== Constraints are pluggable
+
+On top of in-built constraints enumerated below, since
https://issues.apache.org/jira/browse/CASSANDRA-20824[CASSANDRA-20824],
+it is possible to provide your own implementation of a constraint and use it
in Cassandra. This is possible
+thanks to Java SPI, where you need to implement `ConstraintProvider`
interface. There is an example of a custom constraint implementation in
`examples/constraints` in the source distribution you can follow to implement
your own constraints.
+
+The advantage of this approach is that you can use an official release while
you can just "patch" Cassandra by
+providing your additional constraints specific to your business / needs. Feel
free to reach us if you want
+to make your constraint in-built!
+
+If you start to use custom constraints, your nodes will fail to start if there
isn't appropriate JAR these
+constraints are implemented in. If constraints specified in JAR via SPI were
not present, we skipped them and
+a user didn't notice, then it might be possible to insert invalid data (per
constraint) which we consider
+is not desired behavior.
+
== AVAILABLE CONSTRAINTS
=== SCALAR CONSTRAINT
diff --git a/examples/constraints/README.adoc b/examples/constraints/README.adoc
new file mode 100644
index 0000000000..9baafeccd3
--- /dev/null
+++ b/examples/constraints/README.adoc
@@ -0,0 +1,46 @@
+== Cassandra Constraint Example
+
+
+The `CustomConstraintsProvider` class will create custom constraints. For the
purposes of this example,
+there will be two constraints: `MY_CUSTOM_CONSTRAINT` and
`MY_CUSTOM_UNARY_CONSTRAINT`.
+
+If you want to code your own constraints without patching Cassandra yourself,
you need to do the following:
+
+1. Code against interface
`org.apache.cassandra.cql3.constraints.ConstraintProvider`.
+2. Put the class implementing this interface to
`META-INF/services/org.apache.cassandra.cql3.constraints.ConstraintProvider`
+3. Build a JAR both with the implementation and `META-INF` resources, as show
in this example, and put this JAR onto
+Cassandra's classpath.
+4. When Cassandra starts, it will auto-detect new constraints by loading
`CustomConstraintsProvider` in your JAR.
+Custom constraints are resolved first, in-built ones when constraint can not
be resolved by given provider. This
+means that if you have a constraint in Cassandra which is buggy, you can
create your own provider, and return constraint
+which "fixes" the in-built one, without any need to patch Cassandra itself. Of
course, you can just implement any
+other constraint you want so you can start to use your custom constraints
without depending on Cassandra release etc.
+
+There is only one `ConstraintProvider` loaded via `ServiceLoader`.
+
+=== Installation
+
+----
+$ cd <cassandra_src_dir>/examples/constraints
+$ ant install
+----
+
+It will build the constraints and will copy it to `lib` as well as to
`build/lib/jars`.
+
+You remove it from everywhere by
+
+----
+$ cd <cassandra_src_dir>/examples/constraints
+$ ant clean
+----
+
+=== Usage
+
+----
+cqlsh> CREATE KEYSPACE ks WITH replication = {'class': 'SimpleStrategy',
'replication_factor': 1};
+cqlsh> CREATE TABLE ks.tb (id int primary key, col text CHECK
MY_CUSTOM_CONSTRAINT() > 10);
+cqlsh> ALTER TABLE ks.tb ALTER col CHECK MY_CUSTOM_CONSTRAINT() > 20;
+----
+
+`MY_CUSTOM_CONSTRAINT` is functionally same as `LENGTH` constraint and
`MY_CUSTOM_UNARY_CONSTRAINT` is functionally
+same as `JSON` constraint.
\ No newline at end of file
diff --git a/examples/constraints/build.xml b/examples/constraints/build.xml
new file mode 100644
index 0000000000..cd5db0fa44
--- /dev/null
+++ b/examples/constraints/build.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+-->
+
+<project default="jar" name="constraint-example">
+ <property name="cassandra.dir" value="../.." />
+ <property name="cassandra.dir.lib" value="${cassandra.dir}/lib" />
+ <property name="cassandra.classes"
value="${cassandra.dir}/build/classes/main" />
+ <property name="build.src" value="${basedir}/src" />
+ <property name="build.dir" value="${basedir}/build" />
+ <property name="conf.dir" value="${basedir}/conf" />
+ <property name="build.classes" value="${build.dir}/classes" />
+ <property name="final.name" value="constraint-example" />
+
+ <path id="build.classpath">
+ <fileset dir="${cassandra.dir.lib}">
+ <include name="**/*.jar" />
+ </fileset>
+ <fileset dir="${cassandra.dir}/build/lib/jars">
+ <include name="**/*.jar" />
+ </fileset>
+ <pathelement location="${cassandra.classes}" />
+ </path>
+ <target name="init">
+ <mkdir dir="${build.classes}" />
+ </target>
+
+ <target name="build" depends="init">
+ <javac destdir="${build.classes}" debug="true"
includeantruntime="false">
+ <src path="${build.src}" />
+ <classpath refid="build.classpath" />
+ </javac>
+ </target>
+
+ <target name="jar" depends="build">
+ <jar jarfile="${build.dir}/${final.name}.jar">
+ <fileset dir="${build.classes}" />
+ <fileset dir="${build.src}/resources"/>
+ </jar>
+ </target>
+
+ <target name="install" depends="jar">
+ <copy verbose="true" file="${build.dir}/${final.name}.jar"
todir="${cassandra.dir}/lib" overwrite="true"/>
+ <copy verbose="true" file="${build.dir}/${final.name}.jar"
todir="${cassandra.dir}/build/lib/jars" overwrite="true"/>
+ </target>
+
+ <target name="clean">
+ <delete dir="${build.dir}" />
+ <delete file="${cassandra.dir}/lib/${final.name}.jar"/>
+ <delete
file="${cassandra.dir}/build/lib/jars/${final.name}.jar"/>
+ </target>
+</project>
diff --git
a/examples/constraints/src/org/apache/cassandra/cql3/constraints/CustomConstraintsProvider.java
b/examples/constraints/src/org/apache/cassandra/cql3/constraints/CustomConstraintsProvider.java
new file mode 100644
index 0000000000..4d4ced6158
--- /dev/null
+++
b/examples/constraints/src/org/apache/cassandra/cql3/constraints/CustomConstraintsProvider.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cassandra.cql3.constraints;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.cassandra.cql3.constraints.ColumnConstraint;
+import org.apache.cassandra.cql3.constraints.ConstraintFunction;
+import org.apache.cassandra.cql3.constraints.ConstraintProvider;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver;
+import
org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionException;
+import org.apache.cassandra.cql3.constraints.JsonConstraint;
+import org.apache.cassandra.cql3.constraints.LengthConstraint;
+import org.apache.cassandra.cql3.constraints.SatisfiabilityChecker;
+import
org.apache.cassandra.cql3.constraints.SatisfiabilityChecker.UnaryFunctionSatisfiabilityChecker;
+import org.apache.cassandra.cql3.constraints.UnaryConstraintFunction;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import static
org.apache.cassandra.cql3.constraints.AbstractFunctionSatisfiabilityChecker.FUNCTION_SATISFIABILITY_CHECKER;
+import static
org.apache.cassandra.cql3.constraints.ConstraintResolver.getConstraintFunction;
+import static
org.apache.cassandra.cql3.constraints.ConstraintResolver.getUnaryConstraintFunction;
+
+public class CustomConstraintsProvider implements ConstraintProvider
+{
+ @Override
+ public Optional<UnaryConstraintFunction> getUnaryConstraint(String
functionName, List<String> arguments)
+ {
+ if (!functionName.equalsIgnoreCase(MyCustomUnaryConstraint.NAME))
+ return Optional.empty();
+
+ return Optional.of(new MyCustomUnaryConstraint(arguments));
+ }
+
+ @Override
+ public Optional<ConstraintFunction> getConstraintFunction(String
functionName, List<String> arguments)
+ {
+ if (!functionName.equalsIgnoreCase(MyCustomConstraint.NAME))
+ return Optional.empty();
+
+ return Optional.of(new MyCustomConstraint(arguments));
+ }
+
+ @Override
+ public List<? extends SatisfiabilityChecker>
getUnaryConstraintSatisfiabilityCheckers()
+ {
+ return List.of(new MyCustomUnaryConstraint(List.of()));
+ }
+
+ @Override
+ public List<? extends SatisfiabilityChecker>
getConstraintFunctionSatisfiabilityCheckers()
+ {
+ return List.of((constraints, columnMetadata) ->
+
FUNCTION_SATISFIABILITY_CHECKER.check(MyCustomConstraint.NAME,
+ constraints,
+ columnMetadata));
+ }
+
+ /**
+ * Same as length constraint, just under different name to prove the point
+ */
+ public static class MyCustomConstraint extends LengthConstraint
+ {
+ public static final String NAME = "MY_CUSTOM_CONSTRAINT";
+
+ public MyCustomConstraint(List<String> arguments)
+ {
+ super(NAME, arguments);
+ }
+ }
+
+ /**
+ * Same as JSON constraint, just under different name to prove the point
+ */
+ public static class MyCustomUnaryConstraint extends JsonConstraint
+ {
+ public static final String NAME = "MY_CUSTOM_UNARY_CONSTRAINT";
+
+ public MyCustomUnaryConstraint(List<String> arguments)
+ {
+ super(NAME, arguments);
+ }
+ }
+}
\ No newline at end of file
diff --git
a/examples/constraints/src/resources/META-INF/services/org.apache.cassandra.cql3.constraints.ConstraintProvider
b/examples/constraints/src/resources/META-INF/services/org.apache.cassandra.cql3.constraints.ConstraintProvider
new file mode 100644
index 0000000000..a6f61e4087
--- /dev/null
+++
b/examples/constraints/src/resources/META-INF/services/org.apache.cassandra.cql3.constraints.ConstraintProvider
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.cassandra.cql3.constraints.CustomConstraintsProvider
\ No newline at end of file
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/AbstractFunctionConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/AbstractFunctionConstraint.java
index 2f6805bdef..a22453175b 100644
---
a/src/java/org/apache/cassandra/cql3/constraints/AbstractFunctionConstraint.java
+++
b/src/java/org/apache/cassandra/cql3/constraints/AbstractFunctionConstraint.java
@@ -22,7 +22,6 @@ import java.util.List;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.Operator;
-import org.apache.cassandra.utils.LocalizeString;
public abstract class AbstractFunctionConstraint<T> extends ColumnConstraint<T>
{
@@ -52,16 +51,4 @@ public abstract class AbstractFunctionConstraint<T> extends
ColumnConstraint<T>
{
builder.append(toString());
}
-
- public static <T extends Enum<T>> T getEnum(Class<T> enumClass, String
functionName)
- {
- try
- {
- return Enum.valueOf(enumClass,
LocalizeString.toUpperCaseLocalized(functionName));
- }
- catch (IllegalArgumentException e)
- {
- throw new InvalidConstraintDefinitionException("Unrecognized
constraint function: " + functionName);
- }
- }
}
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/ColumnConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/ColumnConstraint.java
index bddea571aa..727790f262 100644
--- a/src/java/org/apache/cassandra/cql3/constraints/ColumnConstraint.java
+++ b/src/java/org/apache/cassandra/cql3/constraints/ColumnConstraint.java
@@ -55,9 +55,9 @@ public abstract class ColumnConstraint<T>
// We are serializing its enum position instead of its name.
// Changing this enum would affect how that int is interpreted when
deserializing.
COMPOSED(ColumnConstraints.serializer, new DuplicatesChecker()),
- FUNCTION(FunctionColumnConstraint.serializer,
FunctionColumnConstraint.getSatisfiabilityCheckers()),
+ FUNCTION(FunctionColumnConstraint.serializer,
ConstraintResolver.getConstraintFunctionSatisfiabilityCheckers()),
SCALAR(ScalarColumnConstraint.serializer, new
ScalarColumnConstraintSatisfiabilityChecker()),
- UNARY_FUNCTION(UnaryFunctionColumnConstraint.serializer,
UnaryFunctionColumnConstraint.Functions.values());
+ UNARY_FUNCTION(UnaryFunctionColumnConstraint.serializer,
ConstraintResolver.getUnarySatisfiabilityCheckers());
private final MetadataSerializer<?> serializer;
private final SatisfiabilityChecker[] satisfiabilityCheckers;
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/ConstraintProvider.java
b/src/java/org/apache/cassandra/cql3/constraints/ConstraintProvider.java
new file mode 100644
index 0000000000..3fa04e8995
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/constraints/ConstraintProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.cql3.constraints;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Users implementing this interface and integrating it with SPI (putting JAR
on a class path and
+ * adding it to META-INF/services/ConstraintProvider) will enrich Cassandra
with their custom constraints.
+ */
+public interface ConstraintProvider
+{
+ /**
+ * Tries to instantiate {@link UnaryConstraintFunction} with given
arguments.
+ * <p>
+ * An implementation of this method should always return new object for
each method call. Do not
+ * cache constraint instances and do not return them! Create a new
instance every time. Do not re-use it.
+ *
+ * @param functionName name of function
+ * @param arguments arguments to the function
+ * @return unary constraint function when possible to create with this
provider, empty optional otherwise.
+ */
+ Optional<UnaryConstraintFunction> getUnaryConstraint(String functionName,
List<String> arguments);
+
+ /**
+ * Tries to instantiate {@link ConstraintFunction} with given arguments.
+ * <p>
+ * An implementation of this method should always return new object for
each method call. Do not
+ * cache constraint instances and do not return them! Create a new
instance every time. Do not re-use it.
+ *
+ * @param functionName name of function
+ * @param arguments arguments to the function
+ * @return constraint function when possible to create with this provider,
empty optional otherwise.
+ */
+ Optional<ConstraintFunction> getConstraintFunction(String functionName,
List<String> arguments);
+
+ /**
+ * @return list of satisfiability checkers for all unary constraints this
provider is responsible for
+ */
+ List<? extends SatisfiabilityChecker>
getUnaryConstraintSatisfiabilityCheckers();
+
+ /**
+ * @return list of satisfiability checkers for all function constraints
this provider is responsible for
+ */
+ List<? extends SatisfiabilityChecker>
getConstraintFunctionSatisfiabilityCheckers();
+}
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/ConstraintResolver.java
b/src/java/org/apache/cassandra/cql3/constraints/ConstraintResolver.java
new file mode 100644
index 0000000000..6ac7cfcd28
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/constraints/ConstraintResolver.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.cql3.constraints;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.function.Function;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import
org.apache.cassandra.cql3.constraints.SatisfiabilityChecker.UnaryFunctionSatisfiabilityChecker;
+import org.apache.cassandra.utils.LocalizeString;
+
+public class ConstraintResolver
+{
+ private static final Logger logger =
LoggerFactory.getLogger(ConstraintResolver.class);
+
+ @VisibleForTesting
+ public static ConstraintProvider customConstraintProvider =
ServiceLoader.load(ConstraintProvider.class).findFirst().orElse(null);
+
+ static
+ {
+ if (customConstraintProvider != null)
+ logger.info("Found custom constraint provider {}",
customConstraintProvider.getClass().getName());
+ }
+
+ public enum UnaryFunctions implements UnaryFunctionSatisfiabilityChecker
+ {
+ NOT_NULL(NotNullConstraint::new),
+ JSON(JsonConstraint::new);
+
+ public final Function<List<String>, ConstraintFunction>
functionCreator;
+
+ UnaryFunctions(Function<List<String>, ConstraintFunction>
functionCreator)
+ {
+ this.functionCreator = functionCreator;
+ }
+ }
+
+ public enum Functions
+ {
+ LENGTH(LengthConstraint::new),
+ OCTET_LENGTH(OctetLengthConstraint::new),
+ REGEXP(RegexpConstraint::new);
+
+ public final Function<List<String>, ConstraintFunction>
functionCreator;
+
+ Functions(Function<List<String>, ConstraintFunction> functionCreator)
+ {
+ this.functionCreator = functionCreator;
+ }
+ }
+
+ /**
+ * Returns implementation of function constraint. First, it iterates over
custom functions, when not found,
+ * then it will look into built-in ones. If it is not found in either,
throws an exception.
+ *
+ * @param functionName name of function of get an instance of a constraint
of
+ * @param arguments arguments for constraint
+ * @return new instance of constraint for given name
+ * @throws InvalidConstraintDefinitionException in case constraint can not
be resolved.
+ */
+ public static ConstraintFunction getConstraintFunction(String
functionName, List<String> arguments)
+ {
+ if (customConstraintProvider != null)
+ {
+ Optional<ConstraintFunction> maybeConstraint =
customConstraintProvider.getConstraintFunction(functionName, arguments);
+ if (maybeConstraint.isPresent())
+ return maybeConstraint.get();
+ }
+
+ return ConstraintResolver.getEnum(Functions.class, functionName)
+ .map(c -> c.functionCreator.apply(arguments))
+ .orElseThrow(() -> new
InvalidConstraintDefinitionException("Unrecognized constraint function: " +
functionName));
+ }
+
+ /**
+ * Returns implementation of unary function constraint. First, it iterates
over built-in functions, when not found,
+ * then it will look into custom constraint provider, if any. If custom
provider is not set or if it is not found
+ * there either, throws an exception.
+ *
+ * @param functionName name of function of get an instance of a constraint
of
+ * @param arguments arguments for constraint
+ * @return new instance of constraint for given name
+ * @throws InvalidConstraintDefinitionException in case constraint can not
be resolved.
+ */
+ public static ConstraintFunction getUnaryConstraintFunction(String
functionName, List<String> arguments)
+ {
+ if (customConstraintProvider != null)
+ {
+ Optional<UnaryConstraintFunction> maybeConstraint =
customConstraintProvider.getUnaryConstraint(functionName, arguments);
+ if (maybeConstraint.isPresent())
+ return maybeConstraint.get();
+ }
+
+ return ConstraintResolver.getEnum(UnaryFunctions.class, functionName)
+ .map(c -> c.functionCreator.apply(arguments))
+ .orElseThrow(() -> new
InvalidConstraintDefinitionException("Unrecognized constraint function: " +
functionName));
+ }
+
+ public static SatisfiabilityChecker[]
getConstraintFunctionSatisfiabilityCheckers()
+ {
+ List<SatisfiabilityChecker> checkers = new
ArrayList<>(Arrays.asList(FunctionColumnConstraint.getSatisfiabilityCheckers()));
+
+ if (customConstraintProvider != null)
+ {
+ List<? extends SatisfiabilityChecker> checkersCustom =
customConstraintProvider.getConstraintFunctionSatisfiabilityCheckers();
+ if (checkersCustom != null)
+ checkers.addAll(checkersCustom);
+ }
+
+ return checkers.toArray(new SatisfiabilityChecker[checkers.size()]);
+ }
+
+ public static SatisfiabilityChecker[] getUnarySatisfiabilityCheckers()
+ {
+ List<SatisfiabilityChecker> checkers = new
ArrayList<>(Arrays.asList(UnaryFunctions.values()));
+
+ if (customConstraintProvider != null)
+ {
+ List<? extends SatisfiabilityChecker> checkersCustom =
customConstraintProvider.getUnaryConstraintSatisfiabilityCheckers();
+ if (checkersCustom != null)
+ checkers.addAll(checkersCustom);
+ }
+
+ return checkers.toArray(new SatisfiabilityChecker[checkers.size()]);
+ }
+
+ public static <T extends Enum<T>> Optional<T> getEnum(Class<T> enumClass,
String functionName)
+ {
+ try
+ {
+ return Optional.of(Enum.valueOf(enumClass,
LocalizeString.toUpperCaseLocalized(functionName)));
+ }
+ catch (IllegalArgumentException e)
+ {
+ return Optional.empty();
+ }
+ }
+}
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/FunctionColumnConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/FunctionColumnConstraint.java
index a25553bd7b..0c60ed3dbe 100644
---
a/src/java/org/apache/cassandra/cql3/constraints/FunctionColumnConstraint.java
+++
b/src/java/org/apache/cassandra/cql3/constraints/FunctionColumnConstraint.java
@@ -22,10 +22,10 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver.Functions;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
@@ -55,7 +55,7 @@ public class FunctionColumnConstraint extends
AbstractFunctionConstraint<Functio
this.term = term;
if (arguments == null)
arguments = new ArrayList<>();
- function = createConstraintFunction(functionName.toCQLString(),
arguments);
+ function =
ConstraintResolver.getConstraintFunction(functionName.toCQLString(), arguments);
}
public FunctionColumnConstraint prepare()
@@ -76,25 +76,6 @@ public class FunctionColumnConstraint extends
AbstractFunctionConstraint<Functio
return satisfiabilityCheckers;
}
- public enum Functions
- {
- LENGTH(LengthConstraint::new),
- OCTET_LENGTH(OctetLengthConstraint::new),
- REGEXP(RegexpConstraint::new);
-
- private final Function<List<String>, ConstraintFunction>
functionCreator;
-
- Functions(Function<List<String>, ConstraintFunction> functionCreator)
- {
- this.functionCreator = functionCreator;
- }
- }
-
- private static ConstraintFunction createConstraintFunction(String
functionName, List<String> args)
- {
- return getEnum(Functions.class,
functionName).functionCreator.apply(args);
- }
-
private FunctionColumnConstraint(ConstraintFunction function, Operator
relationType, String term)
{
super(relationType, term);
@@ -211,7 +192,7 @@ public class FunctionColumnConstraint extends
AbstractFunctionConstraint<Functio
ConstraintFunction function;
try
{
- function = createConstraintFunction(functionName, args);
+ function =
ConstraintResolver.getConstraintFunction(functionName, args);
}
catch (Exception e)
{
diff --git a/src/java/org/apache/cassandra/cql3/constraints/JsonConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/JsonConstraint.java
index 15a19d7954..540d471696 100644
--- a/src/java/org/apache/cassandra/cql3/constraints/JsonConstraint.java
+++ b/src/java/org/apache/cassandra/cql3/constraints/JsonConstraint.java
@@ -36,9 +36,14 @@ public class JsonConstraint extends UnaryConstraintFunction
public static final String FUNCTION_NAME = "JSON";
+ public JsonConstraint(String name, List<String> args)
+ {
+ super(name, args);
+ }
+
public JsonConstraint(List<String> args)
{
- super(FUNCTION_NAME, args);
+ this(FUNCTION_NAME, args);
}
@Override
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/LengthConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/LengthConstraint.java
index 59d78afcaa..3304762713 100644
--- a/src/java/org/apache/cassandra/cql3/constraints/LengthConstraint.java
+++ b/src/java/org/apache/cassandra/cql3/constraints/LengthConstraint.java
@@ -34,9 +34,14 @@ public class LengthConstraint extends ConstraintFunction
private static final String NAME = "LENGTH";
private static final List<AbstractType<?>> SUPPORTED_TYPES =
List.of(BytesType.instance, UTF8Type.instance, AsciiType.instance);
+ public LengthConstraint(String name, List<String> args)
+ {
+ super(name, args);
+ }
+
public LengthConstraint(List<String> args)
{
- super(NAME, args);
+ this(NAME, args);
}
@Override
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/UnaryConstraintFunction.java
b/src/java/org/apache/cassandra/cql3/constraints/UnaryConstraintFunction.java
index 0e4b0ddd2d..d7c3941c6e 100644
---
a/src/java/org/apache/cassandra/cql3/constraints/UnaryConstraintFunction.java
+++
b/src/java/org/apache/cassandra/cql3/constraints/UnaryConstraintFunction.java
@@ -21,8 +21,9 @@ package org.apache.cassandra.cql3.constraints;
import java.util.List;
import org.apache.cassandra.cql3.Operator;
+import
org.apache.cassandra.cql3.constraints.SatisfiabilityChecker.UnaryFunctionSatisfiabilityChecker;
-public abstract class UnaryConstraintFunction extends ConstraintFunction
+public abstract class UnaryConstraintFunction extends ConstraintFunction
implements UnaryFunctionSatisfiabilityChecker
{
public UnaryConstraintFunction(String name, List<String> args)
{
diff --git
a/src/java/org/apache/cassandra/cql3/constraints/UnaryFunctionColumnConstraint.java
b/src/java/org/apache/cassandra/cql3/constraints/UnaryFunctionColumnConstraint.java
index c8edd1189e..6afeb35028 100644
---
a/src/java/org/apache/cassandra/cql3/constraints/UnaryFunctionColumnConstraint.java
+++
b/src/java/org/apache/cassandra/cql3/constraints/UnaryFunctionColumnConstraint.java
@@ -22,13 +22,12 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Function;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator;
-import
org.apache.cassandra.cql3.constraints.SatisfiabilityChecker.UnaryFunctionSatisfiabilityChecker;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver.UnaryFunctions;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
@@ -56,12 +55,12 @@ public class UnaryFunctionColumnConstraint extends
AbstractFunctionConstraint<Un
public Raw(ColumnIdentifier functionName, List<String> arguments)
{
- function = createConstraintFunction(functionName.toString(),
arguments);
+ function =
ConstraintResolver.getUnaryConstraintFunction(functionName.toString(),
arguments);
}
public Raw(ColumnIdentifier functionName)
{
- function = createConstraintFunction(functionName.toString(),
List.of());
+ function =
ConstraintResolver.getUnaryConstraintFunction(functionName.toString(),
List.of());
}
public UnaryFunctionColumnConstraint prepare()
@@ -70,24 +69,6 @@ public class UnaryFunctionColumnConstraint extends
AbstractFunctionConstraint<Un
}
}
- public enum Functions implements UnaryFunctionSatisfiabilityChecker
- {
- NOT_NULL(NotNullConstraint::new),
- JSON(JsonConstraint::new);
-
- private final Function<List<String>, ConstraintFunction>
functionCreator;
-
- Functions(Function<List<String>, ConstraintFunction> functionCreator)
- {
- this.functionCreator = functionCreator;
- }
- }
-
- private static ConstraintFunction createConstraintFunction(String
functionName, List<String> arguments)
- {
- return getEnum(Functions.class,
functionName).functionCreator.apply(arguments);
- }
-
public UnaryFunctionColumnConstraint(ConstraintFunction function)
{
super(null, null);
@@ -134,7 +115,7 @@ public class UnaryFunctionColumnConstraint extends
AbstractFunctionConstraint<Un
@Override
public boolean enablesDuplicateDefinitions(String name)
{
- return Functions.valueOf(name).enableDuplicateDefinitions();
+ return UnaryFunctions.valueOf(name).enableDuplicateDefinitions();
}
@Override
@@ -209,7 +190,7 @@ public class UnaryFunctionColumnConstraint extends
AbstractFunctionConstraint<Un
@VisibleForTesting
public ConstraintFunction getConstraintFunction(String functionName,
List<String> args)
{
- return createConstraintFunction(functionName, args);
+ return ConstraintResolver.getUnaryConstraintFunction(functionName,
args);
}
@Override
diff --git
a/test/unit/org/apache/cassandra/constraints/ConstraintsProviderTest.java
b/test/unit/org/apache/cassandra/constraints/ConstraintsProviderTest.java
new file mode 100644
index 0000000000..eb3e617cbc
--- /dev/null
+++ b/test/unit/org/apache/cassandra/constraints/ConstraintsProviderTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.constraints;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.Test;
+
+import
org.apache.cassandra.constraints.ConstraintsProviderTest.CustomConstraintProvider.MyCustomConstraint;
+import
org.apache.cassandra.constraints.ConstraintsProviderTest.CustomConstraintProvider.MyCustomUnaryConstraint;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.cql3.constraints.ConstraintFunction;
+import org.apache.cassandra.cql3.constraints.ConstraintProvider;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver.Functions;
+import org.apache.cassandra.cql3.constraints.ConstraintResolver.UnaryFunctions;
+import
org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionException;
+import org.apache.cassandra.cql3.constraints.JsonConstraint;
+import org.apache.cassandra.cql3.constraints.LengthConstraint;
+import org.apache.cassandra.cql3.constraints.SatisfiabilityChecker;
+import org.apache.cassandra.cql3.constraints.UnaryConstraintFunction;
+import org.apache.cassandra.db.marshal.UTF8Type;
+
+import static
org.apache.cassandra.cql3.constraints.AbstractFunctionSatisfiabilityChecker.FUNCTION_SATISFIABILITY_CHECKER;
+import static
org.apache.cassandra.cql3.constraints.ConstraintResolver.customConstraintProvider;
+import static
org.apache.cassandra.cql3.constraints.ConstraintResolver.getConstraintFunction;
+import static
org.apache.cassandra.cql3.constraints.ConstraintResolver.getUnaryConstraintFunction;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class ConstraintsProviderTest
+{
+ @Test
+ public void testContraintProvider()
+ {
+ customConstraintProvider = new CustomConstraintProvider();
+
+ ConstraintFunction constraintFunction =
getConstraintFunction(MyCustomConstraint.NAME, List.of());
+ assertNotNull(constraintFunction);
+ constraintFunction.evaluate(UTF8Type.instance,
+ Operator.EQ,
+
Integer.toString(MyCustomConstraint.NAME.length()),
+
UTF8Type.instance.fromString(MyCustomConstraint.NAME));
+
+ ConstraintFunction unaryConstraint =
getUnaryConstraintFunction(MyCustomUnaryConstraint.NAME, List.of());
+ assertNotNull(unaryConstraint);
+ unaryConstraint.evaluate(UTF8Type.instance,
UTF8Type.instance.fromString("{\"a\": 4, \"b\": 10}"));
+
+ assertThatThrownBy(() -> getConstraintFunction("not_existing",
List.of()))
+ .isInstanceOf(InvalidConstraintDefinitionException.class);
+
+ SatisfiabilityChecker[] functionSatCheckers =
ConstraintResolver.getConstraintFunctionSatisfiabilityCheckers();
+ assertNotNull(functionSatCheckers);
+ // in built + these in provider
+ assertEquals(functionSatCheckers.length,
+ Functions.values().length +
customConstraintProvider.getConstraintFunctionSatisfiabilityCheckers().size());
+
+ SatisfiabilityChecker[] unarySatCheckers =
ConstraintResolver.getUnarySatisfiabilityCheckers();
+ assertNotNull(unarySatCheckers);
+ // in built + these in provider
+ assertEquals(unarySatCheckers.length,
+ UnaryFunctions.values().length +
customConstraintProvider.getUnaryConstraintSatisfiabilityCheckers().size());
+ }
+
+ public static class CustomConstraintProvider implements ConstraintProvider
+ {
+ @Override
+ public Optional<UnaryConstraintFunction> getUnaryConstraint(String
functionName, List<String> arguments)
+ {
+ if (!functionName.equalsIgnoreCase(MyCustomUnaryConstraint.NAME))
+ return Optional.empty();
+
+ return Optional.of(new MyCustomUnaryConstraint(arguments));
+ }
+
+ @Override
+ public Optional<ConstraintFunction> getConstraintFunction(String
functionName, List<String> arguments)
+ {
+ if (!functionName.equalsIgnoreCase(MyCustomConstraint.NAME))
+ return Optional.empty();
+
+ return Optional.of(new MyCustomConstraint(arguments));
+ }
+
+ @Override
+ public List<? extends SatisfiabilityChecker>
getUnaryConstraintSatisfiabilityCheckers()
+ {
+ return List.of(new MyCustomUnaryConstraint(List.of()));
+ }
+
+ @Override
+ public List<? extends SatisfiabilityChecker>
getConstraintFunctionSatisfiabilityCheckers()
+ {
+ return List.of((constraints, columnMetadata) ->
+
FUNCTION_SATISFIABILITY_CHECKER.check(MyCustomConstraint.NAME,
+ constraints,
+
columnMetadata));
+ }
+
+ /**
+ * Same as length constraint, just under different name to prove the
point
+ */
+ public static class MyCustomConstraint extends LengthConstraint
+ {
+ public static final String NAME = "MY_CUSTOM_CONSTRAINT";
+
+ public MyCustomConstraint(List<String> arguments)
+ {
+ super(NAME, arguments);
+ }
+ }
+
+ /**
+ * Same as JSON constraint, just under different name to prove the
point
+ */
+ public static class MyCustomUnaryConstraint extends JsonConstraint
+ {
+ public static final String NAME = "MY_CUSTOM_UNARY_CONSTRAINT";
+
+ public MyCustomUnaryConstraint(List<String> arguments)
+ {
+ super(NAME, arguments);
+ }
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]