Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions doc/modules/cassandra/pages/developing/cql/constraints.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ For example, we can create a constraint that checks that name can't be longer th

----
CREATE TABLE ks.tb (
name text CHECK LENGTH(name) < 256
name text CHECK LENGTH() < 256
...,
);
----

Altering that constraint can be done with:

----
ALTER TABLE ks.tb ALTER name LENGTH(name) < 512;
ALTER TABLE ks.tb ALTER name LENGTH() < 512;
----

Finally, the constraint can be removed:
Expand All @@ -130,7 +130,7 @@ For example, we can create a constraint that checks that name can't be bigger th

----
CREATE TABLE ks.tb (
name text CHECK OCTET_LENGTH(name) < 2
name text CHECK OCTET_LENGTH() < 2
...,
);
----
Expand Down Expand Up @@ -158,8 +158,8 @@ For example, let's have this table:
CREATE TABLE ks.tb (
id int,
cl int,
col1 int CHECK NOT_NULL(col1),
col2 int CHECK NOT_NULL(col2),
col1 int CHECK NOT NULL,
col2 int CHECK NOT NULL,
PRIMARY KEY (id, cl)
);
----
Expand Down Expand Up @@ -205,7 +205,7 @@ Defines a constraint which checks if a column contains a string which is a valid
----
CREATE TABLE ks.tb (
id int primary key,
val text CHECK JSON(val)
val text CHECK JSON()
);

-- valid JSON string
Expand All @@ -230,7 +230,7 @@ Defines a constraint which checks text-like values againt a regular expression.
----
CREATE TABLE ks.tb (
id int primary key,
value CHECK REGEXP(value) = 'a.*b'
value CHECK REGEXP() = 'a.*b'
)
----

Expand All @@ -243,7 +243,7 @@ cassandra@cqlsh> INSERT INTO ks.tb (id , value ) VALUES ( 1, 'aaaaa');
Negation can be also used:

----
ALTER TABLE ks.tb ALTER value CHECK REGEXP(value) != 'a.*b';
ALTER TABLE ks.tb ALTER value CHECK REGEXP() != 'a.*b';
----

which would logically invert the condition:
Expand Down
37 changes: 32 additions & 5 deletions src/antlr/Parser.g
Original file line number Diff line number Diff line change
Expand Up @@ -967,8 +967,8 @@ tableDefinition[CreateTableStatement.Raw stmt]
;

tableColumns[CreateTableStatement.Raw stmt]
@init { boolean isStatic = false; }
: k=ident v=comparatorType (K_STATIC { isStatic = true; })? (mask=columnMask)? (constraints=columnConstraints)? { $stmt.addColumn(k, v, isStatic, mask, constraints); }
@init { boolean isStatic = false; boolean isNotNull = false; }
: k=ident v=comparatorType (K_STATIC { isStatic = true; })? (K_NOT K_NULL { isNotNull = true; })? (mask=columnMask)? (constraints=columnConstraints)? { $stmt.addColumn(k, v, isStatic, isNotNull, mask, constraints); }
(K_PRIMARY K_KEY { $stmt.setPartitionKeyColumn(k); })?
| K_PRIMARY K_KEY '(' tablePartitionKey[stmt] (',' c=ident { $stmt.markClusteringColumn(c); } )* ')'
;
Expand All @@ -982,9 +982,30 @@ columnConstraints returns [ColumnConstraints.Raw constraints]
;

columnConstraint returns [ColumnConstraint columnConstraint]
: funcName=ident '(' k=ident ')' op=relationType t=value { $columnConstraint = new FunctionColumnConstraint.Raw(funcName, k, op, t.getText()).prepare(); }
| funcName=ident '(' k=ident ')' { $columnConstraint = new UnaryFunctionColumnConstraint.Raw(funcName, k).prepare(); }
| k=ident op=relationType t=value { $columnConstraint = new ScalarColumnConstraint.Raw(k, op, t.getText()).prepare(); }
@init { List<String> arguments = new ArrayList<>(); }
: K_NOT K_NULL
{
$columnConstraint = new UnaryFunctionColumnConstraint.Raw("NOT_NULL").prepare();
}
| funcName=ident columnConstraintsArguments[arguments] (op=relationType t=value)?
{
if (op != null && t != null)
{
$columnConstraint = new FunctionColumnConstraint.Raw(funcName, arguments, op, t.getText()).prepare();
}
else
{
$columnConstraint = new UnaryFunctionColumnConstraint.Raw(funcName, arguments).prepare();
}
}
| k=ident op=relationType t=value
{
$columnConstraint = new ScalarColumnConstraint.Raw(k, op, t.getText()).prepare();
}
| funcName=ident
{
$columnConstraint = new UnaryFunctionColumnConstraint.Raw(funcName).prepare();
}
;

columnMask returns [ColumnMask.Raw mask]
Expand All @@ -997,6 +1018,12 @@ columnMaskArguments[List<Term.Raw> arguments]
: '(' ')' | '(' c=term { arguments.add(c); } (',' cn=term { arguments.add(cn); })* ')'
;

columnConstraintsArguments[List<String> arguments]
: '(' ')'
| '(' c=term { try { arguments.add(c.toString()); } catch (Throwable t) { throw new SyntaxException("Constraint function parameters need to be strings."); }; } (',' cn=term { try { arguments.add(cn.toString()); } catch (Throwable t) { throw new SyntaxException("Constraint function parameters need to be strings."); }; })* ')'
| '(' ci=ident { throw new SyntaxException("Constraint function parameters need to be strings."); } (',' cni=ident)* ')'
;

tablePartitionKey[CreateTableStatement.Raw stmt]
@init {List<ColumnIdentifier> l = new ArrayList<ColumnIdentifier>();}
@after{ $stmt.setPartitionKeyColumns(l); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import java.util.List;

import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.utils.LocalizeString;
Expand All @@ -30,9 +29,8 @@ public abstract class AbstractFunctionConstraint<T> extends ColumnConstraint<T>
protected final Operator relationType;
protected final String term;

public AbstractFunctionConstraint(ColumnIdentifier columnName, Operator relationType, String term)
public AbstractFunctionConstraint(Operator relationType, String term)
{
super(columnName);
this.relationType = relationType;
this.term = term;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
*/
public abstract class ColumnConstraint<T>
{
protected final ColumnIdentifier columnName;
protected ColumnIdentifier columnName;

public ColumnConstraint(ColumnIdentifier columnName)
public void setColumnName(ColumnIdentifier columnName)
{
this.columnName = columnName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,17 @@ public class ColumnConstraints extends ColumnConstraint<ColumnConstraints>

public ColumnConstraints(List<ColumnConstraint<?>> constraints)
{
super(null);
this.constraints = constraints;
}

@Override
public void setColumnName(ColumnIdentifier columnName)
{
this.columnName = columnName;
for (ColumnConstraint<?> constraint : constraints)
constraint.setColumnName(columnName);
}

@Override
public String name()
{
Expand Down Expand Up @@ -117,6 +124,17 @@ public boolean hasRelevantConstraints()
return false;
}

public boolean containsNotNullConstraint()
{
for (ColumnConstraint<?> c : constraints)
{
if (c.toString().equals(NotNullConstraint.CQL_FUNCTION_NAME))
return true;
}

return false;
}

@Override
public void validate(ColumnMetadata columnMetadata) throws InvalidConstraintDefinitionException
{
Expand Down Expand Up @@ -218,18 +236,26 @@ public ColumnConstraints prepare(ColumnIdentifier column)

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));
// We only check scalar constraints column name, as the rest of the constraints
// imply the name from the column they are defined at
if (constraint.getConstraintType() == ConstraintType.SCALAR)
{
if (!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);
ColumnConstraints columnConstraints = new ColumnConstraints(constraints);
columnConstraints.setColumnName(column);
return columnConstraints;
}
}

public static class Serializer implements MetadataSerializer<ColumnConstraints>
{

@Override
public void serialize(ColumnConstraints columnConstraint, DataOutputPlus out, Version version) throws IOException
{
Expand All @@ -255,6 +281,10 @@ public ColumnConstraints deserialize(DataInputPlus in, Version version) throws I
.deserialize(in, version);
columnConstraints.add(constraint);
}

// we are not setting column name here on purpose
// that is deffered in ColumnMetadata's constructor,
// we do not have the access to a column name here anyway
return new ColumnConstraints(columnConstraints);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@
package org.apache.cassandra.cql3.constraints;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.apache.cassandra.cql3.ColumnIdentifier;
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.schema.ColumnMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;

import static java.lang.String.format;
import static org.apache.cassandra.cql3.Operator.EQ;
import static org.apache.cassandra.cql3.Operator.GT;
import static org.apache.cassandra.cql3.Operator.GTE;
Expand All @@ -41,13 +44,22 @@ public abstract class ConstraintFunction
{
public static final List<Operator> DEFAULT_FUNCTION_OPERATORS = List.of(EQ, NEQ, GTE, GT, LTE, LT);

protected final ColumnIdentifier columnName;
protected ColumnIdentifier columnName;
protected final String name;
protected final List<String> args;
// args as propagated from cql
protected final List<String> rawArgs;

public ConstraintFunction(ColumnIdentifier columnName, String name)
public ConstraintFunction(String name, List<String> args)
{
this.columnName = columnName;
this.name = name;
this.rawArgs = args;
this.args = unquote(args);
}

public List<String> arguments()
{
return args;
}

/**
Expand Down Expand Up @@ -84,6 +96,7 @@ public void evaluate(AbstractType<?> valueType, ByteBuffer columnValue) throws C
*/
public void validate(ColumnMetadata columnMetadata, String term) throws InvalidConstraintDefinitionException
{
maybeThrowOnNonEmptyArguments(name);
}

/**
Expand All @@ -100,4 +113,44 @@ public void validate(ColumnMetadata columnMetadata, String term) throws InvalidC
* @return supported types for given constraint
*/
public abstract List<AbstractType<?>> getSupportedTypes();

/**
* Tells whether implementation supports specifying arguments on its function.
* <br>
* In this case, this function will return "true"
* <pre>
* val int check length() < 1024
* </pre>
*
* In this case, this function will return "false"
* <pre>
* val int check someconstraint('abc', 'def')
* </pre>
* @return true if this constraint does not accept any parameters, false otherwise.
*/
public boolean isParameterless() { return true; }

@Override
public String toString()
{
return name;
}

protected void maybeThrowOnNonEmptyArguments(String constraintName)
{
if (!isParameterless())
return;

if (args != null && !args.isEmpty())
throw new InvalidConstraintDefinitionException(format("Constraint %s does not accept any arguments.", constraintName));
}

private List<String> unquote(List<String> quotedArgs)
{
List<String> unquotedArgs = new ArrayList<>();
for (String quotedArg : quotedArgs)
unquotedArgs.add(ParseUtils.unquote(quotedArg));

return unquotedArgs;
}
}
Loading