mirror of https://github.com/apache/cassandra
Various fixes in constraint framework
- fix some edge cases for NOT_NULL - ability to specify constraints when altering a column - ensure constraint is specified on a column it is bound to - fix nullity check on map type - fix satistfiability check on function constraints patch by Stefan Miklosovic; reviewed by Bernardo Botella for CASSANDRA-20481
This commit is contained in:
parent
2e356413ee
commit
bb66561142
|
|
@ -1,4 +1,5 @@
|
|||
5.1
|
||||
* Various fixes in constraint framework (CASSANDRA-20481)
|
||||
* Add support in CAS for -= on numeric types, and fixed improper handling of empty bytes which lead to NPE (CASSANDRA-20477)
|
||||
* Do not fail to start a node with materialized views after they are turned off in config (CASSANDRA-20452)
|
||||
* Fix nodetool gcstats output, support human-readable units and more output formats (CASSANDRA-19022)
|
||||
|
|
|
|||
|
|
@ -988,14 +988,14 @@ alterTableStatement returns [AlterTableStatement.Raw stmt]
|
|||
|
||||
| K_ALTER ( K_IF K_EXISTS { $stmt.ifColumnExists(true); } )? id=cident
|
||||
( mask=columnMask { $stmt.mask(id, mask); }
|
||||
| constraints=columnConstraints { $stmt.constraint(id, constraints); }
|
||||
| K_DROP K_MASKED { $stmt.mask(id, null); }
|
||||
| K_DROP K_CHECK { $stmt.constraint(id, null); }
|
||||
| (constraints=columnConstraints) { $stmt.constraint(id, constraints); })
|
||||
| K_DROP K_CHECK { $stmt.constraint(id, null); })
|
||||
|
||||
| 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); }
|
||||
| ('(' id1=ident v1=comparatorType b1=isStaticColumn (m1=columnMask)? { $stmt.add(id1, v1, b1, m1); }
|
||||
( ',' idn=ident vn=comparatorType bn=isStaticColumn (mn=columnMask)? { $stmt.add(idn, vn, bn, mn); mn=null; } )* ')') )
|
||||
( id=ident v=comparatorType b=isStaticColumn (m=columnMask)? (c=columnConstraints)? { $stmt.add(id, v, b, m, c); }
|
||||
| ('(' id1=ident v1=comparatorType b1=isStaticColumn (m1=columnMask)? (c=columnConstraints)? { $stmt.add(id1, v1, b1, m1, c); }
|
||||
( ',' idn=ident vn=comparatorType bn=isStaticColumn (mn=columnMask)? (c=columnConstraints)? { $stmt.add(idn, vn, bn, mn, c); mn=null; c=null;} )* ')') )
|
||||
|
||||
| K_DROP ( K_IF K_EXISTS { $stmt.ifColumnExists(true); } )?
|
||||
( id=ident { $stmt.drop(id); }
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import java.util.TreeSet;
|
|||
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.cql3.functions.types.ParseUtils;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
|
|
@ -70,6 +72,8 @@ public abstract class AbstractFunctionSatisfiabilityChecker<CONSTRAINT_TYPE exte
|
|||
*/
|
||||
abstract Pair<List<CONSTRAINT_TYPE>, List<CONSTRAINT_TYPE>> categorizeConstraints(List<ColumnConstraint<?>> constraints, String functionName);
|
||||
|
||||
abstract AbstractType<?> returnType(ColumnMetadata columnMetadata);
|
||||
|
||||
private void checkSupportedOperators(List<CONSTRAINT_TYPE> allConstraints, String functionName)
|
||||
{
|
||||
for (CONSTRAINT_TYPE constraint : allConstraints)
|
||||
|
|
@ -147,11 +151,12 @@ public abstract class AbstractFunctionSatisfiabilityChecker<CONSTRAINT_TYPE exte
|
|||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer firstTermBuffer = columnMetadata.type.fromString(ParseUtils.unquote(firstTerm));
|
||||
ByteBuffer secondTermBuffer = columnMetadata.type.fromString(ParseUtils.unquote(secondTerm));
|
||||
AbstractType<?> returnType = returnType(columnMetadata);
|
||||
ByteBuffer firstTermBuffer = returnType.fromString(ParseUtils.unquote(firstTerm));
|
||||
ByteBuffer secondTermBuffer = returnType.fromString(ParseUtils.unquote(secondTerm));
|
||||
|
||||
boolean firstSatisfaction = firstRelation.isSatisfiedBy(columnMetadata.type, secondTermBuffer, firstTermBuffer);
|
||||
boolean secondSatisfaction = secondRelation.isSatisfiedBy(columnMetadata.type, firstTermBuffer, secondTermBuffer);
|
||||
boolean firstSatisfaction = firstRelation.isSatisfiedBy(returnType, secondTermBuffer, firstTermBuffer);
|
||||
boolean secondSatisfaction = secondRelation.isSatisfiedBy(returnType, firstTermBuffer, secondTermBuffer);
|
||||
|
||||
if (!firstSatisfaction || !secondSatisfaction)
|
||||
throw new InvalidConstraintDefinitionException(format("Constraints of %s are not satisfiable: %s %s %s, %s %s %s",
|
||||
|
|
@ -186,6 +191,14 @@ public abstract class AbstractFunctionSatisfiabilityChecker<CONSTRAINT_TYPE exte
|
|||
|
||||
return Pair.create(scalars, notEqualScalars);
|
||||
}
|
||||
|
||||
@Override
|
||||
AbstractType<?> returnType(ColumnMetadata metadata)
|
||||
{
|
||||
// function constraints will always have terms of int32 type
|
||||
// unlike scalar constraints where it will be a type of column
|
||||
return metadata.type;
|
||||
}
|
||||
};
|
||||
|
||||
public static final AbstractFunctionSatisfiabilityChecker<FunctionColumnConstraint> FUNCTION_SATISFIABILITY_CHECKER = new AbstractFunctionSatisfiabilityChecker<>()
|
||||
|
|
@ -215,5 +228,11 @@ public abstract class AbstractFunctionSatisfiabilityChecker<CONSTRAINT_TYPE exte
|
|||
|
||||
return Pair.create(funnctionColumnConstraints, notEqualConstraints);
|
||||
}
|
||||
|
||||
@Override
|
||||
AbstractType<?> returnType(ColumnMetadata columnMetadata)
|
||||
{
|
||||
return Int32Type.instance;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import org.apache.cassandra.cql3.constraints.ScalarColumnConstraint.ScalarColumn
|
|||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.tcm.serialization.MetadataSerializer;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
|
@ -116,8 +117,10 @@ public abstract class ColumnConstraint<T>
|
|||
*/
|
||||
public void evaluate(AbstractType<?> valueType, ByteBuffer columnValue) throws ConstraintViolationException
|
||||
{
|
||||
if (columnValue.capacity() == 0)
|
||||
if (columnValue == ByteBufferUtil.EMPTY_BYTE_BUFFER)
|
||||
throw new ConstraintViolationException("Column value does not satisfy value constraint for column '" + columnName + "' as it is null.");
|
||||
else if (valueType.isEmptyValueMeaningless() && columnValue.capacity() == 0)
|
||||
throw new ConstraintViolationException("Column value does not satisfy value constraint for column '" + columnName + "' as it is empty.");
|
||||
|
||||
internalEvaluate(valueType, columnValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import java.util.Objects;
|
|||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.CqlBuilder;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
|
|
@ -108,7 +109,7 @@ public class ColumnConstraints extends ColumnConstraint<ColumnConstraints>
|
|||
// Checks if there is at least one constraint that will perform checks
|
||||
public boolean hasRelevantConstraints()
|
||||
{
|
||||
for (ColumnConstraint c : constraints)
|
||||
for (ColumnConstraint<?> c : constraints)
|
||||
{
|
||||
if (c != ColumnConstraints.NO_OP)
|
||||
return true;
|
||||
|
|
@ -120,9 +121,12 @@ public class ColumnConstraints extends ColumnConstraint<ColumnConstraints>
|
|||
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 the table " + columnMetadata.ksName + '.' + columnMetadata.cfName + '.' +
|
||||
(columnMetadata.type.isCollection() ? " When using collections, constraints can be used only of frozen collections." : ""));
|
||||
}
|
||||
|
||||
// this will look at constraints as a whole,
|
||||
// checking if combinations of a particular constraint make sense (duplicities, satisfiability etc.).
|
||||
|
|
@ -207,10 +211,18 @@ public class ColumnConstraints extends ColumnConstraint<ColumnConstraints>
|
|||
this.constraints = Collections.emptyList();
|
||||
}
|
||||
|
||||
public ColumnConstraints prepare()
|
||||
public ColumnConstraints prepare(ColumnIdentifier column)
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return NO_OP;
|
||||
|
||||
for (ColumnConstraint<?> constraint : constraints)
|
||||
{
|
||||
if (constraint.columnName != null && !column.equals(constraint.columnName))
|
||||
throw new InvalidConstraintDefinitionException(format("Constraint %s was not specified on a column it operates on: %s but on: %s",
|
||||
constraint, column.toCQLString(), constraint.columnName));
|
||||
}
|
||||
|
||||
return new ColumnConstraints(constraints);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.ColumnIdentifier;
|
|||
import org.apache.cassandra.cql3.Operator;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import static org.apache.cassandra.cql3.Operator.EQ;
|
||||
import static org.apache.cassandra.cql3.Operator.GT;
|
||||
|
|
@ -55,8 +56,10 @@ public abstract class ConstraintFunction
|
|||
*/
|
||||
public void evaluate(AbstractType<?> valueType, Operator relationType, String term, ByteBuffer columnValue) throws ConstraintViolationException
|
||||
{
|
||||
if (columnValue.capacity() == 0)
|
||||
if (columnValue == ByteBufferUtil.EMPTY_BYTE_BUFFER)
|
||||
throw new ConstraintViolationException("Column value does not satisfy value constraint for column '" + columnName + "' as it is null.");
|
||||
else if (valueType.isEmptyValueMeaningless() && columnValue.capacity() == 0)
|
||||
throw new ConstraintViolationException("Column value does not satisfy value constraint for column '" + columnName + "' as it is empty.");
|
||||
|
||||
internalEvaluate(valueType, relationType, term, columnValue);
|
||||
}
|
||||
|
|
@ -88,10 +91,7 @@ public abstract class ConstraintFunction
|
|||
*
|
||||
* @return list of operators this function is allowed to have.
|
||||
*/
|
||||
public List<Operator> getSupportedOperators()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
public abstract List<Operator> getSupportedOperators();
|
||||
|
||||
/**
|
||||
* Tells what types of columns are supported by this constraint.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import org.apache.cassandra.utils.JsonUtils;
|
|||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class JsonConstraint extends ConstraintFunction
|
||||
public class JsonConstraint extends UnaryConstraintFunction
|
||||
{
|
||||
private static final List<AbstractType<?>> SUPPORTED_TYPES = List.of(UTF8Type.instance, AsciiType.instance);
|
||||
|
||||
|
|
@ -39,12 +39,7 @@ public class JsonConstraint extends ConstraintFunction
|
|||
|
||||
public JsonConstraint(ColumnIdentifier columnName)
|
||||
{
|
||||
this(columnName, FUNCTION_NAME);
|
||||
}
|
||||
|
||||
public JsonConstraint(ColumnIdentifier columnName, String name)
|
||||
{
|
||||
super(columnName, name);
|
||||
super(columnName, FUNCTION_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -28,18 +28,13 @@ import org.apache.cassandra.schema.ColumnMetadata;
|
|||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class NotNullConstraint extends ConstraintFunction
|
||||
public class NotNullConstraint extends UnaryConstraintFunction
|
||||
{
|
||||
public static final String FUNCTION_NAME = "NOT_NULL";
|
||||
|
||||
public NotNullConstraint(ColumnIdentifier columnName)
|
||||
{
|
||||
this(columnName, FUNCTION_NAME);
|
||||
}
|
||||
|
||||
public NotNullConstraint(ColumnIdentifier columnName, String name)
|
||||
{
|
||||
super(columnName, name);
|
||||
super(columnName, FUNCTION_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.cql3.constraints;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.Operator;
|
||||
|
||||
public abstract class UnaryConstraintFunction extends ConstraintFunction
|
||||
{
|
||||
public UnaryConstraintFunction(ColumnIdentifier columnName, String name)
|
||||
{
|
||||
super(columnName, name);
|
||||
}
|
||||
|
||||
public List<Operator> getSupportedOperators()
|
||||
{
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
|
@ -371,9 +371,7 @@ public class UpdateStatement extends ModificationStatement
|
|||
|
||||
public static void evaluateConstraint(ColumnMetadata columnMetadata, ByteBuffer cellData)
|
||||
{
|
||||
for (ColumnConstraint constraint : columnMetadata.getColumnConstraints().getConstraints())
|
||||
{
|
||||
for (ColumnConstraint<?> constraint : columnMetadata.getColumnConstraints().getConstraints())
|
||||
constraint.evaluate(columnMetadata.type, cellData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,13 +257,16 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
private final boolean isStatic;
|
||||
@Nullable
|
||||
private final ColumnMask.Raw mask;
|
||||
@Nullable
|
||||
private final ColumnConstraints.Raw constraints;
|
||||
|
||||
Column(ColumnIdentifier name, CQL3Type.Raw type, boolean isStatic, @Nullable ColumnMask.Raw mask)
|
||||
Column(ColumnIdentifier name, CQL3Type.Raw type, boolean isStatic, @Nullable ColumnMask.Raw mask, @Nullable ColumnConstraints.Raw constraints)
|
||||
{
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.isStatic = isStatic;
|
||||
this.mask = mask;
|
||||
this.constraints = constraints;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,6 +314,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
AbstractType<?> type = column.type.prepare(keyspaceName, keyspace.types).getType();
|
||||
boolean isStatic = column.isStatic;
|
||||
ColumnMask mask = column.mask == null ? null : column.mask.prepare(keyspaceName, tableName, name, type, keyspace.userFunctions);
|
||||
ColumnConstraints columnConstraints = column.constraints == null ? ColumnConstraints.NO_OP : column.constraints.prepare(name);
|
||||
|
||||
if (null != tableBuilder.getColumn(name)) {
|
||||
if (!ifColumnNotExists)
|
||||
|
|
@ -361,9 +365,9 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
}
|
||||
|
||||
if (isStatic)
|
||||
tableBuilder.addStaticColumn(name, type, mask);
|
||||
tableBuilder.addStaticColumn(name, type, mask, columnConstraints);
|
||||
else
|
||||
tableBuilder.addRegularColumn(name, type, mask);
|
||||
tableBuilder.addRegularColumn(name, type, mask, columnConstraints);
|
||||
|
||||
if (!isStatic)
|
||||
{
|
||||
|
|
@ -372,7 +376,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
if (view.includeAllColumns)
|
||||
{
|
||||
ColumnMetadata viewColumn = ColumnMetadata.regularColumn(view.metadata, name.bytes, type)
|
||||
.withNewMask(mask);
|
||||
.withNewMask(mask)
|
||||
.withNewColumnConstraints(columnConstraints);
|
||||
viewsBuilder.put(viewsBuilder.get(view.name()).withAddedRegularColumn(viewColumn));
|
||||
}
|
||||
}
|
||||
|
|
@ -732,7 +737,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
if (column != null)
|
||||
{
|
||||
ColumnConstraints oldConstraints = column.getColumnConstraints();
|
||||
ColumnConstraints newConstraints = constraints == null ? ColumnConstraints.NO_OP : constraints.prepare();
|
||||
ColumnConstraints newConstraints = constraints == null ? ColumnConstraints.NO_OP : constraints.prepare(columnName);
|
||||
if (Objects.equals(oldConstraints, newConstraints))
|
||||
return keyspace;
|
||||
newConstraints.validate(column);
|
||||
|
|
@ -837,10 +842,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
rawMask = mask;
|
||||
}
|
||||
|
||||
public void add(ColumnIdentifier name, CQL3Type.Raw type, boolean isStatic, @Nullable ColumnMask.Raw mask)
|
||||
public void add(ColumnIdentifier name, CQL3Type.Raw type, boolean isStatic, @Nullable ColumnMask.Raw mask, @Nullable ColumnConstraints.Raw constraints)
|
||||
{
|
||||
kind = Kind.ADD_COLUMNS;
|
||||
addedColumns.add(new AddColumns.Column(name, type, isStatic, mask));
|
||||
addedColumns.add(new AddColumns.Column(name, type, isStatic, mask, constraints));
|
||||
}
|
||||
|
||||
public void drop(ColumnIdentifier name)
|
||||
|
|
|
|||
|
|
@ -578,7 +578,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
if (null == constraints)
|
||||
columnConstraints.put(column, ColumnConstraints.NO_OP);
|
||||
else
|
||||
columnConstraints.put(column, constraints.prepare());
|
||||
columnConstraints.put(column, constraints.prepare(column));
|
||||
}
|
||||
|
||||
public void setCompactStorage()
|
||||
|
|
|
|||
|
|
@ -211,20 +211,12 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
|
|||
|
||||
public void checkConstraints(ByteBuffer bytes, ColumnConstraints constraints) throws ConstraintViolationException
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return;
|
||||
|
||||
T value = getSerializer().deserialize(bytes);
|
||||
constraints.evaluate(this, bytes);
|
||||
checkConstraints(bytes, constraints.getConstraints());
|
||||
}
|
||||
|
||||
public void checkConstraints(ByteBuffer bytes, List<ColumnConstraint> constraints) throws ConstraintViolationException
|
||||
public void checkConstraints(ByteBuffer bytes, List<ColumnConstraint<?>> constraints) throws ConstraintViolationException
|
||||
{
|
||||
if (constraints.isEmpty())
|
||||
return;
|
||||
|
||||
T value = getSerializer().deserialize(bytes);
|
||||
for (ColumnConstraint constraint : constraints)
|
||||
for (ColumnConstraint<?> constraint : constraints)
|
||||
constraint.evaluate(this, bytes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -171,6 +171,12 @@ public abstract class CollectionType<T> extends MultiElementType<T>
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return isFrozenCollection();
|
||||
}
|
||||
|
||||
public ByteBuffer serializeForNativeProtocol(Iterator<Cell<?>> cells)
|
||||
{
|
||||
assert isMultiCell();
|
||||
|
|
|
|||
|
|
@ -456,10 +456,4 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
|
|||
|
||||
return getSerializer().getSerializedValue(((Cell<?>) columnData).buffer(), keyOrIndex, getValuesType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstrainable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ public class TableMetadata implements SchemaElement
|
|||
// 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<ColumnConstraint<?>> partitionKeyConstraints;
|
||||
public final List<ColumnMetadata> columnsWithConstraints;
|
||||
public final List<ColumnMetadata> notNullColumns;
|
||||
|
||||
|
|
@ -248,7 +248,7 @@ public class TableMetadata implements SchemaElement
|
|||
else
|
||||
ref = TableMetadataRef.withInitialReference(new TableMetadataRef(Schema.instance, keyspace, name, id), this);
|
||||
|
||||
List<ColumnConstraint> pkConstraints = new ArrayList<>(this.partitionKeyColumns.size());
|
||||
List<ColumnConstraint<?>> pkConstraints = new ArrayList<>(this.partitionKeyColumns.size());
|
||||
for (ColumnMetadata column : this.partitionKeyColumns)
|
||||
{
|
||||
if (column.hasConstraint())
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ import org.junit.Test;
|
|||
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
||||
|
||||
public class AlterTableWithTableConstraintValidationTest extends CqlConstraintValidationTester
|
||||
{
|
||||
|
||||
@Test
|
||||
public void testCreateTableWithColumnNamedConstraintDescribeTableNonFunction() throws Throwable
|
||||
{
|
||||
|
|
@ -239,4 +237,44 @@ public class AlterTableWithTableConstraintValidationTest extends CqlConstraintVa
|
|||
String expectedErrorMessage = "Column 'foo' doesn't exist";
|
||||
assertInvalidThrowMessage(expectedErrorMessage, InvalidRequestException.class, "ALTER TABLE %s ALTER foo CHECK foo < 100");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAlterExistingColumnWithCheckOnNonExistingColumn() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk int, ck1 text, ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2));");
|
||||
assertInvalidThrowMessage("Constraint ck3 < 100 was not specified on a column it operates on: ck1 but on: ck3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ALTER ck1 CHECK ck3 < 100");
|
||||
assertInvalidThrowMessage("Constraint NOT_NULL(ck3) was not specified on a column it operates on: ck1 but on: ck3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ALTER ck1 CHECK NOT_NULL(ck3)");
|
||||
assertInvalidThrowMessage("Constraint LENGTH(ck3) > 10 was not specified on a column it operates on: ck1 but on: ck3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ALTER ck1 CHECK LENGTH(ck3) > 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddNewColumnWithCheckOnNonExistingColumn() throws Throwable
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk int, ck1 text, ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2));");
|
||||
|
||||
assertInvalidThrowMessage("Constraint v3 < 100 was not specified on a column it operates on: v2 but on: v3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ADD v2 int CHECK v3 < 100");
|
||||
|
||||
assertInvalidThrowMessage("Constraint NOT_NULL(v3) was not specified on a column it operates on: v2 but on: v3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ADD v2 int CHECK NOT_NULL(v3)");
|
||||
|
||||
assertInvalidThrowMessage("Constraint LENGTH(v3) > 10 was not specified on a column it operates on: v2 but on: v3",
|
||||
InvalidRequestException.class,
|
||||
"ALTER TABLE %s ADD v2 int CHECK LENGTH(v3) > 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddColumnWithCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, col1 int, primary key (pk));");
|
||||
execute("ALTER TABLE %s ADD col2 int CHECK col2 > 0");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,13 +71,13 @@ public class ConstraintsSatisfiabilityTest
|
|||
if (op1 == NEQ)
|
||||
{
|
||||
// a_column != 0 and a_column != 10 -> valid
|
||||
check(op1, 0, op2, 100, quadFunction, null, columnMetadata);
|
||||
check(op1, 50, op2, 100, quadFunction, null, columnMetadata);
|
||||
// does not make sense to check twice
|
||||
// check a_column != 0 and a_column != 0
|
||||
check(op1, 0, op2, 0, quadFunction, "There are duplicate constraint definitions on column", columnMetadata);
|
||||
}
|
||||
else
|
||||
check(op1, 0, op2, 100, quadFunction, "There are duplicate constraint definitions on column", columnMetadata);
|
||||
check(op1, 50, op2, 100, quadFunction, "There are duplicate constraint definitions on column", columnMetadata);
|
||||
}
|
||||
else if ((op1 == GT && op2 == GTE) ||
|
||||
(op1 == GTE && op2 == GT) ||
|
||||
|
|
@ -85,18 +85,25 @@ public class ConstraintsSatisfiabilityTest
|
|||
(op1 == LTE && op2 == LT) ||
|
||||
(op1 == EQ || op2 == EQ))
|
||||
{
|
||||
check(op1, 0, op2, 100, quadFunction, "not supported", columnMetadata);
|
||||
check(op1, 50, op2, 100, quadFunction, "not supported", columnMetadata);
|
||||
}
|
||||
else if ((op1 == LTE && op2 == GT) ||
|
||||
(op1 == LT && op2 == GT) ||
|
||||
(op1 == LTE && op2 == GTE) ||
|
||||
(op1 == LT && op2 == GTE))
|
||||
{
|
||||
check(op1, 0, op2, 100, quadFunction, "are not satisfiable", columnMetadata);
|
||||
check(op1, 50, op2, 100, quadFunction, "are not satisfiable", columnMetadata);
|
||||
}
|
||||
else if ((op1 == GT && op2 == LTE) ||
|
||||
(op1 == GT && op2 == LT) ||
|
||||
(op1 == GTE && op2 == LTE) ||
|
||||
(op1 == GTE && op2 == LT))
|
||||
{
|
||||
check(op1, 50, op2, 100, quadFunction, null, columnMetadata);
|
||||
}
|
||||
else if (!(op1 == NEQ || op2 == NEQ))
|
||||
{
|
||||
check(op1, 0, op2, 100, quadFunction, null, columnMetadata);
|
||||
check(op1, 50, op2, 100, quadFunction, null, columnMetadata);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionException;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.utils.Generators;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static accord.utils.Property.qt;
|
||||
|
|
@ -1438,4 +1440,20 @@ public class CreateTableWithColumnCqlConstraintValidationTest extends CqlConstra
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableAddConstraintWithCheckOnNonExistingColumn() throws Throwable
|
||||
{
|
||||
assertThatThrownBy(() -> createTable("CREATE TABLE %s (pk int, ck1 text CHECK NOT_NULL(ck3), ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2));"))
|
||||
.hasRootCauseMessage("Constraint NOT_NULL(ck3) was not specified on a column it operates on: ck1 but on: ck3")
|
||||
.rootCause().isInstanceOf(InvalidConstraintDefinitionException.class);
|
||||
|
||||
assertThatThrownBy(() -> createTable("CREATE TABLE %s (pk int, ck1 int CHECK ck3 > 5, ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2));"))
|
||||
.hasRootCauseMessage("Constraint ck3 > 5 was not specified on a column it operates on: ck1 but on: ck3")
|
||||
.rootCause().isInstanceOf(InvalidConstraintDefinitionException.class);
|
||||
|
||||
assertThatThrownBy(() -> createTable("CREATE TABLE %s (pk int, ck1 text CHECK LENGTH(ck3) > 10, ck2 text, v int, PRIMARY KEY ((pk),ck1, ck2));"))
|
||||
.hasRootCauseMessage("Constraint LENGTH(ck3) > 10 was not specified on a column it operates on: ck1 but on: ck3")
|
||||
.rootCause().isInstanceOf(InvalidConstraintDefinitionException.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class JsonConstraintTest
|
|||
run("{}");
|
||||
run("{\"a\": 5, \"b\": \"1\", \"c\": [1,2,3]}");
|
||||
run("nonsense", "Value for column 'a_column' violated JSON constraint as it is not a valid JSON.");
|
||||
run("", "Column value does not satisfy value constraint for column 'a_column' as it is null.");
|
||||
run("", "Value for column 'a_column' violated JSON constraint as it is not a valid JSON.");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.cassandra.constraints;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.cql3.constraints.ColumnConstraints;
|
||||
import org.apache.cassandra.cql3.constraints.ConstraintViolationException;
|
||||
import org.apache.cassandra.cql3.constraints.FunctionColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionException;
|
||||
import org.apache.cassandra.cql3.constraints.NotNullConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.ScalarColumnConstraint;
|
||||
import org.apache.cassandra.cql3.constraints.UnaryFunctionColumnConstraint;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
import org.apache.cassandra.db.marshal.ListType;
|
||||
import org.apache.cassandra.db.marshal.MapType;
|
||||
import org.apache.cassandra.db.marshal.SetType;
|
||||
import org.apache.cassandra.db.marshal.UTF8Type;
|
||||
import org.apache.cassandra.db.marshal.UUIDType;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
|
||||
import static java.util.List.of;
|
||||
import static org.apache.cassandra.cql3.Operator.GT;
|
||||
import static org.apache.cassandra.schema.ColumnMetadata.Kind.REGULAR;
|
||||
import static org.apache.cassandra.utils.ByteBufferUtil.EMPTY_BYTE_BUFFER;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* TODO - UDTs are not supported yet in constraints as such
|
||||
*/
|
||||
public class NotNullConstraintTest
|
||||
{
|
||||
private static final ColumnIdentifier columnIdentifier = new ColumnIdentifier("a_column", false);
|
||||
private static final ColumnConstraints unaryConstraint = new ColumnConstraints(of(new UnaryFunctionColumnConstraint.Raw(new ColumnIdentifier(NotNullConstraint.FUNCTION_NAME, false), columnIdentifier).prepare()));
|
||||
private static final ColumnConstraints scalarConstraint = new ColumnConstraints(of(new ScalarColumnConstraint.Raw(columnIdentifier, GT, "5").prepare()));
|
||||
private static final ColumnConstraints functionConstraint = new ColumnConstraints(of(new FunctionColumnConstraint.Raw(new ColumnIdentifier("LENGTH", false), columnIdentifier, GT, "5").prepare()));
|
||||
|
||||
@Test
|
||||
public void testNotNullConstraintValidation()
|
||||
{
|
||||
// unary
|
||||
unaryConstraint.validate(getColumnOfType(UTF8Type.instance));
|
||||
assertThatThrownBy(() -> unaryConstraint.evaluate(UTF8Type.instance, EMPTY_BYTE_BUFFER))
|
||||
.hasMessage("Column value does not satisfy value constraint for column 'a_column' as it is null.")
|
||||
.isInstanceOf(ConstraintViolationException.class);
|
||||
|
||||
// not null / empty
|
||||
unaryConstraint.evaluate(UTF8Type.instance, UTF8Type.instance.fromString("a value"));
|
||||
|
||||
// scalar
|
||||
scalarConstraint.validate(getColumnOfType(Int32Type.instance));
|
||||
assertThatThrownBy(() -> scalarConstraint.evaluate(Int32Type.instance, EMPTY_BYTE_BUFFER))
|
||||
.hasMessage("Column value does not satisfy value constraint for column 'a_column' as it is null.")
|
||||
.isInstanceOf(ConstraintViolationException.class);
|
||||
|
||||
// function, e.g. length
|
||||
functionConstraint.validate(getColumnOfType(UTF8Type.instance));
|
||||
assertThatThrownBy(() -> functionConstraint.evaluate(UTF8Type.instance, EMPTY_BYTE_BUFFER))
|
||||
.hasMessage("Column value does not satisfy value constraint for column 'a_column' as it is null.")
|
||||
.isInstanceOf(ConstraintViolationException.class);
|
||||
|
||||
// empty string is not _null_ string so this passes
|
||||
unaryConstraint.evaluate(UTF8Type.instance, UTF8Type.instance.fromString(""));
|
||||
|
||||
// test a type for which empty value is meaningless
|
||||
|
||||
assertThatThrownBy(() -> unaryConstraint.evaluate(UUIDType.instance, ByteBuffer.allocate(0)))
|
||||
.hasMessage("Column value does not satisfy value constraint for column 'a_column' as it is empty.")
|
||||
.isInstanceOf(ConstraintViolationException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCollections()
|
||||
{
|
||||
checkList(false);
|
||||
checkSet(false);
|
||||
checkMap(false);
|
||||
|
||||
checkList(true);
|
||||
checkSet(true);
|
||||
checkMap(true);
|
||||
}
|
||||
|
||||
private static ColumnMetadata getColumnOfType(AbstractType<?> type)
|
||||
{
|
||||
return new ColumnMetadata("a", "b", columnIdentifier, type, -1, REGULAR, null);
|
||||
}
|
||||
|
||||
private void checkList(boolean frozen)
|
||||
{
|
||||
if (frozen)
|
||||
{
|
||||
ListType<Integer> listType = ListType.getInstance(Int32Type.instance, false);
|
||||
ByteBuffer payload = listType.getSerializer().serialize(List.of(1, 2, 3));
|
||||
checkFrozenCollection(listType, payload);
|
||||
}
|
||||
else
|
||||
checkUnfrozenCollection(ListType.getInstance(Int32Type.instance, true));
|
||||
}
|
||||
|
||||
private void checkMap(boolean frozen)
|
||||
{
|
||||
if (frozen)
|
||||
{
|
||||
MapType<Integer, Integer> mapType = MapType.getInstance(Int32Type.instance, Int32Type.instance, false);
|
||||
ByteBuffer payload = mapType.getSerializer().serialize(Map.of(1, 1, 2, 2, 3, 3));
|
||||
checkFrozenCollection(mapType, payload);
|
||||
}
|
||||
else
|
||||
checkUnfrozenCollection(MapType.getInstance(Int32Type.instance, Int32Type.instance, true));
|
||||
}
|
||||
|
||||
private void checkSet(boolean frozen)
|
||||
{
|
||||
if (frozen)
|
||||
{
|
||||
SetType<Integer> setType = SetType.getInstance(Int32Type.instance, false);
|
||||
ByteBuffer payload = setType.getSerializer().serialize(Set.of(1, 2, 3));
|
||||
checkFrozenCollection(setType, payload);
|
||||
}
|
||||
else
|
||||
checkUnfrozenCollection(SetType.getInstance(Int32Type.instance, true));
|
||||
}
|
||||
|
||||
private void checkFrozenCollection(AbstractType<?> type, ByteBuffer payload)
|
||||
{
|
||||
unaryConstraint.validate(getColumnOfType(type));
|
||||
unaryConstraint.evaluate(type, payload);
|
||||
|
||||
assertThatThrownBy(() -> unaryConstraint.evaluate(type, EMPTY_BYTE_BUFFER))
|
||||
.hasMessage("Column value does not satisfy value constraint for column 'a_column' as it is null.")
|
||||
.isInstanceOf(ConstraintViolationException.class);
|
||||
}
|
||||
|
||||
private void checkUnfrozenCollection(AbstractType<?> type)
|
||||
{
|
||||
assertThatThrownBy(() -> unaryConstraint.validate(getColumnOfType(type)))
|
||||
.hasMessageContaining("Constraint cannot be defined on the column")
|
||||
.hasMessageContaining("When using collections, constraints can be used only of frozen collections")
|
||||
.isInstanceOf(InvalidConstraintDefinitionException.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.schema.CompactionParams;
|
||||
|
||||
/**
|
||||
* Test various "extensions" to a column spec when altering / creating a table
|
||||
*/
|
||||
public class ColumnSpecificationTest extends CQLTester
|
||||
{
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
DatabaseDescriptor.setDynamicDataMaskingEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTableWithColumnHavingMaskBeforeCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1);");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAlterColumnWithMaskAndCheckStandalone()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, name text, primary key (pk));");
|
||||
execute("ALTER TABLE %s ALTER name MASKED WITH system.mask_default()");
|
||||
execute("ALTER TABLE %s ALTER name CHECK NOT_NULL(name) AND LENGTH(name) > 1;");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAlterColumnWithMask()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, name text, primary key (pk));");
|
||||
execute("ALTER TABLE %s ALTER name MASKED WITH system.mask_default()");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default()");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAlterColumnWithCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, name text, primary key (pk));");
|
||||
execute("ALTER TABLE %s ALTER name CHECK NOT_NULL(name) AND LENGTH(name) > 1;");
|
||||
verifyColumnSpec("name text CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingCheckToColumnWithMask()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text MASKED WITH system.mask_default());");
|
||||
execute("ALTER TABLE %s ALTER name CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingMaskToColumnWithCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text CHECK NOT_NULL(name) AND LENGTH(name) > 1);");
|
||||
execute("ALTER TABLE %s ALTER name MASKED WITH system.mask_default()");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDroppingCheckKeepsMask()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1);");
|
||||
execute("ALTER TABLE %s ALTER name DROP CHECK");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default()");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void droppingMaskKeepsCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1);");
|
||||
execute("ALTER TABLE %s ALTER name DROP MASKED");
|
||||
verifyColumnSpec("name text CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddColumnWithCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key);");
|
||||
execute("ALTER TABLE %s ADD name text CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
verifyColumnSpec("name text CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddColumnWithMask()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key);");
|
||||
execute("ALTER TABLE %s ADD name text MASKED WITH system.mask_default()");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default()");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddColumnWithMaskAndCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key);");
|
||||
execute("ALTER TABLE %s ADD name text MASKED WITH system.mask_default() CHECK NOT_NULL(name)");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlterTableAddColumnWithMaskAndMultipleChecks()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key);");
|
||||
execute("ALTER TABLE %s ADD name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO - investigate if it is possible to specify checks before mask when creating a table
|
||||
*/
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testFailingCreateTableWithColumnHavingMaskAfterCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text primary key, name text CHECK NOT_NULL(name) AND LENGTH(name) > 1 MASKED WITH system.mask_default());");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO - investigate if it is possible to specify both check and mask, check being first
|
||||
*/
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testFailingAlterTableAlterColumnWithCheckAndMask()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, name text, primary key (pk));");
|
||||
execute("ALTER TABLE %s ALTER name CHECK NOT_NULL(name) AND LENGTH(name) > 1 MASKED WITH system.mask_default();");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO - investigate if it is possible to specify both check and mask, mask being first
|
||||
*/
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testFailingAlterTableAlterColumnWithMaskAndCheck()
|
||||
{
|
||||
createTable("CREATE TABLE %s (pk text, name text, primary key (pk));");
|
||||
execute("ALTER TABLE %s ALTER name MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
verifyColumnSpec("name text MASKED WITH system.mask_default() CHECK NOT_NULL(name) AND LENGTH(name) > 1");
|
||||
}
|
||||
|
||||
private void verifyColumnSpec(String modifiedColumn)
|
||||
{
|
||||
assertRowsContains(executeNetWithoutPaging("DESCRIBE TABLE " + KEYSPACE + '.' + currentTable()),
|
||||
row(KEYSPACE,
|
||||
"table",
|
||||
currentTable(),
|
||||
"CREATE TABLE " + KEYSPACE + '.' + currentTable() + " (\n" +
|
||||
" pk text PRIMARY KEY,\n" +
|
||||
" " + modifiedColumn + '\n' +
|
||||
") WITH " + tableParametersCql()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -263,10 +263,15 @@ public class AbstractTypeTest
|
|||
public void isConstrainedTest()
|
||||
{
|
||||
qt().forAll(genBuilder().build()).checkAssert(type -> {
|
||||
if (type instanceof MapType || type instanceof TupleType || type instanceof AbstractCompositeType)
|
||||
if (type instanceof TupleType || type instanceof AbstractCompositeType)
|
||||
assertThat(type.isConstrainable()).isEqualTo(false);
|
||||
else
|
||||
assertThat(type.isConstrainable()).isEqualTo(true);
|
||||
{
|
||||
if (type.isCollection() && !type.isFrozenCollection())
|
||||
assertThat(type.isConstrainable()).isEqualTo(false);
|
||||
else
|
||||
assertThat(type.isConstrainable()).isEqualTo(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue