jmckenzie-dev commented on code in PR #3562:
URL: https://github.com/apache/cassandra/pull/3562#discussion_r1917283496


##########
doc/modules/cassandra/pages/developing/cql/constraints.adoc:
##########
@@ -0,0 +1,93 @@
+= Constraints
+
+Constraints provide a way of specifying and enforcing conditions at a
+column level in a table schema definition and enforcing them at write time.
+
+== CREATE CONSTRAINT
+
+Constraints can be created within the column definition, or as part
+of the table properties.
+
+The main syntaxis to define a constraint is as follows:
+
+[source,bnf]
+----
+CREATE TABLE keyspace.table (
+       name text,
+       i int CHECK (condition) (AND (condition))*
+       ...,
+
+);
+----
+
+As shown in this syntax, more than one constraint can be defined for a given 
column using the AND keyword.
+
+== ALTER CONSTRAINT
+
+Altering a constraint is done by following the alter column CQL syntax:
+[source,bnf]
+----
+ALTER TABLE [IF EXISTS] <table> ALTER [IF EXISTS] <column> CHECK <condition>;
+----
+
+== DROP CONSTRAINT
+And DROP can be used to drop constraints for a column as well.
+[source,bnf]
+----
+ALTER TABLE [IF EXISTS] <table> ALTER [IF EXISTS] <column> DROP CHECK;
+----
+
+== AVAILABLE CONSTRAINTS
+
+=== SCALAR CONSTRAINT
+
+Defines a comparator against a numeric type. It support all numeric types 
supported in Cassandra, with all the regular
+comparators.
+
+For example, we can define constraints that ensure that i is bigger or equal 
than 100 but smaller than 1000.
+
+[source,bnf]
+----
+CREATE TABLE keyspace.table (
+       name text,
+       i int CHECK i < 1000 AND i > 100
+       ...,
+);
+----
+
+Altering that constraint can be done with:
+
+----
+ALTER TABLE keyspace.table ALTER i CHECK i >= 500;
+----
+
+Finally, the constraint can be removed:
+
+----
+ALTER TABLE keyspace.table ALTER i DROP CHECK;
+----
+
+=== LENGTH CONSTRAINT
+
+Defines a condition that checks the lenght of text or binary type.

Review Comment:
   nit: length



##########
src/java/org/apache/cassandra/cql3/constraints/FunctionColumnConstraint.java:
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.io.IOException;
+
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+public class FunctionColumnConstraint implements 
ColumnConstraint<FunctionColumnConstraint>
+{
+    public static final Serializer serializer = new Serializer();
+
+    public final ConstraintFunction function;
+    public final ColumnIdentifier columnName;
+    public final Operator relationType;
+    public final String term;
+
+    public final static class Raw
+    {
+        public final ConstraintFunction function;
+        public final ColumnIdentifier columnName;
+        public final Operator relationType;
+        public final String term;
+
+        public Raw(ColumnIdentifier functionName, ColumnIdentifier columnName, 
Operator relationType, String term)
+        {
+            this.relationType = relationType;
+            this.columnName = columnName;
+            this.term = term;
+            function = createConstraintFunction(functionName.toCQLString(), 
columnName);
+        }
+
+        public FunctionColumnConstraint prepare()
+        {
+            return new FunctionColumnConstraint(function, columnName, 
relationType, term);
+        }
+    }
+
+    private static ConstraintFunction createConstraintFunction(String 
functionName, ColumnIdentifier columnName)
+    {
+        if (LengthConstraint.FUNCTION_NAME.equalsIgnoreCase(functionName))
+            return new LengthConstraint(columnName);
+        throw new InvalidConstraintDefinitionException("Unrecognized 
constraint function: " + functionName);
+    }
+
+    private FunctionColumnConstraint(ConstraintFunction function, 
ColumnIdentifier columnName, Operator relationType, String term)
+    {
+        this.function = function;
+        this.columnName = columnName;
+        this.relationType = relationType;
+        this.term = term;
+    }
+
+    @Override
+    public void appendCqlTo(CqlBuilder builder)
+    {
+        builder.append(toString());
+    }
+
+    @Override
+    public IVersionedSerializer<FunctionColumnConstraint> serializer()
+    {
+        return serializer;
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue)
+    {
+        function.evaluate(valueType, relationType, term, columnValue);
+    }
+
+    @Override
+    public void validate(ColumnMetadata columnMetadata)
+    {
+        validateArgs(columnMetadata);
+        function.validate(columnMetadata);
+    }
+
+    @Override
+    public ConstraintType getConstraintType()
+    {
+        return ConstraintType.FUNCTION;
+    }
+
+    void validateArgs(ColumnMetadata columnMetadata)
+    {
+        if (!columnMetadata.name.equals(columnName))
+            throw new InvalidConstraintDefinitionException("Function parameter 
should be the column name");
+    }
+
+    @Override
+    public String toString()
+    {
+        return function.getName() + "(" + columnName + ") " + relationType + " 
" + term;
+    }
+
+    public static class Serializer implements 
IVersionedSerializer<FunctionColumnConstraint>
+    {
+        @Override
+        public void serialize(FunctionColumnConstraint columnConstraint, 
DataOutputPlus out, int version) throws IOException
+        {
+            FunctionColumnConstraint condition = columnConstraint;

Review Comment:
   Looks like `condition` is just a redundant alias of `columnConstraint`? Same 
with `serializedSize`? 



##########
src/java/org/apache/cassandra/cql3/constraints/LengthConstraint.java:
##########
@@ -0,0 +1,118 @@
+/*
+ * 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 com.google.common.collect.Sets;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.AsciiType;
+import org.apache.cassandra.db.marshal.BytesType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.nio.ByteBuffer;

Review Comment:
   nit: base java imports above o.a.c



##########
src/java/org/apache/cassandra/schema/ColumnMetadata.java:
##########
@@ -275,6 +318,11 @@ public boolean isMasked()
         return mask != null;
     }
 
+    public boolean hasConstraint()
+    {
+        return hasConstraints;

Review Comment:
   Why cache this instead of always evaluating `!columnConstraints.isEmpty()`?



##########
src/java/org/apache/cassandra/schema/ColumnMetadata.java:
##########
@@ -627,11 +716,24 @@ public ColumnMetadata deserialize(DataInputPlus in, Types 
types, UserFunctions f
             boolean masked = in.readBoolean();
             if (masked)
                 mask = ColumnMask.serializer.deserialize(in, ksName, type, 
types, functions, version);
-            return new ColumnMetadata(ksName, tableName, new 
ColumnIdentifier(nameBB, name), type, position, kind, mask);
+            ColumnConstraints constraints;
+            if (version.isAtLeast(Version.V6) && in.readBoolean())
+                constraints = ColumnConstraints.serializer.deserialize(in, 
version.asInt());
+            else
+                constraints = ColumnConstraints.NO_OP;
+            return new ColumnMetadata(ksName, tableName, new 
ColumnIdentifier(nameBB, name), type, position, kind, mask, constraints);
         }
 
         public long serializedSize(ColumnMetadata t, Version version)
         {
+            long constraintsSize = 0;
+            if (version.isAtLeast(Version.V6))

Review Comment:
   How does our logic resolve / handle being in a mixed version cluster where 
some members are older versions and some newer? Does TCM keep the version at 
the lowest common denominator in that case?



##########
doc/modules/cassandra/pages/developing/cql/constraints.adoc:
##########
@@ -0,0 +1,93 @@
+= Constraints
+
+Constraints provide a way of specifying and enforcing conditions at a
+column level in a table schema definition and enforcing them at write time.
+
+== CREATE CONSTRAINT
+
+Constraints can be created within the column definition, or as part
+of the table properties.
+
+The main syntaxis to define a constraint is as follows:

Review Comment:
   nit: syntax



##########
doc/modules/cassandra/pages/developing/cql/constraints.adoc:
##########
@@ -0,0 +1,93 @@
+= Constraints
+
+Constraints provide a way of specifying and enforcing conditions at a
+column level in a table schema definition and enforcing them at write time.
+
+== CREATE CONSTRAINT
+
+Constraints can be created within the column definition, or as part
+of the table properties.
+
+The main syntaxis to define a constraint is as follows:
+
+[source,bnf]
+----
+CREATE TABLE keyspace.table (
+       name text,
+       i int CHECK (condition) (AND (condition))*
+       ...,
+
+);
+----
+
+As shown in this syntax, more than one constraint can be defined for a given 
column using the AND keyword.
+
+== ALTER CONSTRAINT
+
+Altering a constraint is done by following the alter column CQL syntax:
+[source,bnf]
+----
+ALTER TABLE [IF EXISTS] <table> ALTER [IF EXISTS] <column> CHECK <condition>;
+----
+
+== DROP CONSTRAINT
+And DROP can be used to drop constraints for a column as well.
+[source,bnf]
+----
+ALTER TABLE [IF EXISTS] <table> ALTER [IF EXISTS] <column> DROP CHECK;

Review Comment:
   To confirm my understanding, if you have multiple checks on a column you 
have to drop all of them and recreate them, we don't allow aliasing specific 
check names and doing granular work modifying or dropping specifics?



##########
doc/modules/cassandra/nav.adoc:
##########
@@ -60,6 +60,7 @@
 *** xref:cassandra:developing/cql/json.adoc[JSON]
 *** xref:cassandra:developing/cql/security.adoc[Security]
 *** xref:cassandra:developing/cql/triggers.adoc[Triggers]
+*** xref:cassandra:developing/cql/constraints.adoc[Constraints]

Review Comment:
   ... how are these organized in here? This isn't a CEP-42 problem btw, more 
of a general "this looks like a random pile of stuff" problem. :)



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraint.java:
##########
@@ -0,0 +1,83 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+/**
+ * Common class for the conditions that a CQL Constraint needs to implement to 
be integrated in the
+ * CQL Constraints framework.
+ */
+public interface ColumnConstraint<T>
+{
+
+    // Enum containing all the possible constraint serializers to help with 
serialization/deserialization
+    // of constraints.
+    public enum ConstraintType
+    {
+        // The order of that enum matters!!

Review Comment:
   Add clarification on why (so future maintainers in both places can take this 
into account).



##########
src/java/org/apache/cassandra/cql3/constraints/ScalarColumnConstraint.java:
##########
@@ -0,0 +1,172 @@
+/*
+ * 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.io.IOException;
+
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+public class ScalarColumnConstraint implements 
ColumnConstraint<ScalarColumnConstraint>
+{
+    public final ColumnIdentifier param;
+    public final Operator relationType;
+    public final String term;
+
+    public final static Serializer serializer = new Serializer();
+
+    public final static class Raw
+    {
+        public final ColumnIdentifier param;
+        public final Operator relationType;
+        public final String term;
+
+        public Raw(ColumnIdentifier param, Operator relationType, String term)
+        {
+            this.param = param;
+            this.relationType = relationType;
+            this.term = term;
+        }
+
+        public ScalarColumnConstraint prepare()
+        {
+            return new ScalarColumnConstraint(param, relationType, term);
+        }
+    }
+
+    private ScalarColumnConstraint(ColumnIdentifier param, Operator 
relationType, String term)
+    {
+        this.param = param;
+        this.relationType = relationType;
+        this.term = term;
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue)
+    {
+        Number columnValueNumber;
+        float sizeConstraint;
+
+        try
+        {
+            columnValueNumber = (Number) columnValue;
+            sizeConstraint = Float.parseFloat(term);
+        }
+        catch (NumberFormatException exception)
+        {
+            throw new ConstraintViolationException(param + " and " + term + " 
need to be numbers.");
+        }
+
+        switch (relationType)
+        {
+            case EQ:
+                if (Float.compare(columnValueNumber.floatValue(), 
sizeConstraint) != 0)
+                    throw new ConstraintViolationException(param + " value 
should be exactly " + sizeConstraint);
+                break;
+            case NEQ:
+                if (Double.compare(columnValueNumber.floatValue(), 
sizeConstraint) == 0)

Review Comment:
   Why are we using `Float.compare` on EQ case and `Double.compare` in NEQ? If 
intentional, should document why. If not, should fix / unify on `Float`.



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+// group of constraints for the column
+public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
+{
+    public static final Serializer serializer = new Serializer();
+
+    private static final Set<Class<? extends AbstractType>> UNSUPPORTED_TYPES 
= ImmutableSet.of(MapType.class,
+                                                                               
                 TupleType.class,
+                                                                               
                 UserType.class,
+                                                                               
                 CompositeType.class,
+                                                                               
                 DynamicCompositeType.class);
+
+
+
+    private final List<ColumnConstraint<?>> constraints;
+    private final int constraintsSize;
+    private final boolean isEmpty;
+
+    public ColumnConstraints(List<ColumnConstraint<?>> constraints)
+    {
+        this.constraints = new ArrayList<>(constraints);
+        // These are catched values to avoid making the calculations every time

Review Comment:
   nit: cached



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;

Review Comment:
   nit: java base imports above o.a.c



##########
doc/modules/cassandra/pages/developing/cql/constraints.adoc:
##########
@@ -0,0 +1,93 @@
+= Constraints
+
+Constraints provide a way of specifying and enforcing conditions at a
+column level in a table schema definition and enforcing them at write time.
+
+== CREATE CONSTRAINT
+
+Constraints can be created within the column definition, or as part
+of the table properties.
+
+The main syntaxis to define a constraint is as follows:
+
+[source,bnf]
+----
+CREATE TABLE keyspace.table (
+       name text,
+       i int CHECK (condition) (AND (condition))*

Review Comment:
   The CEP indicated a couple different syntaxes to add constraints; did we 
keep those options or only allow for the one here? Link for reference: 
https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=309496560#CEP42:ConstraintsFramework-Approach



##########
src/java/org/apache/cassandra/cql3/Json.java:
##########
@@ -69,6 +72,17 @@ public Prepared prepareAndCollectMarkers(TableMetadata 
metadata, Collection<Colu
         {
             return new PreparedLiteral(parseJson(text, receivers));
         }
+
+        @Override
+        public Prepared collectMarkers(TableMetadata metadata, 
Collection<ColumnMetadata> receivers, VariableSpecifications boundNames)
+        {
+            return new UnpreparedLiteral(parseUnpreparedJson(text, receivers));
+        }
+
+        public String getText()

Review Comment:
   This looks to be unused.



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+// group of constraints for the column
+public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
+{
+    public static final Serializer serializer = new Serializer();
+
+    private static final Set<Class<? extends AbstractType>> UNSUPPORTED_TYPES 
= ImmutableSet.of(MapType.class,
+                                                                               
                 TupleType.class,
+                                                                               
                 UserType.class,
+                                                                               
                 CompositeType.class,
+                                                                               
                 DynamicCompositeType.class);
+
+
+
+    private final List<ColumnConstraint<?>> constraints;
+    private final int constraintsSize;
+    private final boolean isEmpty;
+
+    public ColumnConstraints(List<ColumnConstraint<?>> constraints)
+    {
+        this.constraints = new ArrayList<>(constraints);
+        // These are catched values to avoid making the calculations every time
+        this.constraintsSize = constraints.size();
+        this.isEmpty = constraints.isEmpty();

Review Comment:
   Both of these should just be accessing an integer value inside an ArrayList. 
[Example from 
OpenJDK](https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/ArrayList.java#L287-L294):
   ```java
       /**
        * Returns the number of elements in this list.
        *
        * @return the number of elements in this list
        */
       public int size() {
           return size;
       }
   
       /**
        * Returns {@code true} if this list contains no elements.
        *
        * @return {@code true} if this list contains no elements
        */
       public boolean isEmpty() {
           return size == 0;
       }
   ```
   So I _think_ we can drop this caching.



##########
src/java/org/apache/cassandra/cql3/Json.java:
##########
@@ -323,4 +361,63 @@ static Map<ColumnIdentifier, Term> parseJson(String 
jsonString, Collection<Colum
             throw new InvalidRequestException(exc.getMessage());
         }
     }
+
+    static Term.Raw getUnpreparedTerm(ColumnSpecification spec, Object 
parsedJsonObject)
+    {
+        if (spec.type.isNumber())
+            return 
Constants.Literal.floatingPoint(parsedJsonObject.toString());

Review Comment:
   Why are we treating all JSON #'s as floats here?



##########
src/java/org/apache/cassandra/cql3/constraints/ConstraintFunction.java:
##########
@@ -0,0 +1,52 @@
+/*
+ * 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 org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+/**
+ * Interface to be implemented by functions that are executed as part of CQL 
constraints.
+ */
+public interface ConstraintFunction
+{
+    /**
+     * @return the function name to be executed.
+     */
+    String getName();
+
+    /**
+     * Method that provides the execution of the condition. It can either 
succeed or throw a {@link ConstraintViolationException}.
+     *
+     * @param valueType

Review Comment:
   I'd just drop any `@param` that's self-explanatory and we don't add any 
description to. Alternatively feel free to add descriptions if helpful.
   
   Also, the top level javadoc on this is pretty redundant with the method 
signature; maybe elaborate on what happens in the innards of `evaluate` (i.e. 
for a given column value, applies the relation to it and throws an exception if 
input value is outside the bounds of the constraint or something like that).



##########
src/java/org/apache/cassandra/schema/ColumnMetadata.java:
##########
@@ -126,42 +135,60 @@ private static long comparisonOrder(Kind kind, boolean 
isComplex, long position,
 
     public static ColumnMetadata partitionKeyColumn(TableMetadata table, 
ByteBuffer name, AbstractType<?> type, int position)
     {
-        return new ColumnMetadata(table, name, type, position, 
Kind.PARTITION_KEY, null);
+        return new ColumnMetadata(table, name, type, position, 
Kind.PARTITION_KEY, null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata partitionKeyColumn(String keyspace, String 
table, String name, AbstractType<?> type, int position)
     {
-        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, position, Kind.PARTITION_KEY, 
null);
+        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, position, Kind.PARTITION_KEY, 
null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata clusteringColumn(TableMetadata table, 
ByteBuffer name, AbstractType<?> type, int position)
     {
-        return new ColumnMetadata(table, name, type, position, 
Kind.CLUSTERING, null);
+        return new ColumnMetadata(table, name, type, position, 
Kind.CLUSTERING, null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata clusteringColumn(String keyspace, String 
table, String name, AbstractType<?> type, int position)
     {
-        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, position, Kind.CLUSTERING, 
null);
+        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, position, Kind.CLUSTERING, 
null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata regularColumn(TableMetadata table, ByteBuffer 
name, AbstractType<?> type)
     {
-        return new ColumnMetadata(table, name, type, NO_POSITION, 
Kind.REGULAR, null);
+        return new ColumnMetadata(table, name, type, NO_POSITION, 
Kind.REGULAR, null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata regularColumn(String keyspace, String table, 
String name, AbstractType<?> type)
     {
-        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, NO_POSITION, Kind.REGULAR, 
null);
+        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, NO_POSITION, Kind.REGULAR, 
null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata staticColumn(TableMetadata table, ByteBuffer 
name, AbstractType<?> type)
     {
-        return new ColumnMetadata(table, name, type, NO_POSITION, Kind.STATIC, 
null);
+        return new ColumnMetadata(table, name, type, NO_POSITION, Kind.STATIC, 
null, ColumnConstraints.NO_OP);
     }
 
     public static ColumnMetadata staticColumn(String keyspace, String table, 
String name, AbstractType<?> type)
     {
-        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, NO_POSITION, Kind.STATIC, null);
+        return new ColumnMetadata(keyspace, table, 
ColumnIdentifier.getInterned(name, true), type, NO_POSITION, Kind.STATIC, null, 
ColumnConstraints.NO_OP);
+    }
+
+    public ColumnMetadata(TableMetadata table,

Review Comment:
   Stylistic idea here. Right now you've added `ColumnConstraints.NO_OP` to all 
the method calls here which is a lot of noise with very little signal. What if 
we had:
   ```java
   static ColumnMetadata buildWithoutConstraints(...) {}
   static ColumnMetadata buildWithConstraints(..., @Nonnull ColumnConstraints 
columnConstraints){]
   ```
   Where the 1st method we'd call w/all the callers in this class so we don't 
have to pass that redundant `NO_OP` in all of them, and we rely on method names 
to help disambiguate what kind of construction we're doing?
   
   Don't feel super strongly on this but got similar feedback from David a 
couple weeks ago on this w/callbacks and I liked the aesthetics of how the end 
result turned out.



##########
src/java/org/apache/cassandra/cql3/statements/UpdateStatement.java:
##########
@@ -261,6 +269,15 @@ protected ModificationStatement 
prepareInternal(ClientState state,
                                                                            
false,
                                                                            
false);
 
+            Json.Prepared unprepared = jsonValue.collectMarkers(metadata, 
defs, bindVariables);
+            List<ColumnIdentifier> columnNames = new ArrayList<>();

Review Comment:
   I don't see `columnNames` and `columnValues` being used; am I missing 
something?



##########
test/unit/org/apache/cassandra/cql3/validation/operations/InsertTest.java:
##########
@@ -132,12 +132,13 @@ public void testInsert() throws Throwable
     private void testInsert(boolean forceFlush) throws Throwable
     {
         createTable("CREATE TABLE %s (partitionKey int," +
+                                      "pk2 int," +

Review Comment:
   No strong opinion here, but what was the reasoning behind modifying the 
`testInsert` here to use a composite primary key? Specifically as part of this 
patch for constraints here.



##########
src/java/org/apache/cassandra/cql3/Json.java:
##########
@@ -125,6 +145,24 @@ public Term.Raw getRawTermForColumn(ColumnMetadata def, 
boolean defaultUnset)
         }
     }
 
+    private static class UnpreparedLiteral extends Prepared

Review Comment:
   `UnpreparedLiteral extends Prepared` had me scratching my head for a second. 
;) Should we consider renaming `Prepared` and updating the javadoc for it if it 
no longer represents prepared data?



##########
src/java/org/apache/cassandra/cql3/constraints/ScalarColumnConstraint.java:
##########
@@ -0,0 +1,172 @@
+/*
+ * 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.io.IOException;
+
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.cql3.Operator;
+import org.apache.cassandra.db.TypeSizes;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+public class ScalarColumnConstraint implements 
ColumnConstraint<ScalarColumnConstraint>
+{
+    public final ColumnIdentifier param;
+    public final Operator relationType;
+    public final String term;
+
+    public final static Serializer serializer = new Serializer();
+
+    public final static class Raw
+    {
+        public final ColumnIdentifier param;
+        public final Operator relationType;
+        public final String term;
+
+        public Raw(ColumnIdentifier param, Operator relationType, String term)
+        {
+            this.param = param;
+            this.relationType = relationType;
+            this.term = term;
+        }
+
+        public ScalarColumnConstraint prepare()
+        {
+            return new ScalarColumnConstraint(param, relationType, term);
+        }
+    }
+
+    private ScalarColumnConstraint(ColumnIdentifier param, Operator 
relationType, String term)
+    {
+        this.param = param;
+        this.relationType = relationType;
+        this.term = term;
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue)
+    {
+        Number columnValueNumber;
+        float sizeConstraint;
+
+        try
+        {
+            columnValueNumber = (Number) columnValue;
+            sizeConstraint = Float.parseFloat(term);
+        }
+        catch (NumberFormatException exception)
+        {
+            throw new ConstraintViolationException(param + " and " + term + " 
need to be numbers.");
+        }
+
+        switch (relationType)
+        {
+            case EQ:
+                if (Float.compare(columnValueNumber.floatValue(), 
sizeConstraint) != 0)
+                    throw new ConstraintViolationException(param + " value 
should be exactly " + sizeConstraint);
+                break;
+            case NEQ:
+                if (Double.compare(columnValueNumber.floatValue(), 
sizeConstraint) == 0)
+                    throw new ConstraintViolationException(param + " value 
should be different from " + sizeConstraint);
+                break;
+            case GT:
+                if (columnValueNumber.floatValue() <= sizeConstraint)
+                    throw new ConstraintViolationException(param + " value 
should be larger than " + sizeConstraint);
+                break;
+            case LT:
+                if (columnValueNumber.floatValue() >= sizeConstraint)
+                    throw new ConstraintViolationException(param + " value 
should be smaller than " + sizeConstraint);
+                break;
+            case GTE:
+                if (columnValueNumber.floatValue() < sizeConstraint)
+                    throw new ConstraintViolationException(param + " value 
should be larger or equal than " + sizeConstraint);
+                break;
+            case LTE:
+                if (columnValueNumber.floatValue() > sizeConstraint)
+                    throw new ConstraintViolationException(param + " value 
should be smaller or equal than " + sizeConstraint);
+                break;
+            default:
+                throw new ConstraintViolationException("Invalid relation type: 
" + relationType);
+        }
+    }
+
+    @Override
+    public void validate(ColumnMetadata columnMetadata) throws 
InvalidConstraintDefinitionException
+    {
+        if (!columnMetadata.type.isNumber())
+            throw new InvalidConstraintDefinitionException(param + " is not a 
number");
+    }
+
+    @Override
+    public ConstraintType getConstraintType()
+    {
+        return ConstraintType.SCALAR;
+    }
+
+    @Override
+    public String toString()
+    {
+        return param + " " + relationType + ' ' + term;

Review Comment:
   nit: consistency with " " vs. ' '



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+// group of constraints for the column
+public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
+{
+    public static final Serializer serializer = new Serializer();
+
+    private static final Set<Class<? extends AbstractType>> UNSUPPORTED_TYPES 
= ImmutableSet.of(MapType.class,
+                                                                               
                 TupleType.class,
+                                                                               
                 UserType.class,
+                                                                               
                 CompositeType.class,
+                                                                               
                 DynamicCompositeType.class);
+
+
+
+    private final List<ColumnConstraint<?>> constraints;
+    private final int constraintsSize;
+    private final boolean isEmpty;
+
+    public ColumnConstraints(List<ColumnConstraint<?>> constraints)
+    {
+        this.constraints = new ArrayList<>(constraints);
+        // These are catched values to avoid making the calculations every time
+        this.constraintsSize = constraints.size();
+        this.isEmpty = constraints.isEmpty();
+    }
+
+    @Override
+    public IVersionedSerializer<ColumnConstraints> serializer()
+    {
+        return serializer;
+    }
+
+    @Override
+    public void appendCqlTo(CqlBuilder builder)
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.appendCqlTo(builder);
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue) throws ConstraintViolationException
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.evaluate(valueType, columnValue);
+    }
+
+    public List<ColumnConstraint<?>> getConstraints()
+    {
+        return constraints;
+    }
+
+    public boolean isEmpty()
+    {
+        return isEmpty;
+    }
+
+    public int getSize()
+    {
+        return constraintsSize;
+    }
+
+    @Override
+    public void validate(ColumnMetadata columnMetadata) throws 
InvalidConstraintDefinitionException
+    {
+        if (UNSUPPORTED_TYPES.contains(columnMetadata.type.getClass()))
+            throw new InvalidConstraintDefinitionException("Constraint cannot 
be defined on column '"
+                                                           + 
columnMetadata.name + "' with type: "
+                                                           + 
columnMetadata.type.asCQL3Type());
+
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.validate(columnMetadata);
+    }
+
+    @Override
+    public ConstraintType getConstraintType()
+    {
+        return ConstraintType.COMPOSED;
+    }
+
+    public static class Noop extends ColumnConstraints
+    {
+        public static final Noop INSTANCE = new Noop();
+
+        public Noop()
+        {
+            super(Collections.emptyList());
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            return true;
+        }
+
+        @Override
+        public int getSize()
+        {
+            return 0;
+        }
+    }
+
+    public final static class Raw
+    {
+        private final List<ColumnConstraint<?>> constraints;
+
+        public Raw(List<ColumnConstraint<?>> constraints)
+        {
+            this.constraints = constraints;
+        }
+
+        public Raw()
+        {
+            this.constraints = Collections.emptyList();
+        }
+
+        public ColumnConstraints prepare()
+        {
+            return new ColumnConstraints(constraints);
+        }
+    }
+
+    public static class Serializer implements 
IVersionedSerializer<ColumnConstraints>
+    {
+        @Override
+        public void serialize(ColumnConstraints columnConstraint, 
DataOutputPlus out, int version) throws IOException
+        {
+            out.writeInt(columnConstraint.getSize());
+            for (ColumnConstraint constraint : 
columnConstraint.getConstraints())
+            {
+                // We serialize the serializer position in the enum to save 
space
+                out.writeInt(constraint.getConstraintType().ordinal());
+                constraint.serializer().serialize(constraint, out, version);
+            }
+        }
+
+        @Override
+        public ColumnConstraints deserialize(DataInputPlus in, int version) 
throws IOException

Review Comment:
   On `serialize` we have:
   1. int: columnConstraint.getSize()
   2. per constraint:
   2a. int: ordinal of constraint type
   2b. Type T: in this case ColumnConstraint serializer
   
   On `deserialize` we have:
   1. int: num constraints
   2. for each constraint:
   2a. _**short**_ serializer position
   2b. Type T: in this case ColumnConstraint
   
   So the int ser in 2a. doesn't look to match the short deser in 2a. That a 
bug, or is there nuance to the "serialize serializer position" bit I'm missing?
   
   fwiw, the entire ser/deser here has me squinting and thinking hard in 
general.



##########
src/resources/org/apache/cassandra/cql3/reserved_keywords.txt:
##########
@@ -54,4 +54,5 @@ USE
 USING
 VIEW
 WHERE
-WITH
\ No newline at end of file
+WITH
+CONSTRAINT

Review Comment:
   Do we want just `CONSTRAINT` or also include `CHECK`?



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+// group of constraints for the column
+public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
+{
+    public static final Serializer serializer = new Serializer();
+
+    private static final Set<Class<? extends AbstractType>> UNSUPPORTED_TYPES 
= ImmutableSet.of(MapType.class,
+                                                                               
                 TupleType.class,
+                                                                               
                 UserType.class,
+                                                                               
                 CompositeType.class,
+                                                                               
                 DynamicCompositeType.class);
+
+
+
+    private final List<ColumnConstraint<?>> constraints;
+    private final int constraintsSize;
+    private final boolean isEmpty;
+
+    public ColumnConstraints(List<ColumnConstraint<?>> constraints)
+    {
+        this.constraints = new ArrayList<>(constraints);
+        // These are catched values to avoid making the calculations every time
+        this.constraintsSize = constraints.size();
+        this.isEmpty = constraints.isEmpty();
+    }
+
+    @Override
+    public IVersionedSerializer<ColumnConstraints> serializer()
+    {
+        return serializer;
+    }
+
+    @Override
+    public void appendCqlTo(CqlBuilder builder)
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.appendCqlTo(builder);
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue) throws ConstraintViolationException
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.evaluate(valueType, columnValue);
+    }
+
+    public List<ColumnConstraint<?>> getConstraints()
+    {
+        return constraints;
+    }
+
+    public boolean isEmpty()
+    {
+        return isEmpty;
+    }
+
+    public int getSize()
+    {
+        return constraintsSize;
+    }
+
+    @Override
+    public void validate(ColumnMetadata columnMetadata) throws 
InvalidConstraintDefinitionException
+    {
+        if (UNSUPPORTED_TYPES.contains(columnMetadata.type.getClass()))
+            throw new InvalidConstraintDefinitionException("Constraint cannot 
be defined on column '"
+                                                           + 
columnMetadata.name + "' with type: "
+                                                           + 
columnMetadata.type.asCQL3Type());
+
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.validate(columnMetadata);
+    }
+
+    @Override
+    public ConstraintType getConstraintType()
+    {
+        return ConstraintType.COMPOSED;
+    }
+
+    public static class Noop extends ColumnConstraints
+    {
+        public static final Noop INSTANCE = new Noop();
+
+        public Noop()
+        {
+            super(Collections.emptyList());
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            return true;
+        }
+
+        @Override
+        public int getSize()
+        {
+            return 0;
+        }
+    }
+
+    public final static class Raw
+    {
+        private final List<ColumnConstraint<?>> constraints;
+
+        public Raw(List<ColumnConstraint<?>> constraints)
+        {
+            this.constraints = constraints;
+        }
+
+        public Raw()
+        {
+            this.constraints = Collections.emptyList();
+        }
+
+        public ColumnConstraints prepare()
+        {
+            return new ColumnConstraints(constraints);
+        }
+    }
+
+    public static class Serializer implements 
IVersionedSerializer<ColumnConstraints>
+    {
+        @Override
+        public void serialize(ColumnConstraints columnConstraint, 
DataOutputPlus out, int version) throws IOException
+        {
+            out.writeInt(columnConstraint.getSize());
+            for (ColumnConstraint constraint : 
columnConstraint.getConstraints())
+            {
+                // We serialize the serializer position in the enum to save 
space
+                out.writeInt(constraint.getConstraintType().ordinal());
+                constraint.serializer().serialize(constraint, out, version);
+            }
+        }
+
+        @Override
+        public ColumnConstraints deserialize(DataInputPlus in, int version) 
throws IOException
+        {
+            List<ColumnConstraint<?>> columnConstraints = new ArrayList<>();
+            int numberOfConstraints = in.readInt();
+            for (int i = 0; i < numberOfConstraints; i++)
+            {
+                int serializerPosition = in.readShort();
+                ColumnConstraint<?> constraint = (ColumnConstraint<?>) 
ConstraintType
+                                                                       
.getSerializer(serializerPosition)
+                                                                       
.deserialize(in, version);
+                columnConstraints.add(constraint);
+            }
+            return new ColumnConstraints(columnConstraints);
+        }
+
+        @Override
+        public long serializedSize(ColumnConstraints columnConstraint, int 
version)
+        {
+            long constraintsSize = 0;
+            for (ColumnConstraint constraint : 
columnConstraint.getConstraints())

Review Comment:
   did we forget to add the `out.writeInt(columnConstraint.getSize());` from 
`serialize` here?



##########
src/java/org/apache/cassandra/cql3/constraints/ColumnConstraints.java:
##########
@@ -0,0 +1,200 @@
+/*
+ * 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 org.apache.cassandra.cql3.CqlBuilder;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.marshal.CompositeType;
+import org.apache.cassandra.db.marshal.DynamicCompositeType;
+import org.apache.cassandra.db.marshal.MapType;
+import org.apache.cassandra.db.marshal.TupleType;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.IVersionedSerializer;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.ColumnMetadata;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+
+// group of constraints for the column
+public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
+{
+    public static final Serializer serializer = new Serializer();
+
+    private static final Set<Class<? extends AbstractType>> UNSUPPORTED_TYPES 
= ImmutableSet.of(MapType.class,
+                                                                               
                 TupleType.class,
+                                                                               
                 UserType.class,
+                                                                               
                 CompositeType.class,
+                                                                               
                 DynamicCompositeType.class);
+
+
+
+    private final List<ColumnConstraint<?>> constraints;
+    private final int constraintsSize;
+    private final boolean isEmpty;
+
+    public ColumnConstraints(List<ColumnConstraint<?>> constraints)
+    {
+        this.constraints = new ArrayList<>(constraints);
+        // These are catched values to avoid making the calculations every time
+        this.constraintsSize = constraints.size();
+        this.isEmpty = constraints.isEmpty();
+    }
+
+    @Override
+    public IVersionedSerializer<ColumnConstraints> serializer()
+    {
+        return serializer;
+    }
+
+    @Override
+    public void appendCqlTo(CqlBuilder builder)
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.appendCqlTo(builder);
+    }
+
+    @Override
+    public void evaluate(Class<? extends AbstractType> valueType, Object 
columnValue) throws ConstraintViolationException
+    {
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.evaluate(valueType, columnValue);
+    }
+
+    public List<ColumnConstraint<?>> getConstraints()
+    {
+        return constraints;
+    }
+
+    public boolean isEmpty()
+    {
+        return isEmpty;
+    }
+
+    public int getSize()
+    {
+        return constraintsSize;
+    }
+
+    @Override
+    public void validate(ColumnMetadata columnMetadata) throws 
InvalidConstraintDefinitionException
+    {
+        if (UNSUPPORTED_TYPES.contains(columnMetadata.type.getClass()))
+            throw new InvalidConstraintDefinitionException("Constraint cannot 
be defined on column '"
+                                                           + 
columnMetadata.name + "' with type: "
+                                                           + 
columnMetadata.type.asCQL3Type());
+
+        for (ColumnConstraint<?> constraint : constraints)
+            constraint.validate(columnMetadata);
+    }
+
+    @Override
+    public ConstraintType getConstraintType()
+    {
+        return ConstraintType.COMPOSED;
+    }
+
+    public static class Noop extends ColumnConstraints
+    {
+        public static final Noop INSTANCE = new Noop();
+
+        public Noop()
+        {
+            super(Collections.emptyList());
+        }
+
+        @Override
+        public boolean isEmpty()
+        {
+            return true;
+        }
+
+        @Override
+        public int getSize()
+        {
+            return 0;
+        }
+    }
+
+    public final static class Raw
+    {
+        private final List<ColumnConstraint<?>> constraints;
+
+        public Raw(List<ColumnConstraint<?>> constraints)
+        {
+            this.constraints = constraints;
+        }
+
+        public Raw()
+        {
+            this.constraints = Collections.emptyList();
+        }
+
+        public ColumnConstraints prepare()
+        {
+            return new ColumnConstraints(constraints);
+        }
+    }
+
+    public static class Serializer implements 
IVersionedSerializer<ColumnConstraints>
+    {
+        @Override
+        public void serialize(ColumnConstraints columnConstraint, 
DataOutputPlus out, int version) throws IOException
+        {
+            out.writeInt(columnConstraint.getSize());
+            for (ColumnConstraint constraint : 
columnConstraint.getConstraints())
+            {
+                // We serialize the serializer position in the enum to save 
space

Review Comment:
   This comment confused me a bit. Do we mean to say we serialize the ordinal 
of the enum rather than text value to save space? Because I see:
   1. Serialized ordinal
   2. Serialized version
   
   No serialization of serializer's position. Could be misreading though.



##########
src/java/org/apache/cassandra/schema/TableMetadata.java:
##########
@@ -196,6 +200,9 @@ public enum Kind
     public final DataResource resource;
     public TableMetadataRef ref;
 
+    public final List<ColumnConstraint> partitionKeyConstraints;
+    public final List<ColumnMetadata> columnsWithConstraints;

Review Comment:
   Recommend you hoist up the comment below around "We cache the columns with 
constraints to avoid iterations over columns" to explain the purpose of this 
here and why the asymmetry in naming.



##########
src/java/org/apache/cassandra/schema/ColumnMetadata.java:
##########
@@ -320,7 +387,8 @@ private boolean equalsWithoutType(ColumnMetadata other)
             && position == other.position
             && ksName.equals(other.ksName)
             && cfName.equals(other.cfName)
-            && Objects.equals(mask, other.mask);
+            && Objects.equals(mask, other.mask)
+            && Objects.equals(columnConstraints, other.columnConstraints);

Review Comment:
   Just to confirm, do we want to explicitly only check memory address equality 
between columnConstraints instances rather than contents therein? Since we're 
logically allowing non-memory identical comparisons with other fields including 
relying on the `.equals` implementation within `ColumnMask`. Just curious what 
the thinking was here.



##########
src/java/org/apache/cassandra/schema/ColumnMetadata.java:
##########
@@ -115,6 +118,12 @@ public boolean isPrimaryKeyKind()
     @Nullable
     private final ColumnMask mask;
 
+    @Nonnull
+    private ColumnConstraints columnConstraints;
+
+    @Nonnull

Review Comment:
   Don't need @Nonnull on a primitive do you? It's nonnull by definition I 
thought.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to