mirror of https://github.com/apache/cassandra
CEP-42 - Add Constraints Framework
patch by Bernardo Botella; reviewed by Yifan Cai, Stefan Miklosovic, Josh McKenzie, Maxwell Guo, Dmitry Konstantinov, Sam Tunnicliffe for CASSANDRA-19947
This commit is contained in:
parent
f74b1786e3
commit
5cbf993d96
|
|
@ -1,4 +1,5 @@
|
|||
5.1
|
||||
* CEP-42 - Add Constraints Framework (CASSANDRA-19947)
|
||||
* Add table metric PurgeableTombstoneScannedHistogram and a tracing event for scanned purgeable tombstones (CASSANDRA-20132)
|
||||
* Make sure we can parse the expanded CQL before writing it to the log or sending it to replicas (CASSANDRA-20218)
|
||||
* Add format_bytes and format_time functions (CASSANDRA-19546)
|
||||
|
|
|
|||
3
NEWS.txt
3
NEWS.txt
|
|
@ -76,6 +76,9 @@ New features
|
|||
metadata. In the first instance, this encompasses cluster membership, token ownership and schema metadata. See
|
||||
https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-21%3A+Transactional+Cluster+Metadata for more detail on
|
||||
the motivation and design, and see "Upgrading" below for specific instructions on migrating clusters to this system.
|
||||
- CEP-42 Constraints Framework provides flexibility to Cassandra users and operators by providing a set of
|
||||
usable constraints at table level, that will ease validations at application level and protect
|
||||
the Database from misconfigured clients.
|
||||
More updates and documentation to follow.
|
||||
- New Guardrails added:
|
||||
- Whether bulk loading of SSTables is allowed.
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
*** xref:cassandra:developing/cql/appendices.adoc[Appendices]
|
||||
*** xref:cassandra:developing/cql/changes.adoc[Changes]
|
||||
*** xref:cassandra:developing/cql/SASI.adoc[SASI]
|
||||
|
|
|
|||
|
|
@ -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 syntax 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 length of text or binary type.
|
||||
|
||||
For example, we can create a constraint that checks that name can't be longer than 256 characters:
|
||||
|
||||
----
|
||||
CREATE TABLE keyspace.table (
|
||||
name text CHECK LENGTH(name) < 256
|
||||
...,
|
||||
);
|
||||
----
|
||||
|
||||
Altering that constraint can be done with:
|
||||
|
||||
----
|
||||
ALTER TABLE keyspace.table ALTER name LENGTH(name) < 512;
|
||||
----
|
||||
|
||||
Finally, the constraint can be removed:
|
||||
|
||||
----
|
||||
ALTER TABLE keyspace.table ALTER name DROP CHECK;
|
||||
----
|
||||
|
|
@ -21,5 +21,6 @@ For that reason, when used in this document, these terms (tables, rows and colum
|
|||
* xref:developing/cql/json.adoc[JSON]
|
||||
* xref:developing/cql/security.adoc[CQL security]
|
||||
* xref:developing/cql/triggers.adoc[Triggers]
|
||||
* xref:developing/cql/constraints.adoc[Constraints]
|
||||
* xref:developing/cql/appendices.adoc[Appendices]
|
||||
* xref:developing/cql/changes.adoc[Changes]
|
||||
|
|
@ -7,6 +7,7 @@ This section covers the new features in Apache Cassandra 5.1.
|
|||
|
||||
* https://cwiki.apache.org/confluence/x/FQRACw[ACID Transactions (Accord)]
|
||||
* https://cwiki.apache.org/confluence/x/YyD1D[Transactional Cluster Metadata]
|
||||
* https://cwiki.apache.org/confluence/x/8IpyEg[Constraints]
|
||||
|
||||
|
||||
== New Features in Apache Cassandra 5.0
|
||||
|
|
|
|||
|
|
@ -322,7 +322,14 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
|
|||
|
||||
<userType> ::= utname=<cfOrKsName> ;
|
||||
|
||||
<storageType> ::= ( <simpleStorageType> | <collectionType> | <frozenCollectionType> | <vectorType> | <userType> ) ( <column_mask> )? ;
|
||||
<storageType> ::= ( <simpleStorageType> | <collectionType> | <frozenCollectionType> | <vectorType> | <userType> ) ( <constraintsExpr> )? ( <column_mask> )? ;
|
||||
|
||||
<constraintsExpr> ::= "CHECK" <constraint> ( "AND" <constraint> )*
|
||||
;
|
||||
|
||||
<constraint> ::= <cident> <cmp> <term>
|
||||
| <functionArguments> <cmp> <term>
|
||||
;
|
||||
|
||||
<column_mask> ::= "MASKED" "WITH" ( "DEFAULT" | <functionName> <selectionFunctionArguments> );
|
||||
|
||||
|
|
@ -1463,7 +1470,7 @@ syntax_rules += r'''
|
|||
| "WITH" <cfamProperty> ( "AND" <cfamProperty> )*
|
||||
| "RENAME" ("IF" "EXISTS")? existcol=<cident> "TO" newcol=<cident>
|
||||
( "AND" existcol=<cident> "TO" newcol=<cident> )*
|
||||
| "ALTER" ("IF" "EXISTS")? existcol=<cident> ( <column_mask> | "DROP" "MASKED" )
|
||||
| "ALTER" ("IF" "EXISTS")? existcol=<cident> ( <constraintsExpr> | <column_mask> | "DROP" ( "CHECK" | "MASKED" ) )
|
||||
;
|
||||
|
||||
<alterUserTypeStatement> ::= "ALTER" "TYPE" ("IF" "EXISTS")? ut=<userTypeName>
|
||||
|
|
|
|||
|
|
@ -636,7 +636,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
|
|||
self.trycompletions(prefix + ' new_table (col_a ine',
|
||||
immediate='t ')
|
||||
self.trycompletions(prefix + ' new_table (col_a int ',
|
||||
choices=[',', 'MASKED', 'PRIMARY'])
|
||||
choices=[',', 'CHECK', 'MASKED', 'PRIMARY'])
|
||||
self.trycompletions(prefix + ' new_table (col_a int M',
|
||||
immediate='ASKED WITH ')
|
||||
self.trycompletions(prefix + ' new_table (col_a int MASKED WITH ',
|
||||
|
|
@ -1106,7 +1106,7 @@ class TestCqlshCompletion(CqlshCompletionCase):
|
|||
self.trycompletions('ALTER TABLE IF EXISTS new_table ADD ', choices=['<new_column_name>', 'IF'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ADD IF NOT EXISTS ', choices=['<new_column_name>'])
|
||||
self.trycompletions('ALTER TABLE new_table ADD IF NOT EXISTS ', choices=['<new_column_name>'])
|
||||
self.trycompletions('ALTER TABLE new_table ADD col int ', choices=[';', 'MASKED', 'static'])
|
||||
self.trycompletions('ALTER TABLE new_table ADD col int ', choices=[';', 'MASKED', 'CHECK', 'static'])
|
||||
self.trycompletions('ALTER TABLE new_table ADD col int M', immediate='ASKED WITH ')
|
||||
self.trycompletions('ALTER TABLE new_table ADD col int MASKED WITH ',
|
||||
choices=['DEFAULT', self.cqlsh.keyspace + '.', 'system.'],
|
||||
|
|
@ -1116,12 +1116,15 @@ class TestCqlshCompletion(CqlshCompletionCase):
|
|||
self.trycompletions('ALTER TABLE IF EXISTS new_table DROP ', choices=['IF', '<quotedName>', '<identifier>'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER ', choices=['IF', '<quotedName>', '<identifier>'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF E', immediate='XISTS ')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col ', choices=['MASKED', 'DROP'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col ', choices=['CHECK', 'MASKED', 'DROP'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col C', immediate='HECK ')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col M', immediate='ASKED WITH ')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col MASKED WITH ',
|
||||
choices=['DEFAULT', self.cqlsh.keyspace + '.', 'system.'],
|
||||
other_choices_ok=True)
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col D', immediate='ROP MASKED ;')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col D', immediate='ROP ')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col DROP ', choices=['CHECK', 'MASKED'])
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col DROP C', immediate='HECK ;')
|
||||
self.trycompletions('ALTER TABLE IF EXISTS new_table ALTER IF EXISTS col DROP M', immediate='ASKED ;')
|
||||
|
||||
def test_complete_in_alter_type(self):
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import Parser,Lexer;
|
|||
|
||||
import org.apache.cassandra.auth.*;
|
||||
import org.apache.cassandra.cql3.conditions.*;
|
||||
import org.apache.cassandra.cql3.constraints.*;
|
||||
import org.apache.cassandra.cql3.functions.*;
|
||||
import org.apache.cassandra.cql3.functions.masking.*;
|
||||
import org.apache.cassandra.cql3.restrictions.CustomIndexExpression;
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ K_GROUP: G R O U P;
|
|||
K_CLUSTER: C L U S T E R;
|
||||
K_INTERNALS: I N T E R N A L S;
|
||||
K_ONLY: O N L Y;
|
||||
K_CHECK: C H E C K;
|
||||
|
||||
K_GRANT: G R A N T;
|
||||
K_ALL: A L L;
|
||||
|
|
|
|||
|
|
@ -781,11 +781,24 @@ tableDefinition[CreateTableStatement.Raw stmt]
|
|||
|
||||
tableColumns[CreateTableStatement.Raw stmt]
|
||||
@init { boolean isStatic = false; }
|
||||
: k=ident v=comparatorType (K_STATIC { isStatic = true; })? (mask=columnMask)? { $stmt.addColumn(k, v, isStatic, mask); }
|
||||
: k=ident v=comparatorType (K_STATIC { isStatic = true; })? (mask=columnMask)? (constraints=columnConstraints)? { $stmt.addColumn(k, v, isStatic, mask, constraints); }
|
||||
(K_PRIMARY K_KEY { $stmt.setPartitionKeyColumn(k); })?
|
||||
| K_PRIMARY K_KEY '(' tablePartitionKey[stmt] (',' c=ident { $stmt.markClusteringColumn(c); } )* ')'
|
||||
;
|
||||
|
||||
columnConstraints returns [ColumnConstraints.Raw constraints]
|
||||
@init {
|
||||
boolean isStatic = false;
|
||||
List constraintsList = new ArrayList();
|
||||
}
|
||||
: K_CHECK cc=columnConstraint { constraintsList.add(cc); } (K_AND cc=columnConstraint { constraintsList.add(cc); })* { $constraints = new ColumnConstraints.Raw(constraintsList); }
|
||||
;
|
||||
|
||||
columnConstraint returns [ColumnConstraint columnConstraint]
|
||||
: funcName=ident '(' k=ident ')' op=relationType t=value { $columnConstraint = new FunctionColumnConstraint.Raw(funcName, k, op, t.getText()).prepare(); }
|
||||
| k=ident op=relationType t=value { $columnConstraint = new ScalarColumnConstraint.Raw(k, op, t.getText()).prepare(); }
|
||||
;
|
||||
|
||||
columnMask returns [ColumnMask.Raw mask]
|
||||
@init { List<Term.Raw> arguments = new ArrayList<>(); }
|
||||
: K_MASKED K_WITH name=functionName columnMaskArguments[arguments] { $mask = new ColumnMask.Raw(name, arguments); }
|
||||
|
|
@ -965,7 +978,9 @@ alterTableStatement returns [AlterTableStatement.Raw stmt]
|
|||
|
||||
| K_ALTER ( K_IF K_EXISTS { $stmt.ifColumnExists(true); } )? id=cident
|
||||
( mask=columnMask { $stmt.mask(id, mask); }
|
||||
| K_DROP K_MASKED { $stmt.mask(id, null); } )
|
||||
| K_DROP K_MASKED { $stmt.mask(id, null); }
|
||||
| K_DROP K_CHECK { $stmt.dropConstraints(id); }
|
||||
| (constraints=columnConstraints) { $stmt.alterConstraints(id, constraints); })
|
||||
|
||||
| K_ADD ( K_IF K_NOT K_EXISTS { $stmt.ifColumnNotExists(true); } )?
|
||||
( id=ident v=comparatorType b=isStaticColumn (m=columnMask)? { $stmt.add(id, v, b, m); }
|
||||
|
|
@ -2067,5 +2082,6 @@ basic_unreserved_keyword returns [String str]
|
|||
| K_VECTOR
|
||||
| K_ANN
|
||||
| K_BETWEEN
|
||||
| K_CHECK
|
||||
) { $str = $k.text; }
|
||||
;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.cql3;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
|
|
@ -64,4 +65,16 @@ public abstract class Validation
|
|||
throw new InvalidRequestException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkConstraints(TableMetadata metadata, ByteBuffer key)
|
||||
{
|
||||
try
|
||||
{
|
||||
metadata.partitionKeyType.checkConstraints(key, metadata.partitionKeyConstraints);
|
||||
}
|
||||
catch (ConstraintViolationException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.CqlBuilder;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
|
||||
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, with T as a constraint serializer.
|
||||
*/
|
||||
public interface ColumnConstraint<T>
|
||||
{
|
||||
|
||||
// Enum containing all the possible constraint serializers to help with serialization/deserialization
|
||||
// of constraints.
|
||||
enum ConstraintType
|
||||
{
|
||||
// The order of that enum matters!!
|
||||
// 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),
|
||||
FUNCTION(FunctionColumnConstraint.serializer),
|
||||
SCALAR(ScalarColumnConstraint.serializer);
|
||||
|
||||
private final MetadataSerializer<?> serializer;
|
||||
|
||||
ConstraintType(MetadataSerializer<?> serializer)
|
||||
{
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
public static MetadataSerializer<?> getSerializer(int i)
|
||||
{
|
||||
return ConstraintType.values()[i].serializer;
|
||||
}
|
||||
}
|
||||
|
||||
MetadataSerializer<T> serializer();
|
||||
|
||||
void appendCqlTo(CqlBuilder builder);
|
||||
|
||||
/**
|
||||
* Method that evaluates the condition. It can either succeed or throw a {@link ConstraintViolationException}.
|
||||
*
|
||||
* @param valueType value type of the column value under test
|
||||
* @param columnValue Column value to be evaluated at write time
|
||||
*/
|
||||
void evaluate(AbstractType<?> valueType, ByteBuffer columnValue) throws ConstraintViolationException;
|
||||
|
||||
/**
|
||||
* Method to validate the condition. This method is called when creating constraint via CQL.
|
||||
* A {@link InvalidConstraintDefinitionException} is thrown for invalid consrtaint definition.
|
||||
*
|
||||
* @param columnMetadata Metadata of the column in which the constraint is defined.
|
||||
*/
|
||||
void validate(ColumnMetadata columnMetadata) throws InvalidConstraintDefinitionException;
|
||||
|
||||
/**
|
||||
* Method to get the Constraint serializer
|
||||
*
|
||||
* @return the Constraint type serializer
|
||||
*/
|
||||
ConstraintType getConstraintType();
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.cassandra.cql3.CqlBuilder;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
|
||||
import org.apache.cassandra.tcm.serialization.Version;
|
||||
|
||||
|
||||
// group of constraints for the column
|
||||
public class ColumnConstraints implements ColumnConstraint<ColumnConstraints>
|
||||
{
|
||||
public static final Serializer serializer = new Serializer();
|
||||
public static final ColumnConstraints NO_OP = new Noop();
|
||||
|
||||
private final List<ColumnConstraint<?>> constraints;
|
||||
|
||||
public ColumnConstraints(List<ColumnConstraint<?>> constraints)
|
||||
{
|
||||
this.constraints = constraints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataSerializer<ColumnConstraints> serializer()
|
||||
{
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendCqlTo(CqlBuilder builder)
|
||||
{
|
||||
for (ColumnConstraint<?> constraint : constraints)
|
||||
constraint.appendCqlTo(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(AbstractType<?> valueType, ByteBuffer columnValue) throws ConstraintViolationException
|
||||
{
|
||||
for (ColumnConstraint<?> constraint : constraints)
|
||||
constraint.evaluate(valueType, columnValue);
|
||||
}
|
||||
|
||||
public List<ColumnConstraint<?>> getConstraints()
|
||||
{
|
||||
return constraints;
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return constraints.isEmpty();
|
||||
}
|
||||
|
||||
public int getSize()
|
||||
{
|
||||
return constraints.size();
|
||||
}
|
||||
|
||||
// Checks if there is at least one constraint that will perform checks
|
||||
public boolean hasRelevantConstraints()
|
||||
{
|
||||
for (ColumnConstraint c : constraints)
|
||||
{
|
||||
if (c != ColumnConstraints.NO_OP)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ColumnMetadata columnMetadata) throws InvalidConstraintDefinitionException
|
||||
{
|
||||
if (!columnMetadata.type.isConstrainable())
|
||||
throw new InvalidConstraintDefinitionException("Constraint cannot be defined on the column "
|
||||
+ columnMetadata.name + " of type " + columnMetadata.type.asCQL3Type()
|
||||
+ " for the table " + columnMetadata.ksName + "." + columnMetadata.cfName);
|
||||
|
||||
for (ColumnConstraint<?> constraint : constraints)
|
||||
constraint.validate(columnMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConstraintType getConstraintType()
|
||||
{
|
||||
return ConstraintType.COMPOSED;
|
||||
}
|
||||
|
||||
private static class Noop extends ColumnConstraints
|
||||
{
|
||||
private Noop()
|
||||
{
|
||||
super(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ColumnMetadata columnMetadata)
|
||||
{
|
||||
// Do nothing. It is always valid
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return NO_OP;
|
||||
return new ColumnConstraints(constraints);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Serializer implements MetadataSerializer<ColumnConstraints>
|
||||
{
|
||||
|
||||
@Override
|
||||
public void serialize(ColumnConstraints columnConstraint, DataOutputPlus out, Version version) throws IOException
|
||||
{
|
||||
out.writeInt(columnConstraint.getSize());
|
||||
for (ColumnConstraint constraint : columnConstraint.getConstraints())
|
||||
{
|
||||
// We serialize the serializer ordinal in the enum to save space
|
||||
out.writeShort(constraint.getConstraintType().ordinal());
|
||||
constraint.serializer().serialize(constraint, out, version);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColumnConstraints deserialize(DataInputPlus in, Version 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, Version version)
|
||||
{
|
||||
long constraintsSize = TypeSizes.INT_SIZE;
|
||||
for (ColumnConstraint constraint : columnConstraint.getConstraints())
|
||||
{
|
||||
constraintsSize += TypeSizes.SHORT_SIZE;
|
||||
constraintsSize += constraint.serializer().serializedSize(constraint, version);
|
||||
}
|
||||
return constraintsSize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof ColumnConstraints))
|
||||
return false;
|
||||
|
||||
ColumnConstraints other = (ColumnConstraints) o;
|
||||
return Objects.equals(constraints, other.constraints);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
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 performs the actual condition test, executed during the write path.
|
||||
* It the test is not successful, it throws a {@link ConstraintViolationException}.
|
||||
*/
|
||||
void evaluate(AbstractType<?> valueType, Operator relationType, String term, ByteBuffer columnValue) throws ConstraintViolationException;
|
||||
|
||||
/**
|
||||
* Method that validates that a condition is valid. This method is called when the CQL constraint is created to determine
|
||||
* if the CQL statement is valid or needs to be rejected as invalid throwing a {@link InvalidConstraintDefinitionException}
|
||||
*/
|
||||
void validate(ColumnMetadata columnMetadata) throws InvalidConstraintDefinitionException;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.exceptions.InvalidRequestException;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the CQL operation did not comply with the defined Constraints
|
||||
*/
|
||||
public class ConstraintViolationException extends InvalidRequestException
|
||||
{
|
||||
public ConstraintViolationException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
import java.util.function.Function;
|
||||
|
||||
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.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
|
||||
import org.apache.cassandra.tcm.serialization.Version;
|
||||
import org.apache.cassandra.utils.LocalizeString;
|
||||
|
||||
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 enum Functions
|
||||
{
|
||||
LENGTH(LengthConstraint::new);
|
||||
|
||||
private final Function<ColumnIdentifier, ConstraintFunction> functionCreator;
|
||||
|
||||
Functions(Function<ColumnIdentifier, ConstraintFunction> functionCreator)
|
||||
{
|
||||
this.functionCreator = functionCreator;
|
||||
}
|
||||
}
|
||||
|
||||
private static ConstraintFunction createConstraintFunction(String functionName, ColumnIdentifier columnName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Functions.valueOf(LocalizeString.toUpperCaseLocalized(functionName)).functionCreator.apply(columnName);
|
||||
}
|
||||
catch (IllegalArgumentException ex)
|
||||
{
|
||||
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 MetadataSerializer<FunctionColumnConstraint> serializer()
|
||||
{
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(AbstractType<?> valueType, ByteBuffer 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 MetadataSerializer<FunctionColumnConstraint>
|
||||
{
|
||||
@Override
|
||||
public void serialize(FunctionColumnConstraint columnConstraint, DataOutputPlus out, Version version) throws IOException
|
||||
{
|
||||
out.writeUTF(columnConstraint.function.getName());
|
||||
out.writeUTF(columnConstraint.columnName.toCQLString());
|
||||
columnConstraint.relationType.writeTo(out);
|
||||
out.writeUTF(columnConstraint.term);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FunctionColumnConstraint deserialize(DataInputPlus in, Version version) throws IOException
|
||||
{
|
||||
String functionName = in.readUTF();
|
||||
ConstraintFunction function;
|
||||
String columnNameString = in.readUTF();
|
||||
ColumnIdentifier columnName = new ColumnIdentifier(columnNameString, true);
|
||||
try
|
||||
{
|
||||
function = createConstraintFunction(functionName, columnName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new IOException(e);
|
||||
}
|
||||
Operator relationType = Operator.readFrom(in);
|
||||
final String term = in.readUTF();
|
||||
return new FunctionColumnConstraint(function, columnName, relationType, term);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(FunctionColumnConstraint columnConstraint, Version version)
|
||||
{
|
||||
return TypeSizes.sizeof(columnConstraint.function.getClass().getName())
|
||||
+ TypeSizes.sizeof(columnConstraint.columnName.toCQLString())
|
||||
+ TypeSizes.sizeof(columnConstraint.term)
|
||||
+ Operator.serializedSize();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof FunctionColumnConstraint))
|
||||
return false;
|
||||
|
||||
FunctionColumnConstraint other = (FunctionColumnConstraint) o;
|
||||
|
||||
return function.equals(other.function)
|
||||
&& columnName.equals(other.columnName)
|
||||
&& relationType == other.relationType
|
||||
&& term.equals(other.term);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.exceptions.InvalidRequestException;
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the CQL constraint is not valid
|
||||
*/
|
||||
public class InvalidConstraintDefinitionException extends InvalidRequestException
|
||||
{
|
||||
public InvalidConstraintDefinitionException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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.nio.ByteBuffer;
|
||||
|
||||
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.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
public class LengthConstraint implements ConstraintFunction
|
||||
{
|
||||
private static final AbstractType<?>[] SUPPORTED_TYPES = new AbstractType[] { BytesType.instance, UTF8Type.instance, AsciiType.instance };
|
||||
|
||||
public static final String FUNCTION_NAME = "LENGTH";
|
||||
|
||||
private final ColumnIdentifier columnName;
|
||||
|
||||
public LengthConstraint(ColumnIdentifier columnName)
|
||||
{
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return FUNCTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(AbstractType<?> valueType, Operator relationType, String term, ByteBuffer columnValue)
|
||||
{
|
||||
int valueLength = getValueLength(columnValue, valueType);
|
||||
int sizeConstraint = Integer.parseInt(term);
|
||||
|
||||
ByteBuffer leftOperand = ByteBufferUtil.bytes(valueLength);
|
||||
ByteBuffer rightOperand = ByteBufferUtil.bytes(sizeConstraint);
|
||||
|
||||
if (!relationType.isSatisfiedBy(Int32Type.instance, leftOperand, rightOperand))
|
||||
throw new ConstraintViolationException(columnName + " does not satisfy length constraint. "
|
||||
+ valueLength + " should be " + relationType + ' ' + term);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(ColumnMetadata columnMetadata)
|
||||
{
|
||||
boolean supported = false;
|
||||
AbstractType<?> unwrapped = columnMetadata.type.unwrap();
|
||||
for (AbstractType<?> supportedType : SUPPORTED_TYPES)
|
||||
{
|
||||
if (supportedType == unwrapped)
|
||||
{
|
||||
supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!supported)
|
||||
throw invalidConstraintDefinitionException(columnMetadata.type);
|
||||
}
|
||||
|
||||
private int getValueLength(ByteBuffer value, AbstractType<?> valueType)
|
||||
{
|
||||
if (valueType.getClass() == BytesType.class)
|
||||
{
|
||||
return value.remaining();
|
||||
}
|
||||
|
||||
if (valueType.getClass() == AsciiType.class || valueType.getClass() == UTF8Type.class)
|
||||
return ((String) valueType.compose(value)).length();
|
||||
|
||||
throw invalidConstraintDefinitionException(valueType);
|
||||
}
|
||||
|
||||
private InvalidConstraintDefinitionException invalidConstraintDefinitionException(AbstractType<?> valueType)
|
||||
{
|
||||
throw new InvalidConstraintDefinitionException("Column type " + valueType.getClass() + " is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof LengthConstraint))
|
||||
return false;
|
||||
|
||||
LengthConstraint other = (LengthConstraint) o;
|
||||
|
||||
return columnName.equals(other.columnName);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
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.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
|
||||
import org.apache.cassandra.tcm.serialization.Version;
|
||||
|
||||
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(AbstractType<?> valueType, ByteBuffer columnValue)
|
||||
{
|
||||
ByteBuffer value;
|
||||
try
|
||||
{
|
||||
value = valueType.fromString(term);
|
||||
}
|
||||
catch (NumberFormatException exception)
|
||||
{
|
||||
throw new ConstraintViolationException(param + " and " + term + " need to be numbers.");
|
||||
}
|
||||
|
||||
if (!relationType.isSatisfiedBy(valueType, columnValue, value))
|
||||
throw new ConstraintViolationException("Column value does not satisfy value constraint. "
|
||||
+ " It should be " + relationType + " " + term);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataSerializer<ScalarColumnConstraint> serializer()
|
||||
{
|
||||
return serializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendCqlTo(CqlBuilder builder)
|
||||
{
|
||||
builder.append(toString());
|
||||
}
|
||||
|
||||
private static class Serializer implements MetadataSerializer<ScalarColumnConstraint>
|
||||
{
|
||||
@Override
|
||||
public void serialize(ScalarColumnConstraint columnConstraint, DataOutputPlus out, Version version) throws IOException
|
||||
{
|
||||
out.writeUTF(columnConstraint.param.toCQLString());
|
||||
columnConstraint.relationType.writeTo(out);
|
||||
out.writeUTF(columnConstraint.term);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarColumnConstraint deserialize(DataInputPlus in, Version version) throws IOException
|
||||
{
|
||||
ColumnIdentifier param = new ColumnIdentifier(in.readUTF(), true);
|
||||
Operator relationType = Operator.readFrom(in);
|
||||
return new ScalarColumnConstraint(param, relationType, in.readUTF());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long serializedSize(ScalarColumnConstraint columnConstraint, Version version)
|
||||
{
|
||||
return TypeSizes.sizeof(columnConstraint.term)
|
||||
+ Operator.serializedSize()
|
||||
+ TypeSizes.sizeof(columnConstraint.param.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
return true;
|
||||
|
||||
if (!(o instanceof ScalarColumnConstraint))
|
||||
return false;
|
||||
|
||||
ScalarColumnConstraint other = (ScalarColumnConstraint) o;
|
||||
|
||||
return param.equals(other.param)
|
||||
&& relationType == other.relationType
|
||||
&& term.equals(other.term);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
|
||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.locator.Replica;
|
||||
|
|
@ -800,6 +801,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
for (ByteBuffer key : keys)
|
||||
{
|
||||
Validation.validateKey(metadata(), key);
|
||||
Validation.checkConstraints(metadata(), key);
|
||||
DecoratedKey dk = metadata().partitioner.decorateKey(key);
|
||||
|
||||
PartitionUpdate.Builder updateBuilder = collector.getPartitionUpdateBuilder(metadata(), dk, options.getConsistency());
|
||||
|
|
@ -810,9 +812,11 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
}
|
||||
else
|
||||
{
|
||||
// Clustering keys need to be checked on their own
|
||||
for (Clustering<?> clustering : clusterings)
|
||||
{
|
||||
clustering.validate();
|
||||
checkClusteringConstraints(clustering);
|
||||
addUpdateForKey(updateBuilder, clustering, params);
|
||||
}
|
||||
}
|
||||
|
|
@ -820,6 +824,24 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
}
|
||||
}
|
||||
|
||||
private void checkClusteringConstraints(Clustering<?> clustering)
|
||||
{
|
||||
for (ColumnMetadata column : metadata.clusteringColumns())
|
||||
{
|
||||
if (column.hasConstraint())
|
||||
{
|
||||
try
|
||||
{
|
||||
clustering.checkConstraints(column.position(), metadata.comparator, column.getColumnConstraints());
|
||||
}
|
||||
catch (ConstraintViolationException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Slices createSlices(QueryOptions options)
|
||||
{
|
||||
return restrictions.getSlices(options);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.cql3.statements;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
@ -26,12 +27,15 @@ import org.apache.cassandra.audit.AuditLogEntryType;
|
|||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.conditions.ColumnCondition;
|
||||
import org.apache.cassandra.cql3.conditions.Conditions;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint;
|
||||
import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
||||
import org.apache.cassandra.cql3.terms.Constants;
|
||||
import org.apache.cassandra.cql3.terms.Term;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
import org.apache.cassandra.db.Slice;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
|
|
@ -93,7 +97,9 @@ public class UpdateStatement extends ModificationStatement
|
|||
for (int i = 0, isize = updates.size(); i < isize; i++)
|
||||
updates.get(i).execute(updateBuilder.partitionKey(), params);
|
||||
|
||||
updateBuilder.add(params.buildRow());
|
||||
Row row = params.buildRow();
|
||||
evaluateConstraintsForRow(row, metadata);
|
||||
updateBuilder.add(row);
|
||||
}
|
||||
|
||||
if (updatesStaticRow())
|
||||
|
|
@ -348,4 +354,26 @@ public class UpdateStatement extends ModificationStatement
|
|||
{
|
||||
return new AuditLogContext(AuditLogEntryType.UPDATE, keyspace(), table());
|
||||
}
|
||||
|
||||
public static void evaluateConstraintsForRow(Row row, TableMetadata metadata)
|
||||
{
|
||||
for (ColumnMetadata column : metadata.columnsWithConstraints)
|
||||
{
|
||||
Cell<?> cell = row.getCell(column);
|
||||
if (cell != null)
|
||||
{
|
||||
ColumnMetadata columnMetadata = cell.column();
|
||||
ByteBuffer cellData = cell.buffer();
|
||||
evaluateConstraint(columnMetadata, cellData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void evaluateConstraint(ColumnMetadata columnMetadata, ByteBuffer cellData)
|
||||
{
|
||||
for (ColumnConstraint constraint : columnMetadata.getColumnConstraints().getConstraints())
|
||||
{
|
||||
constraint.evaluate(columnMetadata.type, cellData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import org.apache.cassandra.auth.Permission;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.CQLStatement;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.QualifiedName;
|
||||
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
|
||||
|
|
@ -710,6 +711,67 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
}
|
||||
}
|
||||
|
||||
public static class DropConstraints extends AlterTableStatement
|
||||
{
|
||||
final ColumnIdentifier columnName;
|
||||
|
||||
DropConstraints(String keyspaceName, String tableName, boolean ifTableExists, ColumnIdentifier columnName)
|
||||
{
|
||||
super(keyspaceName, tableName, ifTableExists);
|
||||
this.columnName = columnName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
|
||||
{
|
||||
ColumnMetadata columnMetadata = table.getColumn(columnName);
|
||||
columnMetadata.removeColumnConstraints();
|
||||
|
||||
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
|
||||
Views.Builder viewsBuilder = keyspace.views.unbuild();
|
||||
TableMetadata tableMetadata = tableBuilder.build();
|
||||
tableMetadata.validate();
|
||||
|
||||
return keyspace.withSwapped(keyspace.tables.withSwapped(tableMetadata))
|
||||
.withSwapped(viewsBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
public static class AlterConstraints extends AlterTableStatement
|
||||
{
|
||||
final ColumnIdentifier columnName;
|
||||
final ColumnConstraints constraints;
|
||||
|
||||
AlterConstraints(String keyspaceName, String tableName, boolean ifTableExists, ColumnIdentifier columnName, ColumnConstraints constraints)
|
||||
{
|
||||
super(keyspaceName, tableName, ifTableExists);
|
||||
this.columnName = columnName;
|
||||
this.constraints = constraints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table, ClusterMetadata metadata)
|
||||
{
|
||||
TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
|
||||
|
||||
for (ColumnMetadata column : tableBuilder.columns())
|
||||
{
|
||||
if (column.name == columnName)
|
||||
{
|
||||
column.setColumnConstraints(constraints);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Views.Builder viewsBuilder = keyspace.views.unbuild();
|
||||
TableMetadata tableMetadata = tableBuilder.build();
|
||||
tableMetadata.validate();
|
||||
|
||||
return keyspace.withSwapped(keyspace.tables.withSwapped(tableMetadata))
|
||||
.withSwapped(viewsBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Raw extends CQLStatement.Raw
|
||||
{
|
||||
private enum Kind
|
||||
|
|
@ -720,13 +782,17 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
DROP_COLUMNS,
|
||||
RENAME_COLUMNS,
|
||||
ALTER_OPTIONS,
|
||||
DROP_COMPACT_STORAGE
|
||||
DROP_COMPACT_STORAGE,
|
||||
DROP_CONSTRAINTS,
|
||||
ALTER_CONSTRAINTS
|
||||
}
|
||||
|
||||
private final QualifiedName name;
|
||||
private final boolean ifTableExists;
|
||||
private boolean ifColumnExists;
|
||||
private boolean ifColumnNotExists;
|
||||
private ColumnIdentifier constraintName;
|
||||
private ColumnConstraints constraints;
|
||||
|
||||
private Kind kind;
|
||||
|
||||
|
|
@ -748,9 +814,15 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
public final TableAttributes attrs = new TableAttributes();
|
||||
|
||||
public Raw(QualifiedName name, boolean ifTableExists)
|
||||
{
|
||||
this(name, ifTableExists, null);
|
||||
}
|
||||
|
||||
public Raw(QualifiedName name, boolean ifTableExists, ColumnIdentifier constraintName)
|
||||
{
|
||||
this.name = name;
|
||||
this.ifTableExists = ifTableExists;
|
||||
this.constraintName = constraintName;
|
||||
}
|
||||
|
||||
public AlterTableStatement prepare(ClientState state)
|
||||
|
|
@ -767,6 +839,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
case RENAME_COLUMNS: return new RenameColumns(keyspaceName, tableName, renamedColumns, ifTableExists, ifColumnExists);
|
||||
case ALTER_OPTIONS: return new AlterOptions(keyspaceName, tableName, attrs, ifTableExists);
|
||||
case DROP_COMPACT_STORAGE: return new DropCompactStorage(keyspaceName, tableName, ifTableExists);
|
||||
case DROP_CONSTRAINTS: return new DropConstraints(keyspaceName, tableName, ifTableExists, constraintName);
|
||||
case ALTER_CONSTRAINTS: return new AlterConstraints(keyspaceName, tableName, ifTableExists, constraintName, constraints);
|
||||
}
|
||||
|
||||
throw new AssertionError();
|
||||
|
|
@ -811,6 +885,19 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
kind = Kind.DROP_COMPACT_STORAGE;
|
||||
}
|
||||
|
||||
public void dropConstraints(ColumnIdentifier name)
|
||||
{
|
||||
kind = Kind.DROP_CONSTRAINTS;
|
||||
this.constraintName = name;
|
||||
}
|
||||
|
||||
public void alterConstraints(ColumnIdentifier name, ColumnConstraints.Raw rawConstraints)
|
||||
{
|
||||
kind = Kind.ALTER_CONSTRAINTS;
|
||||
this.constraintName = name;
|
||||
this.constraints = rawConstraints.prepare();
|
||||
}
|
||||
|
||||
public void timestamp(long timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.apache.cassandra.auth.DataResource;
|
|||
import org.apache.cassandra.auth.IResource;
|
||||
import org.apache.cassandra.auth.Permission;
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
|
||||
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
|
|
@ -56,6 +57,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
private final Map<ColumnIdentifier, ColumnProperties.Raw> rawColumns;
|
||||
private final Set<ColumnIdentifier> staticColumns;
|
||||
private final List<ColumnIdentifier> partitionKeyColumns;
|
||||
private final Map<ColumnIdentifier, ColumnConstraints> columnConstraints;
|
||||
private final List<ColumnIdentifier> clusteringColumns;
|
||||
|
||||
private final LinkedHashMap<ColumnIdentifier, Boolean> clusteringOrder;
|
||||
|
|
@ -72,6 +74,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
Set<ColumnIdentifier> staticColumns,
|
||||
List<ColumnIdentifier> partitionKeyColumns,
|
||||
List<ColumnIdentifier> clusteringColumns,
|
||||
Map<ColumnIdentifier, ColumnConstraints> columnConstraints,
|
||||
LinkedHashMap<ColumnIdentifier, Boolean> clusteringOrder,
|
||||
TableAttributes attrs,
|
||||
boolean ifNotExists,
|
||||
|
|
@ -84,6 +87,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
this.staticColumns = staticColumns;
|
||||
this.partitionKeyColumns = partitionKeyColumns;
|
||||
this.clusteringColumns = clusteringColumns;
|
||||
this.columnConstraints = columnConstraints;
|
||||
|
||||
this.clusteringOrder = clusteringOrder;
|
||||
this.attrs = attrs;
|
||||
|
|
@ -343,13 +347,13 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
for (int i = 0; i < partitionKeyColumns.size(); i++)
|
||||
{
|
||||
ColumnProperties properties = partitionKeyColumnProperties.get(i);
|
||||
builder.addPartitionKeyColumn(partitionKeyColumns.get(i), properties.type, properties.mask);
|
||||
builder.addPartitionKeyColumn(partitionKeyColumns.get(i), properties.type, properties.mask, columnConstraints.get(partitionKeyColumns.get(i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < clusteringColumns.size(); i++)
|
||||
{
|
||||
ColumnProperties properties = clusteringColumnProperties.get(i);
|
||||
builder.addClusteringColumn(clusteringColumns.get(i), properties.type, properties.mask);
|
||||
builder.addClusteringColumn(clusteringColumns.get(i), properties.type, properties.mask, columnConstraints.get(clusteringColumns.get(i)));
|
||||
}
|
||||
|
||||
if (useCompactStorage)
|
||||
|
|
@ -360,11 +364,12 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
{
|
||||
columns.forEach((column, properties) -> {
|
||||
if (staticColumns.contains(column))
|
||||
builder.addStaticColumn(column, properties.type, properties.mask);
|
||||
builder.addStaticColumn(column, properties.type, properties.mask, columnConstraints.get(column));
|
||||
else
|
||||
builder.addRegularColumn(column, properties.type, properties.mask);
|
||||
builder.addRegularColumn(column, properties.type, properties.mask, columnConstraints.get(column));
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
@ -506,6 +511,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
private final Map<ColumnIdentifier, ColumnProperties.Raw> rawColumns = new HashMap<>();
|
||||
private final Set<ColumnIdentifier> staticColumns = new HashSet<>();
|
||||
private final List<ColumnIdentifier> clusteringColumns = new ArrayList<>();
|
||||
private final Map<ColumnIdentifier, ColumnConstraints> columnConstraints = new HashMap<>();
|
||||
|
||||
private List<ColumnIdentifier> partitionKeyColumns;
|
||||
|
||||
|
|
@ -531,6 +537,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
staticColumns,
|
||||
partitionKeyColumns,
|
||||
clusteringColumns,
|
||||
columnConstraints,
|
||||
clusteringOrder,
|
||||
attrs,
|
||||
ifNotExists,
|
||||
|
|
@ -559,14 +566,17 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
return name.getName();
|
||||
}
|
||||
|
||||
public void addColumn(ColumnIdentifier column, CQL3Type.Raw type, boolean isStatic, ColumnMask.Raw mask)
|
||||
public void addColumn(ColumnIdentifier column, CQL3Type.Raw type, boolean isStatic, ColumnMask.Raw mask, ColumnConstraints.Raw constraints)
|
||||
{
|
||||
|
||||
if (null != rawColumns.put(column, new ColumnProperties.Raw(type, mask)))
|
||||
throw ire("Duplicate column '%s' declaration for table '%s'", column, name);
|
||||
|
||||
if (isStatic)
|
||||
staticColumns.add(column);
|
||||
if (null == constraints)
|
||||
columnConstraints.put(column, ColumnConstraints.NO_OP);
|
||||
else
|
||||
columnConstraints.put(column, constraints.prepare());
|
||||
}
|
||||
|
||||
public void setCompactStorage()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.function.ToIntFunction;
|
|||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.config.*;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
|
||||
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
|
||||
import org.apache.cassandra.db.marshal.CompositeType;
|
||||
|
|
@ -752,4 +753,9 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
|
|||
return equals(prefix, (ClusteringPrefix<?>) o);
|
||||
}
|
||||
|
||||
default void checkConstraints(int clusterIndex, ClusteringComparator comparator, ColumnConstraints constraints)
|
||||
{
|
||||
comparator.subtype(clusterIndex).checkConstraints(accessor().toBuffer(get(clusterIndex)), constraints);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import com.google.common.collect.Iterators;
|
|||
|
||||
import net.nicoulaj.compilecommand.annotations.DontInline;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.db.rows.ColumnData;
|
||||
|
|
@ -62,7 +63,8 @@ public class Columns extends AbstractCollection<ColumnMetadata> implements Colle
|
|||
SetType.getInstance(UTF8Type.instance, true),
|
||||
ColumnMetadata.NO_POSITION,
|
||||
ColumnMetadata.Kind.STATIC,
|
||||
null);
|
||||
null,
|
||||
ColumnConstraints.NO_OP);
|
||||
|
||||
public static final ColumnMetadata FIRST_COMPLEX_REGULAR =
|
||||
new ColumnMetadata("",
|
||||
|
|
@ -71,7 +73,8 @@ public class Columns extends AbstractCollection<ColumnMetadata> implements Colle
|
|||
SetType.getInstance(UTF8Type.instance, true),
|
||||
ColumnMetadata.NO_POSITION,
|
||||
ColumnMetadata.Kind.REGULAR,
|
||||
null);
|
||||
null,
|
||||
ColumnConstraints.NO_OP);
|
||||
|
||||
private final Object[] columns;
|
||||
private final int complexIdx; // Index of the first complex column
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
|
||||
import org.apache.cassandra.cql3.terms.Term;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
|
|
@ -47,6 +50,13 @@ public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
// Constraints are not supported for composite types
|
||||
return false;
|
||||
}
|
||||
|
||||
public <VL, VR> int compareCustom(VL left, ValueAccessor<VL> accessorL, VR right, ValueAccessor<VR> accessorR)
|
||||
{
|
||||
if (accessorL.isEmpty(left) || accessorR.isEmpty(right))
|
||||
|
|
@ -281,7 +291,8 @@ public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
|
|||
validate(bb, ByteBufferAccessor.instance);
|
||||
}
|
||||
|
||||
public <V> void validate(V input, ValueAccessor<V> accessor)
|
||||
@Override
|
||||
public <V> void validate(V input, ValueAccessor<V> accessor)
|
||||
{
|
||||
boolean isStatic = readIsStatic(input, accessor);
|
||||
int offset = startingOffset(isStatic);
|
||||
|
|
@ -316,6 +327,55 @@ public abstract class AbstractCompositeType extends AbstractType<ByteBuffer>
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkConstraints(ByteBuffer input, ColumnConstraints constraints) throws ConstraintViolationException
|
||||
{
|
||||
// no constraints defined for the partition keys
|
||||
if (!constraints.hasRelevantConstraints())
|
||||
return;
|
||||
|
||||
ValueAccessor<ByteBuffer> accessor = ByteBufferAccessor.instance;
|
||||
|
||||
boolean isStatic = readIsStatic(input, accessor);
|
||||
int offset = startingOffset(isStatic);
|
||||
|
||||
int i = 0;
|
||||
List<ByteBuffer> partitionKeyValues = new ArrayList<>();
|
||||
while (!accessor.isEmptyFromOffset(input, offset))
|
||||
{
|
||||
AbstractType<?> comparator = getComparator(i, input, accessor, offset);
|
||||
offset += getComparatorSize(i, input, accessor, offset);
|
||||
|
||||
int length = accessor.getUnsignedShort(input, offset);
|
||||
offset += 2;
|
||||
|
||||
ByteBuffer value = accessor.slice(input, offset, length);
|
||||
// partitionKeyValues.add(comparator.compose(value));
|
||||
partitionKeyValues.add(value);
|
||||
offset += length;
|
||||
|
||||
accessor.getByte(input, offset++);
|
||||
++i;
|
||||
}
|
||||
|
||||
if (partitionKeyValues.size() != constraints.getConstraints().size())
|
||||
{
|
||||
// contraints list should have the exact size of partition key values.
|
||||
// Noop constraints are filled for the partition key columns w/o any constraints.
|
||||
throw new IllegalStateException("The number of constraints (" + partitionKeyValues.size() + ") "
|
||||
+ "should be the same with the number of partition key columns ("
|
||||
+ constraints.getConstraints().size() + ")");
|
||||
}
|
||||
|
||||
for (int k = 0; k < constraints.getConstraints().size(); k++)
|
||||
{
|
||||
AbstractType<?> comparator = getComparator(i, input, accessor, offset);
|
||||
ByteBuffer value = partitionKeyValues.get(k);
|
||||
ColumnConstraint constraint = constraints.getConstraints().get(k);
|
||||
constraint.evaluate(comparator, value);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract ByteBuffer decompose(Object... objects);
|
||||
|
||||
abstract protected <V> int getComparatorSize(int i, V value, ValueAccessor<V> accessor, int offset);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import java.util.Objects;
|
|||
import org.apache.cassandra.cql3.AssignmentTestable;
|
||||
import org.apache.cassandra.cql3.CQL3Type;
|
||||
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
|
||||
import org.apache.cassandra.cql3.terms.Term;
|
||||
import org.apache.cassandra.cql3.functions.ArgumentDeserializer;
|
||||
import org.apache.cassandra.db.rows.Cell;
|
||||
|
|
@ -204,6 +207,25 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
getSerializer().validate(value, accessor);
|
||||
}
|
||||
|
||||
public void checkConstraints(ByteBuffer bytes, ColumnConstraints constraints) throws ConstraintViolationException
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return;
|
||||
|
||||
T value = getSerializer().deserialize(bytes);
|
||||
constraints.evaluate(this, bytes);
|
||||
}
|
||||
|
||||
public void checkConstraints(ByteBuffer bytes, List<ColumnConstraint> constraints) throws ConstraintViolationException
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return;
|
||||
|
||||
T value = getSerializer().deserialize(bytes);
|
||||
for (ColumnConstraint constraint : constraints)
|
||||
constraint.evaluate(this, bytes);
|
||||
}
|
||||
|
||||
public final int compare(ByteBuffer left, ByteBuffer right)
|
||||
{
|
||||
return compare(left, ByteBufferAccessor.instance, right, ByteBufferAccessor.instance);
|
||||
|
|
@ -525,6 +547,11 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
return getSerializer().isNull(buffer, accessor);
|
||||
}
|
||||
|
||||
public boolean isNumber()
|
||||
{
|
||||
return unwrap() instanceof org.apache.cassandra.db.marshal.NumberType;
|
||||
}
|
||||
|
||||
// This assumes that no empty values are passed
|
||||
public void writeValue(ByteBuffer value, DataOutputPlus out) throws IOException
|
||||
{
|
||||
|
|
@ -799,4 +826,9 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
return type.compose(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -456,4 +456,10 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
|
|||
|
||||
return getSerializer().getSerializedValue(((Cell<?>) columnData).buffer(), keyOrIndex, getValuesType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,4 +244,10 @@ public class ReversedType<T> extends AbstractType<T>
|
|||
{
|
||||
return baseType.unwrap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return unwrap().isConstrainable();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -616,4 +616,10 @@ public class TupleType extends MultiElementType<ByteBuffer>
|
|||
{
|
||||
throw new UnsupportedOperationException("Multicell tuples are not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -618,6 +618,12 @@ public class UserType extends TupleType implements SchemaElement
|
|||
return "field " + fieldName(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private enum ConflictBehavior
|
||||
{
|
||||
LOG {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
|
@ -30,6 +31,8 @@ import com.google.common.collect.Collections2;
|
|||
import com.google.common.collect.Lists;
|
||||
|
||||
import org.apache.cassandra.cql3.*;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
|
||||
import org.apache.cassandra.cql3.selection.Selectable;
|
||||
import org.apache.cassandra.cql3.selection.Selector;
|
||||
|
|
@ -115,6 +118,9 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
@Nullable
|
||||
private final ColumnMask mask;
|
||||
|
||||
@Nonnull
|
||||
private ColumnConstraints columnConstraints;
|
||||
|
||||
private static long comparisonOrder(Kind kind, boolean isComplex, long position, ColumnIdentifier name)
|
||||
{
|
||||
assert position >= 0 && position < 1 << 12;
|
||||
|
|
@ -126,42 +132,60 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
|
||||
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,
|
||||
ByteBuffer name,
|
||||
AbstractType<?> type,
|
||||
int position,
|
||||
Kind kind,
|
||||
@Nullable ColumnMask mask,
|
||||
@Nonnull ColumnConstraints columnConstraints)
|
||||
{
|
||||
this(table.keyspace,
|
||||
table.name,
|
||||
ColumnIdentifier.getInterned(name, UTF8Type.instance),
|
||||
type,
|
||||
position,
|
||||
kind,
|
||||
mask,
|
||||
columnConstraints);
|
||||
}
|
||||
|
||||
public ColumnMetadata(TableMetadata table,
|
||||
|
|
@ -177,7 +201,8 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
type,
|
||||
position,
|
||||
kind,
|
||||
mask);
|
||||
mask,
|
||||
ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
|
@ -188,6 +213,19 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
int position,
|
||||
Kind kind,
|
||||
@Nullable ColumnMask mask)
|
||||
{
|
||||
this(ksName, cfName, name, type, position, kind, mask, ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public ColumnMetadata(String ksName,
|
||||
String cfName,
|
||||
ColumnIdentifier name,
|
||||
AbstractType<?> type,
|
||||
int position,
|
||||
Kind kind,
|
||||
@Nullable ColumnMask mask,
|
||||
@Nonnull ColumnConstraints columnConstraints)
|
||||
{
|
||||
super(ksName, cfName, name, type);
|
||||
assert name != null && type != null && kind != null;
|
||||
|
|
@ -206,6 +244,7 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
this.asymmetricCellPathComparator = cellPathComparator == null ? null : (a, b) -> cellPathComparator.compare(((Cell<?>)a).path(), (CellPath) b);
|
||||
this.comparisonOrder = comparisonOrder(kind, isComplex(), Math.max(0, position), name);
|
||||
this.mask = mask;
|
||||
this.columnConstraints = columnConstraints;
|
||||
}
|
||||
|
||||
private static Comparator<CellPath> makeCellPathComparator(Kind kind, AbstractType<?> type)
|
||||
|
|
@ -237,22 +276,22 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
|
||||
public ColumnMetadata copy()
|
||||
{
|
||||
return new ColumnMetadata(ksName, cfName, name, type, position, kind, mask);
|
||||
return new ColumnMetadata(ksName, cfName, name, type, position, kind, mask, columnConstraints);
|
||||
}
|
||||
|
||||
public ColumnMetadata withNewName(ColumnIdentifier newName)
|
||||
{
|
||||
return new ColumnMetadata(ksName, cfName, newName, type, position, kind, mask);
|
||||
return new ColumnMetadata(ksName, cfName, newName, type, position, kind, mask, columnConstraints);
|
||||
}
|
||||
|
||||
public ColumnMetadata withNewType(AbstractType<?> newType)
|
||||
{
|
||||
return new ColumnMetadata(ksName, cfName, name, newType, position, kind, mask);
|
||||
return new ColumnMetadata(ksName, cfName, name, newType, position, kind, mask, columnConstraints);
|
||||
}
|
||||
|
||||
public ColumnMetadata withNewMask(@Nullable ColumnMask newMask)
|
||||
{
|
||||
return new ColumnMetadata(ksName, cfName, name, type, position, kind, newMask);
|
||||
return new ColumnMetadata(ksName, cfName, name, type, position, kind, newMask, columnConstraints);
|
||||
}
|
||||
|
||||
public boolean isPartitionKey()
|
||||
|
|
@ -275,6 +314,11 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
return mask != null;
|
||||
}
|
||||
|
||||
public boolean hasConstraint()
|
||||
{
|
||||
return columnConstraints.hasRelevantConstraints();
|
||||
}
|
||||
|
||||
public boolean isRegular()
|
||||
{
|
||||
return kind == Kind.REGULAR;
|
||||
|
|
@ -299,6 +343,21 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
return mask;
|
||||
}
|
||||
|
||||
public ColumnConstraints getColumnConstraints()
|
||||
{
|
||||
return columnConstraints;
|
||||
}
|
||||
|
||||
public void setColumnConstraints(ColumnConstraints constraints)
|
||||
{
|
||||
this.columnConstraints = constraints;
|
||||
}
|
||||
|
||||
public void removeColumnConstraints()
|
||||
{
|
||||
columnConstraints = ColumnConstraints.NO_OP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
|
|
@ -320,7 +379,8 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
&& 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);
|
||||
}
|
||||
|
||||
Optional<Difference> compare(ColumnMetadata other)
|
||||
|
|
@ -351,6 +411,7 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
result = 31 * result + (kind == null ? 0 : kind.hashCode());
|
||||
result = 31 * result + position;
|
||||
result = 31 * result + (mask == null ? 0 : mask.hashCode());
|
||||
result = 31 * result + (columnConstraints == null ? 0 : columnConstraints.hashCode());
|
||||
hash = result;
|
||||
}
|
||||
return result;
|
||||
|
|
@ -516,6 +577,19 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
|
||||
if (isMasked())
|
||||
mask.appendCqlTo(builder);
|
||||
|
||||
if (!columnConstraints.isEmpty())
|
||||
{
|
||||
builder.append(" CHECK ");
|
||||
Iterator<ColumnConstraint<?>> constraintIterator = columnConstraints.getConstraints().iterator();
|
||||
constraintIterator.next().appendCqlTo(builder);
|
||||
|
||||
while (constraintIterator.hasNext())
|
||||
{
|
||||
builder.append(" AND ");
|
||||
constraintIterator.next().appendCqlTo(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String toCQLString(Iterable<ColumnMetadata> defs)
|
||||
|
|
@ -609,6 +683,13 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
out.writeBoolean(t.mask != null);
|
||||
if (t.mask != null)
|
||||
ColumnMask.serializer.serialize(t.mask, out, version);
|
||||
if (version.isAtLeast(Version.V6))
|
||||
{
|
||||
boolean hasConstraints = t.hasConstraint();
|
||||
out.writeBoolean(hasConstraints);
|
||||
if (hasConstraints)
|
||||
ColumnConstraints.serializer.serialize(t.columnConstraints, out, version);
|
||||
}
|
||||
}
|
||||
|
||||
public ColumnMetadata deserialize(DataInputPlus in, Types types, UserFunctions functions, Version version) throws IOException
|
||||
|
|
@ -627,11 +708,23 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
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);
|
||||
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))
|
||||
{
|
||||
constraintsSize += BOOL_SIZE;
|
||||
if (t.hasConstraint())
|
||||
constraintsSize += t.getColumnConstraints().serializer().serializedSize(t.columnConstraints, version);
|
||||
}
|
||||
return sizeof(t.ksName) +
|
||||
sizeof(t.cfName) +
|
||||
sizeof(t.kind.name()) +
|
||||
|
|
@ -641,7 +734,8 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
|
|||
sizeof(t.name.toString()) +
|
||||
ByteBufferUtil.serializedSizeWithShortLength(t.name.bytes) +
|
||||
BOOL_SIZE +
|
||||
((t.mask == null) ? 0 : ColumnMask.serializer.serializedSize(t.mask, version));
|
||||
((t.mask == null) ? 0 : ColumnMask.serializer.serializedSize(t.mask, version)) +
|
||||
constraintsSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import java.util.Map.Entry;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
|
@ -46,9 +47,12 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import org.apache.cassandra.auth.DataResource;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.CqlBuilder;
|
||||
import org.apache.cassandra.cql3.SchemaElement;
|
||||
import org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionException;
|
||||
import org.apache.cassandra.cql3.functions.Function;
|
||||
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
|
||||
import org.apache.cassandra.db.Clustering;
|
||||
|
|
@ -196,6 +200,12 @@ public class TableMetadata implements SchemaElement
|
|||
public final DataResource resource;
|
||||
public TableMetadataRef ref;
|
||||
|
||||
// We cache the columns with constraints to avoid iterations over columns
|
||||
// Partition keys columns are evaluated separately, so we keep the two of them in
|
||||
// two different variables.
|
||||
public final List<ColumnConstraint> partitionKeyConstraints;
|
||||
public final List<ColumnMetadata> columnsWithConstraints;
|
||||
|
||||
protected TableMetadata(Builder builder)
|
||||
{
|
||||
flags = Sets.immutableEnumSet(builder.flags);
|
||||
|
|
@ -235,6 +245,22 @@ public class TableMetadata implements SchemaElement
|
|||
ref = TableMetadataRef.forIndex(Schema.instance, this, keyspace, indexName, id);
|
||||
else
|
||||
ref = TableMetadataRef.withInitialReference(new TableMetadataRef(Schema.instance, keyspace, name, id), this);
|
||||
|
||||
List<ColumnConstraint> pkConstraints = new ArrayList<>(this.partitionKeyColumns.size());
|
||||
for (ColumnMetadata column : this.partitionKeyColumns)
|
||||
{
|
||||
if (column.hasConstraint())
|
||||
pkConstraints.add(column.getColumnConstraints());
|
||||
}
|
||||
this.partitionKeyConstraints = pkConstraints;
|
||||
|
||||
List<ColumnMetadata> columnsWithConstraints = new ArrayList<>();
|
||||
for (ColumnMetadata column : this.columns())
|
||||
{
|
||||
if (column.hasConstraint() && !column.isPartitionKey() && !column.isClusteringColumn())
|
||||
columnsWithConstraints.add(column);
|
||||
}
|
||||
this.columnsWithConstraints = columnsWithConstraints;
|
||||
}
|
||||
|
||||
public static Builder builder(String keyspace, String table)
|
||||
|
|
@ -527,6 +553,19 @@ public class TableMetadata implements SchemaElement
|
|||
except("Missing partition keys for table %s", toString());
|
||||
|
||||
indexes.validate(this);
|
||||
|
||||
for (ColumnMetadata columnMetadata : columns())
|
||||
{
|
||||
ColumnConstraints constraints = columnMetadata.getColumnConstraints();
|
||||
try
|
||||
{
|
||||
constraints.validate(columnMetadata);
|
||||
}
|
||||
catch (InvalidConstraintDefinitionException e)
|
||||
{
|
||||
throw new InvalidRequestException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1019,7 +1058,12 @@ public class TableMetadata implements SchemaElement
|
|||
|
||||
public Builder addPartitionKeyColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, partitionKeyColumns.size(), ColumnMetadata.Kind.PARTITION_KEY, mask));
|
||||
return addPartitionKeyColumn(name, type, mask, ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
public Builder addPartitionKeyColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask, @Nonnull ColumnConstraints cqlConstraints)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, partitionKeyColumns.size(), ColumnMetadata.Kind.PARTITION_KEY, mask, cqlConstraints));
|
||||
}
|
||||
|
||||
public Builder addClusteringColumn(String name, AbstractType<?> type)
|
||||
|
|
@ -1039,7 +1083,12 @@ public class TableMetadata implements SchemaElement
|
|||
|
||||
public Builder addClusteringColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, clusteringColumns.size(), ColumnMetadata.Kind.CLUSTERING, mask));
|
||||
return addClusteringColumn(name, type, mask, ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
public Builder addClusteringColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask, @Nonnull ColumnConstraints cqlConstraints)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, clusteringColumns.size(), ColumnMetadata.Kind.CLUSTERING, mask, cqlConstraints));
|
||||
}
|
||||
|
||||
public Builder addRegularColumn(String name, AbstractType<?> type)
|
||||
|
|
@ -1059,7 +1108,12 @@ public class TableMetadata implements SchemaElement
|
|||
|
||||
public Builder addRegularColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, ColumnMetadata.NO_POSITION, ColumnMetadata.Kind.REGULAR, mask));
|
||||
return addRegularColumn(name, type, mask, ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
public Builder addRegularColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask, @Nonnull ColumnConstraints cqlConstraints)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, ColumnMetadata.NO_POSITION, ColumnMetadata.Kind.REGULAR, mask, cqlConstraints));
|
||||
}
|
||||
|
||||
public Builder addStaticColumn(String name, AbstractType<?> type)
|
||||
|
|
@ -1079,7 +1133,12 @@ public class TableMetadata implements SchemaElement
|
|||
|
||||
public Builder addStaticColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, ColumnMetadata.NO_POSITION, ColumnMetadata.Kind.STATIC, mask));
|
||||
return addStaticColumn(name, type, mask, ColumnConstraints.NO_OP);
|
||||
}
|
||||
|
||||
public Builder addStaticColumn(ColumnIdentifier name, AbstractType<?> type, @Nullable ColumnMask mask, @Nonnull ColumnConstraints cqlConstraints)
|
||||
{
|
||||
return addColumn(new ColumnMetadata(keyspace, this.name, name, type, ColumnMetadata.NO_POSITION, ColumnMetadata.Kind.STATIC, mask, cqlConstraints));
|
||||
}
|
||||
|
||||
public Builder addColumn(ColumnMetadata column)
|
||||
|
|
@ -1376,8 +1435,7 @@ public class TableMetadata implements SchemaElement
|
|||
if (!hasSingleColumnPrimaryKey)
|
||||
appendPrimaryKey(builder);
|
||||
|
||||
builder.decreaseIndent()
|
||||
.append(')');
|
||||
builder.decreaseIndent().append(')');
|
||||
|
||||
builder.append(" WITH ")
|
||||
.increaseIndent();
|
||||
|
|
@ -1461,8 +1519,7 @@ public class TableMetadata implements SchemaElement
|
|||
builder.append(", ")
|
||||
.appendWithSeparators(clusteringColumns, (b, c) -> b.append(c.name), ", ");
|
||||
|
||||
builder.append(')')
|
||||
.newLine();
|
||||
builder.append(')').newLine();
|
||||
}
|
||||
|
||||
void appendTableOptions(CqlBuilder builder, boolean withInternals)
|
||||
|
|
@ -1682,7 +1739,7 @@ public class TableMetadata implements SchemaElement
|
|||
for (ColumnMetadata c : regularAndStaticColumns)
|
||||
{
|
||||
if (c.isStatic())
|
||||
columns.add(new ColumnMetadata(c.ksName, c.cfName, c.name, c.type, -1, ColumnMetadata.Kind.REGULAR, c.getMask()));
|
||||
columns.add(new ColumnMetadata(c.ksName, c.cfName, c.name, c.type, -1, ColumnMetadata.Kind.REGULAR, c.getMask(), c.getColumnConstraints()));
|
||||
}
|
||||
otherColumns = columns.iterator();
|
||||
}
|
||||
|
|
@ -1900,7 +1957,6 @@ public class TableMetadata implements SchemaElement
|
|||
size += Triggers.serializer.serializedSize(t.triggers, version);
|
||||
|
||||
return size;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
|
|||
public class NodeVersion implements Comparable<NodeVersion>
|
||||
{
|
||||
public static final Serializer serializer = new Serializer();
|
||||
public static final Version CURRENT_METADATA_VERSION = Version.V5;
|
||||
public static final Version CURRENT_METADATA_VERSION = Version.V6;
|
||||
public static final NodeVersion CURRENT = new NodeVersion(new CassandraVersion(FBUtilities.getReleaseVersionString()), CURRENT_METADATA_VERSION);
|
||||
private static final CassandraVersion SINCE_VERSION = CassandraVersion.CASSANDRA_5_0;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ public enum Version
|
|||
* - PreInitialize includes datacenter (affects local serialization on first CMS node only)
|
||||
*/
|
||||
V5(5),
|
||||
/**
|
||||
* CEP-42 - Constraints framework. New version due to modifications in table metadata serialization.
|
||||
*/
|
||||
V6(6),
|
||||
|
||||
UNKNOWN(Integer.MAX_VALUE);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* 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.distributed.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.ConsistencyLevel;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.Condition;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
public class ColumnConstraintsTest extends TestBaseImpl
|
||||
{
|
||||
final static Map<String, String> RELATIONS_MAP = Map.of("st", "<",
|
||||
"set", "<=",
|
||||
"et", "=",
|
||||
"net", "!=",
|
||||
"bt", ">",
|
||||
"bet", ">=");
|
||||
|
||||
@Test
|
||||
public void testInvalidConstraintsExceptions() throws IOException
|
||||
{
|
||||
final String tableName = KEYSPACE + ".tbl1";
|
||||
|
||||
try (Cluster cluster = init(Cluster.build(3).start()))
|
||||
{
|
||||
assertThrowsInvalidConstraintException(cluster, String.format("CREATE TABLE %s (pk int, ck1 text CHECK ck1 < 100, ck2 int, v int, " +
|
||||
"PRIMARY KEY ((pk), ck1, ck2));", tableName),
|
||||
"ck1 is not a number");
|
||||
|
||||
assertThrowsInvalidConstraintException(cluster, String.format("CREATE TABLE %s (pk int, ck1 int CHECK LENGTH(ck1) < 100, ck2 int, v int, " +
|
||||
"PRIMARY KEY ((pk), ck1, ck2));", tableName),
|
||||
"Column should be of type class org.apache.cassandra.db.marshal.UTF8Type or " +
|
||||
"class org.apache.cassandra.db.marshal.AsciiType but got class org.apache.cassandra.db.marshal.Int32Type");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConstraint() throws IOException
|
||||
{
|
||||
final String tableName = KEYSPACE + ".tbl1";
|
||||
|
||||
try (Cluster cluster = init(Cluster.build(3).start()))
|
||||
{
|
||||
String createTableStatement = "CREATE TABLE %s (pk int, ck1 int CHECK ck1 < 100, ck2 int, v int, PRIMARY KEY ((pk), ck1, ck2));";
|
||||
cluster.schemaChange(String.format(createTableStatement, tableName));
|
||||
|
||||
String insertStatement = "INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 2, 200, 3)";
|
||||
|
||||
cluster.coordinator(1).execute(String.format("ALTER TABLE %s ALTER ck2 CHECK ck2 < 100", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
// Can't insert
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format(insertStatement, tableName),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("ALTER TABLE %s ALTER ck2 DROP CHECK", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
// Can insert after droping the constraint
|
||||
cluster.coordinator(1).execute(String.format(insertStatement, tableName), ConsistencyLevel.ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstraintWithJsonInsert() throws IOException
|
||||
{
|
||||
final String tableName = KEYSPACE + ".tbl1";
|
||||
|
||||
try (Cluster cluster = init(Cluster.build(3).start()))
|
||||
{
|
||||
String createTableStatement = "CREATE TABLE %s (pk int, ck1 int CHECK ck1 < 100, ck2 int, v uuid, PRIMARY KEY ((pk), ck1, ck2));";
|
||||
cluster.schemaChange(String.format(createTableStatement, tableName));
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s JSON '{\"pk\" : 1, \"ck1\" : 2, \"ck2\" : 2, \"v\" : \"ac064e40-0417-4a4a-bf53-b7cf145afdc2\" }'", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s JSON '{\"pk\" : 1, \"ck1\" : 200, \"ck2\" : 2, \"v\" : \"ac064e40-0417-4a4a-bf53-b7cf145afdc2\" }'", tableName),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s JSON '{\"pk\" : 1, \"ck1\": 100, \"ck2\" : 2, \"v\" : \"ac064e40-0417-4a4a-bf53-b7cf145afdc2\" }'", tableName),
|
||||
"ck1 value length should be smaller than 100");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScalarTableLevelConstraint() throws IOException
|
||||
{
|
||||
Set<String> typesSet = Set.of("int", "double", "float", "decimal");
|
||||
|
||||
try (Cluster cluster = init(Cluster.build(3).start()))
|
||||
{
|
||||
// Create tables
|
||||
for (String type : typesSet)
|
||||
{
|
||||
for (Map.Entry<String, String> relation : RELATIONS_MAP.entrySet())
|
||||
{
|
||||
String tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, relation.getKey());
|
||||
String createTableStatementSmallerThan = "CREATE TABLE " + tableName + " (pk int, ck1 " + type + " CHECK ck1 " + relation.getValue() + " 100, ck2 int, v int, PRIMARY KEY ((pk), ck1, ck2));";
|
||||
cluster.schemaChange(createTableStatementSmallerThan);
|
||||
}
|
||||
}
|
||||
|
||||
for (String type : typesSet)
|
||||
{
|
||||
// st
|
||||
String tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "st");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 2, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName),
|
||||
"ck1 value length be smaller than 100.0");
|
||||
|
||||
// set
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "set");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 2, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
// et
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "et");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
// net
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "net");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
// bt
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "bt");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 1, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
// bet
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "bet");
|
||||
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 200, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 100, 2, 3)", tableName), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO %s (pk, ck1, ck2, v) VALUES (1, 1, 2, 3)", tableName),
|
||||
"ck1 value should be smaller than 100.0");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLengthTableLevelConstraint() throws IOException
|
||||
{
|
||||
Set<String> typesSet = Set.of("varchar", "text", "blob", "ascii");
|
||||
|
||||
try (Cluster cluster = init(Cluster.build(3).start()))
|
||||
{
|
||||
// Create tables
|
||||
for (String type : typesSet)
|
||||
{
|
||||
for (Map.Entry<String, String> relation : RELATIONS_MAP.entrySet())
|
||||
{
|
||||
String tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, relation.getKey());
|
||||
String createTableStatementSmallerThan = "CREATE TABLE " + tableName + " (pk " + type + " CHECK LENGTH(pk) " + relation.getValue() + " 4, ck1 int, ck2 int, v int, PRIMARY KEY ((pk), ck1, ck2));";
|
||||
cluster.schemaChange(createTableStatementSmallerThan);
|
||||
}
|
||||
}
|
||||
|
||||
for (String type : typesSet)
|
||||
{
|
||||
String value = "'%s'";
|
||||
if (type.equals("blob"))
|
||||
value = "textAsBlob('%s')";
|
||||
// st
|
||||
String tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "st");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 200, 2, 3)", "fooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "foooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
// set
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "set");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foo"), ConsistencyLevel.ALL);
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "fooo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "foooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
// et
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "et");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "fooo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "foooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "fo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
// net
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "net");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foooo"), ConsistencyLevel.ALL);
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "fooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
// bt
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "bt");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foooo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "fooo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "foo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
|
||||
// bet
|
||||
tableName = String.format(KEYSPACE + ".%s_tbl1_%s", type, "bet");
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "foooo"), ConsistencyLevel.ALL);
|
||||
cluster.coordinator(1).execute(String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 2, 2, 3)", "fooo"), ConsistencyLevel.ALL);
|
||||
|
||||
assertThrowsConstraintViolationException(cluster,
|
||||
String.format("INSERT INTO " + tableName + " (pk, ck1, ck2, v) VALUES (" + value + ", 100, 2, 3)", "foo"),
|
||||
"ck1 value length should be smaller than 100");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertThrowsConstraintViolationException(Cluster cluster, String statement, String description)
|
||||
{
|
||||
Assertions.assertThatThrownBy(() -> cluster.coordinator(1).execute(statement, ConsistencyLevel.ALL))
|
||||
.describedAs(description)
|
||||
.has(new Condition<Throwable>(t -> t.getClass().getCanonicalName()
|
||||
.equals(InvalidRequestException.class.getCanonicalName()), description));
|
||||
}
|
||||
|
||||
private void assertThrowsInvalidConstraintException(Cluster cluster, String statement, String description)
|
||||
{
|
||||
Assertions.setMaxStackTraceElementsDisplayed(100);
|
||||
assertThatThrownBy(() -> cluster.schemaChange(statement))
|
||||
.describedAs(description)
|
||||
.has(new Condition<Throwable>(t -> t.getClass().getCanonicalName()
|
||||
.equals(InvalidRequestException.class.getCanonicalName()), description));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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.distributed.test.cdc;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
||||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.Feature;
|
||||
import org.apache.cassandra.distributed.test.TestBaseImpl;
|
||||
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.assertTrue;
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.row;
|
||||
|
||||
public abstract class ToggleCDCOnRepair extends TestBaseImpl
|
||||
{
|
||||
Consumer<Cluster> getRepairEnabledRepairAssertion()
|
||||
{
|
||||
return cluster -> {
|
||||
cluster.get(2).runOnInstance(() -> {
|
||||
boolean containCDCInLog = CommitLog.instance.segmentManager
|
||||
.getActiveSegments()
|
||||
.stream()
|
||||
.anyMatch(s -> s.getCDCState() == CommitLogSegment.CDCState.CONTAINS);
|
||||
assertTrue("Mutation should be added to commit log when cdc_on_repair_enabled is true",
|
||||
containCDCInLog);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Consumer<Cluster> getRepairDisabledRepairAssertion()
|
||||
{
|
||||
return cluster -> {
|
||||
cluster.get(2).runOnInstance(() -> {
|
||||
boolean containCDCInLog = CommitLog.instance.segmentManager
|
||||
.getActiveSegments()
|
||||
.stream()
|
||||
.allMatch(s -> s.getCDCState() != CommitLogSegment.CDCState.CONTAINS);
|
||||
assertTrue("No mutation should be added to commit log when cdc_on_repair_enabled is false",
|
||||
containCDCInLog);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// test helper to repair data between nodes when cdc_on_repair_enabled is on or off.
|
||||
void testCDCOnRepairEnabled(boolean enabled, Consumer<Cluster> assertion, boolean constraintsEnabled) throws Exception
|
||||
{
|
||||
try (Cluster cluster = init(Cluster.build(2)
|
||||
.withConfig(c -> c.set("cdc_enabled", true)
|
||||
.set("cdc_on_repair_enabled", enabled)
|
||||
.with(Feature.NETWORK)
|
||||
.with(Feature.GOSSIP))
|
||||
.start()))
|
||||
{
|
||||
cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (k INT PRIMARY KEY, v INT) WITH cdc=true"));
|
||||
|
||||
// Data only in node1
|
||||
cluster.get(1).executeInternal(withKeyspace("INSERT INTO %s.tbl (k, v) VALUES (1, 1)"));
|
||||
|
||||
if (constraintsEnabled)
|
||||
cluster.schemaChange(withKeyspace("ALTER TABLE %s.tbl ALTER v CHECK v != 1"));
|
||||
|
||||
Object[][] result = cluster.get(1).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result, row(1, 1));
|
||||
result = cluster.get(2).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result);
|
||||
|
||||
// repair
|
||||
cluster.get(1).flush(KEYSPACE);
|
||||
cluster.get(2).nodetool("repair", KEYSPACE, "tbl");
|
||||
|
||||
// verify node2 now have data
|
||||
result = cluster.get(2).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result, row(1, 1));
|
||||
|
||||
assertion.accept(cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,80 +18,21 @@
|
|||
|
||||
package org.apache.cassandra.distributed.test.cdc;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogSegment;
|
||||
import org.apache.cassandra.distributed.Cluster;
|
||||
import org.apache.cassandra.distributed.api.Feature;
|
||||
import org.apache.cassandra.distributed.test.TestBaseImpl;
|
||||
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.assertRows;
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.assertTrue;
|
||||
import static org.apache.cassandra.distributed.shared.AssertUtils.row;
|
||||
|
||||
public class ToggleCDCOnRepairEnabledTest extends TestBaseImpl
|
||||
public class ToggleCDCOnRepairEnabledTest extends ToggleCDCOnRepair
|
||||
{
|
||||
@Test
|
||||
public void testCDCOnRepairIsEnabled() throws Exception
|
||||
{
|
||||
testCDCOnRepairEnabled(true, cluster -> {
|
||||
cluster.get(2).runOnInstance(() -> {
|
||||
boolean containCDCInLog = CommitLog.instance.segmentManager
|
||||
.getActiveSegments()
|
||||
.stream()
|
||||
.anyMatch(s -> s.getCDCState() == CommitLogSegment.CDCState.CONTAINS);
|
||||
assertTrue("Mutation should be added to commit log when cdc_on_repair_enabled is true",
|
||||
containCDCInLog);
|
||||
});
|
||||
});
|
||||
testCDCOnRepairEnabled(true, getRepairEnabledRepairAssertion(), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCDCOnRepairIsDisabled() throws Exception
|
||||
{
|
||||
testCDCOnRepairEnabled(false, cluster -> {
|
||||
cluster.get(2).runOnInstance(() -> {
|
||||
boolean containCDCInLog = CommitLog.instance.segmentManager
|
||||
.getActiveSegments()
|
||||
.stream()
|
||||
.allMatch(s -> s.getCDCState() != CommitLogSegment.CDCState.CONTAINS);
|
||||
assertTrue("No mutation should be added to commit log when cdc_on_repair_enabled is false",
|
||||
containCDCInLog);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// test helper to repair data between nodes when cdc_on_repair_enabled is on or off.
|
||||
private void testCDCOnRepairEnabled(boolean enabled, Consumer<Cluster> assertion) throws Exception
|
||||
{
|
||||
try (Cluster cluster = init(Cluster.build(2)
|
||||
.withConfig(c -> c.set("cdc_enabled", true)
|
||||
.set("cdc_on_repair_enabled", enabled)
|
||||
.with(Feature.NETWORK)
|
||||
.with(Feature.GOSSIP))
|
||||
.start()))
|
||||
{
|
||||
cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (k INT PRIMARY KEY, v INT) WITH cdc=true"));
|
||||
|
||||
// Data only in node1
|
||||
cluster.get(1).executeInternal(withKeyspace("INSERT INTO %s.tbl (k, v) VALUES (1, 1)"));
|
||||
Object[][] result = cluster.get(1).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result, row(1, 1));
|
||||
result = cluster.get(2).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result);
|
||||
|
||||
// repair
|
||||
cluster.get(1).flush(KEYSPACE);
|
||||
cluster.get(2).nodetool("repair", KEYSPACE, "tbl");
|
||||
|
||||
// verify node2 now have data
|
||||
result = cluster.get(2).executeInternal(withKeyspace("SELECT * FROM %s.tbl WHERE k = 1"));
|
||||
assertRows(result, row(1, 1));
|
||||
|
||||
assertion.accept(cluster);
|
||||
}
|
||||
testCDCOnRepairEnabled(false, getRepairDisabledRepairAssertion(), false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.distributed.test.cdc;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class ToggleCDCWithConstraintsOnRepairEnabledTest extends ToggleCDCOnRepair
|
||||
{
|
||||
@Test
|
||||
public void testCDCWithConstraintsOnRepairIsEnabled() throws Exception
|
||||
{
|
||||
testCDCOnRepairEnabled(true, getRepairEnabledRepairAssertion(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCDCWithConstraintsOnRepairIsDisabled() throws Exception
|
||||
{
|
||||
testCDCOnRepairEnabled(false, getRepairDisabledRepairAssertion(), true);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ public class SnapshotTest extends TestBaseImpl
|
|||
.start()))
|
||||
{
|
||||
cluster.schemaChange(withKeyspace("create table %s.tbl (id int primary key, x int)"));
|
||||
cluster.schemaChange(withKeyspace("create table %s.tblconstraints (id int primary key, x int check x > 100 and x < 200, v text check LENGTH(v) > 10)"));
|
||||
cluster.schemaChange(withKeyspace("CREATE OR REPLACE FUNCTION %s.fLog (input double) CALLED ON NULL INPUT RETURNS double LANGUAGE java AS 'return Double.valueOf(Math.log(input.doubleValue()));';"));
|
||||
cluster.schemaChange(withKeyspace("CREATE OR REPLACE FUNCTION %s.avgState ( state tuple<int,bigint>, val int ) CALLED ON NULL INPUT RETURNS tuple<int,bigint> LANGUAGE java AS \n" +
|
||||
" 'if (val !=null) { state.setInt(0, state.getInt(0)+1); state.setLong(1, state.getLong(1)+val.intValue()); } return state;'; "));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* 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.contraints;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class AlterTableWithTableConstraintValidationTest extends CqlConstraintValidationTester
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testCreateTableWithColumnNamedConstraintDescribeTableNonFunction() throws Throwable
|
||||
{
|
||||
String table = createTable("CREATE TABLE %s (pk int, ck1 int CHECK ck1 < 100, ck2 int, v int, PRIMARY KEY ((pk),ck1, ck2)) WITH CLUSTERING ORDER BY (ck1 ASC);");
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 DROP CHECK");
|
||||
|
||||
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int,\n" +
|
||||
" ck2 int,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableAddConstraint() throws Throwable
|
||||
{
|
||||
String table = createTable("CREATE TABLE %s (pk int, ck1 int, ck2 int, v int, PRIMARY KEY ((pk),ck1, ck2)) WITH CLUSTERING ORDER BY (ck1 ASC);");
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 CHECK ck1 < 100 AND ck1 > 10");
|
||||
|
||||
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int CHECK ck1 < 100 AND ck1 > 10,\n" +
|
||||
" ck2 int,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableAddMultipleConstraints() throws Throwable
|
||||
{
|
||||
String table = createTable("CREATE TABLE %s (pk int, ck1 int, ck2 int, v int, PRIMARY KEY ((pk),ck1, ck2)) WITH CLUSTERING ORDER BY (ck1 ASC);");
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 CHECK ck1 < 100");
|
||||
execute("ALTER TABLE %s ALTER ck2 CHECK ck2 > 10");
|
||||
|
||||
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int CHECK ck1 < 100,\n" +
|
||||
" ck2 int CHECK ck2 > 10,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableAddMultipleMixedConstraints() throws Throwable
|
||||
{
|
||||
String table = createTable("CREATE TABLE %s (pk int, ck1 int, ck2 text, v int, PRIMARY KEY ((pk), ck1, ck2)) WITH CLUSTERING ORDER BY (ck1 ASC);");
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 CHECK ck1 < 100");
|
||||
|
||||
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int CHECK ck1 < 100,\n" +
|
||||
" ck2 text,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck2 CHECK LENGTH(ck2) = 4");
|
||||
|
||||
tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int CHECK ck1 < 100,\n" +
|
||||
" ck2 text CHECK LENGTH(ck2) = 4,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableAddAndRemoveConstraint() throws Throwable
|
||||
{
|
||||
String table = createTable("CREATE TABLE %s (pk int, ck1 int, ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2)) WITH CLUSTERING ORDER BY (ck1 ASC);");
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 CHECK ck1 < 100");
|
||||
|
||||
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int CHECK ck1 < 100,\n" +
|
||||
" ck2 text,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement));
|
||||
|
||||
execute("ALTER TABLE %s ALTER ck1 DROP CHECK");
|
||||
|
||||
String tableCreateStatement2 = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
|
||||
" pk int,\n" +
|
||||
" ck1 int,\n" +
|
||||
" ck2 text,\n" +
|
||||
" v int,\n" +
|
||||
" PRIMARY KEY (pk, ck1, ck2)\n" +
|
||||
") WITH CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
|
||||
" AND " + tableParametersCql();
|
||||
|
||||
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + KEYSPACE + "." + table),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
table,
|
||||
tableCreateStatement2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterWithConstraintsAndCdcEnabled() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, ck1 int, ck2 int, PRIMARY KEY ((pk),ck1, ck2)) WITH cdc = true;");
|
||||
// It works
|
||||
execute("ALTER TABLE %s ALTER ck1 CHECK ck1 < 100");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterWithCdcAndPKConstraintsEnabled() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text CHECK length(pk) = 100, ck1 int, ck2 int, PRIMARY KEY ((pk), ck1, ck2));");
|
||||
// It works
|
||||
execute("ALTER TABLE %s WITH cdc = true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterWithCdcAndRegularConstraintsEnabled() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, ck1 int CHECK ck1 < 100, ck2 int, PRIMARY KEY (pk));");
|
||||
// It works
|
||||
execute("ALTER TABLE %s WITH cdc = true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterWithCdcAndClusteringConstraintsEnabled() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, ck1 int CHECK ck1 < 100, ck2 int, PRIMARY KEY ((pk), ck1, ck2));");
|
||||
// It works
|
||||
execute("ALTER TABLE %s WITH cdc = true");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.contraints;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraint.ConstraintType;
|
||||
|
||||
import static org.apache.cassandra.cql3.constraints.ColumnConstraint.ConstraintType.COMPOSED;
|
||||
import static org.apache.cassandra.cql3.constraints.ColumnConstraint.ConstraintType.FUNCTION;
|
||||
import static org.apache.cassandra.cql3.constraints.ColumnConstraint.ConstraintType.SCALAR;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ColumnConstraintsTest
|
||||
{
|
||||
private static final ConstraintType[] EXPECTED_VALUES = { COMPOSED, FUNCTION, SCALAR };
|
||||
|
||||
@Test
|
||||
public void testEnumCodesAndNames()
|
||||
{
|
||||
ConstraintType[] values = ConstraintType.values();
|
||||
|
||||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
assertEquals("Column Constraint Serializer mismatch in the enum " + values[i],
|
||||
EXPECTED_VALUES[i].name(), values[i].name());
|
||||
assertEquals("Column Constraint Serializer mismatch in the enum for value " + values[i],
|
||||
ConstraintType.getSerializer(EXPECTED_VALUES[i].ordinal()), ConstraintType.getSerializer(i));
|
||||
}
|
||||
|
||||
assertEquals("Column Constraint Serializer enum constants has changed. Update the test.",
|
||||
EXPECTED_VALUES.length, values.length);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.contraints;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.datastax.driver.core.ResultSet;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.CqlBuilder;
|
||||
import org.apache.cassandra.schema.CompactionParams;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
|
||||
public abstract class CqlConstraintValidationTester extends CQLTester
|
||||
{
|
||||
ResultSet executeDescribeNet(String cql) throws Throwable
|
||||
{
|
||||
return executeDescribeNet(null, cql);
|
||||
}
|
||||
|
||||
ResultSet executeDescribeNet(String useKs, String cql) throws Throwable
|
||||
{
|
||||
return executeNetWithPaging(getProtocolVersion(useKs), cql, useKs, 3);
|
||||
}
|
||||
|
||||
private ProtocolVersion getProtocolVersion(String useKs) throws Throwable
|
||||
{
|
||||
// We're using a trick here to distinguish driver sessions with a "USE keyspace" and without:
|
||||
// As different ProtocolVersions use different driver instances, we use different ProtocolVersions
|
||||
// for the with and without "USE keyspace" cases.
|
||||
|
||||
ProtocolVersion v = useKs != null ? ProtocolVersion.CURRENT : ProtocolVersion.V6;
|
||||
|
||||
if (useKs != null)
|
||||
executeNet(v, "USE " + useKs);
|
||||
return v;
|
||||
}
|
||||
|
||||
static String tableParametersCql()
|
||||
{
|
||||
return "additional_write_policy = '99p'\n" +
|
||||
" AND allow_auto_snapshot = true\n" +
|
||||
" AND bloom_filter_fp_chance = 0.01\n" +
|
||||
" AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" +
|
||||
" AND cdc = false\n" +
|
||||
" AND comment = ''\n" +
|
||||
" AND compaction = " + cqlQuoted(CompactionParams.DEFAULT.asMap()) + "\n" +
|
||||
" AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}\n" +
|
||||
" AND memtable = 'default'\n" +
|
||||
" AND crc_check_chance = 1.0\n" +
|
||||
" AND default_time_to_live = 0\n" +
|
||||
" AND extensions = {}\n" +
|
||||
" AND gc_grace_seconds = 864000\n" +
|
||||
" AND incremental_backups = true\n" +
|
||||
" AND max_index_interval = 2048\n" +
|
||||
" AND memtable_flush_period_in_ms = 0\n" +
|
||||
" AND min_index_interval = 128\n" +
|
||||
" AND read_repair = 'BLOCKING'\n" +
|
||||
" AND speculative_retry = '99p';";
|
||||
}
|
||||
|
||||
private static String cqlQuoted(Map<String, String> map)
|
||||
{
|
||||
return new CqlBuilder().append(map).toString();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -259,6 +259,18 @@ public class AbstractTypeTest
|
|||
return "test".equals(new File(src.getLocation().getPath()).name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isConstrainedTest()
|
||||
{
|
||||
qt().forAll(genBuilder().build()).checkAssert(type -> {
|
||||
if (type instanceof MapType || type instanceof TupleType || type instanceof AbstractCompositeType)
|
||||
assertThat(type.isConstrainable()).isEqualTo(false);
|
||||
else
|
||||
assertThat(type.isConstrainable()).isEqualTo(true);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsafeSharedSerializer()
|
||||
{
|
||||
|
|
@ -950,7 +962,7 @@ public class AbstractTypeTest
|
|||
assertThat(leftDecomposed.hasRemaining()).describedAs(typeRelDesc(".decompose", left, right)).isEqualTo(rightDecomposed.hasRemaining());
|
||||
|
||||
// serialization compatibility means that we can read a cell written using right's type serializer with left's type serializer;
|
||||
// this additinoally imposes the requirement for storing the buffer lenght in the serialized form if the value is of variable length
|
||||
// this additinoally imposes the requirement for storing the buffer length in the serialized form if the value is of variable length
|
||||
// as well as, either both types serialize into a single or multiple cells
|
||||
if (left.isSerializationCompatibleWith(right))
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue