Skip to content

parameterless constraints draft #4022

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
33 changes: 30 additions & 3 deletions src/antlr/Parser.g
Original file line number Diff line number Diff line change
Expand Up @@ -795,9 +795,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, and don't know if worth doing, but here it goes yet another crazy idea... What about something like:

(K_NOT)? funcName=ident

With the K_NOT optional we can "negate" whatever the func does. This would allow us to reuse the NOT notation for all other constraints. A crazy example would be:

ALTER TABLE ks.tb ADD val3 text CHECK NOT JSON

{
$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 @@ -810,6 +831,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,13 +40,17 @@
*/
public abstract class ColumnConstraint<T>
{
protected final ColumnIdentifier columnName;
protected ColumnIdentifier columnName;

public ColumnConstraint(ColumnIdentifier columnName)
public void setColumnName(ColumnIdentifier columnName)
{
this.columnName = columnName;
}

public ColumnConstraint()
{
}

// Enum containing all the possible constraint serializers to help with serialization/deserialization
// of constraints.
public enum ConstraintType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public class ColumnConstraints extends ColumnConstraint<ColumnConstraints>

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

Expand Down Expand Up @@ -133,6 +132,7 @@ public void validate(ColumnMetadata columnMetadata) throws InvalidConstraintDefi
for (SatisfiabilityChecker satisfiabilityChecker : ConstraintType.getSatisfiabilityCheckers())
satisfiabilityChecker.checkSatisfiability(constraints, columnMetadata);


// this validation will check whether it makes sense to execute such constraint on a given column
for (ColumnConstraint<?> constraint : constraints)
constraint.validate(columnMetadata);
Expand Down Expand Up @@ -221,15 +221,18 @@ public ColumnConstraints prepare(ColumnIdentifier column)
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));
else
constraint.setColumnName(column);
}

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 +258,7 @@ public ColumnConstraints deserialize(DataInputPlus in, Version version) throws I
.deserialize(in, version);
columnConstraints.add(constraint);
}

return new ColumnConstraints(columnConstraints);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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 +42,24 @@ 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;

public ConstraintFunction(ColumnIdentifier columnName, String name)
public ConstraintFunction(String name)
{
this(name, List.of());
}

public ConstraintFunction(String name, List<String> args)
{
this.columnName = columnName;
this.name = name;
this.args = 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
{
maybeThrowOnNontEmptyArguments(name);
}

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

public boolean isParameterless() { return true; }

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

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

if (args != null && !args.isEmpty())
throw new InvalidConstraintDefinitionException(format("Constraint %s does not accept any arguments.", constraintName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

Expand All @@ -45,21 +46,21 @@ public class FunctionColumnConstraint extends AbstractFunctionConstraint<Functio
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)
public Raw(ColumnIdentifier functionName, List<String> arguments, Operator relationType, String term)
{
this.relationType = relationType;
this.columnName = columnName;
this.term = term;
function = createConstraintFunction(functionName.toCQLString(), columnName);
if (arguments == null)
arguments = new ArrayList<>();
function = createConstraintFunction(functionName.toCQLString(), arguments);
}

public FunctionColumnConstraint prepare()
{
return new FunctionColumnConstraint(function, columnName, relationType, term);
return new FunctionColumnConstraint(function, relationType, term);
}
}

Expand All @@ -81,23 +82,31 @@ public enum Functions
OCTET_LENGTH(OctetLengthConstraint::new),
REGEXP(RegexpConstraint::new);

private final Function<ColumnIdentifier, ConstraintFunction> functionCreator;
private final Function<List<String>, ConstraintFunction> functionCreator;

Functions(Function<ColumnIdentifier, ConstraintFunction> functionCreator)
Functions(Function<List<String>, ConstraintFunction> functionCreator)
{
this.functionCreator = functionCreator;
}
}

private static ConstraintFunction createConstraintFunction(String functionName, ColumnIdentifier columnName)
private static ConstraintFunction createConstraintFunction(String functionName, List<String> args)
{
return getEnum(Functions.class, functionName).functionCreator.apply(columnName);
return getEnum(Functions.class, functionName).functionCreator.apply(args);
}

private FunctionColumnConstraint(ConstraintFunction function, ColumnIdentifier columnName, Operator relationType, String term)
private FunctionColumnConstraint(ConstraintFunction function, Operator relationType, String term)
{
super(columnName, relationType, term);
super(relationType, term);
this.function = function;
this.columnName = function.columnName;
}

@Override
public void setColumnName(ColumnIdentifier columnName)
{
this.columnName = columnName;
this.function.columnName = columnName;
}

public ConstraintFunction function()
Expand Down Expand Up @@ -156,7 +165,6 @@ protected void internalEvaluate(AbstractType<?> valueType, ByteBuffer columnValu
@Override
public void validate(ColumnMetadata columnMetadata)
{
validateArgs(columnMetadata);
validateTypes(columnMetadata);
function.validate(columnMetadata, term);
}
Expand All @@ -167,18 +175,11 @@ public ConstraintType getConstraintType()
return ConstraintType.FUNCTION;
}

void validateArgs(ColumnMetadata columnMetadata)
{
if (!columnMetadata.name.equals(columnName))
throw new InvalidConstraintDefinitionException(String.format("Parameter of %s constraint should be the column name (%s)",
name(),
columnMetadata.name));
}

@Override
public String toString()
{
return function.name + "(" + columnName + ") " + relationType + " " + term;
String arguments = String.join(",", function.args);
return function.name + '(' + arguments + ") " + relationType + ' ' + term;
}

public static class Serializer implements MetadataSerializer<FunctionColumnConstraint>
Expand All @@ -187,6 +188,12 @@ public static class Serializer implements MetadataSerializer<FunctionColumnConst
public void serialize(FunctionColumnConstraint columnConstraint, DataOutputPlus out, Version version) throws IOException
{
out.writeUTF(columnConstraint.function.name);

int argsSize = columnConstraint.function.args.size();
out.writeInt(argsSize);
for (int i = 0; i < argsSize; i++)
out.writeUTF(columnConstraint.function.args.get(i));

out.writeUTF(columnConstraint.columnName.toCQLString());
columnConstraint.relationType.writeTo(out);
out.writeUTF(columnConstraint.term);
Expand All @@ -196,26 +203,40 @@ public void serialize(FunctionColumnConstraint columnConstraint, DataOutputPlus
public FunctionColumnConstraint deserialize(DataInputPlus in, Version version) throws IOException
{
String functionName = in.readUTF();
ConstraintFunction function;

List<String> args = new ArrayList<>();
int argsSize = in.readInt();
for (int i = 0; i < argsSize; i++)
args.add(in.readUTF());

String columnNameString = in.readUTF();
ColumnIdentifier columnName = new ColumnIdentifier(columnNameString, true);

ConstraintFunction function;
try
{
function = createConstraintFunction(functionName, columnName);
function = createConstraintFunction(functionName, args);
function.columnName = 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);
return new FunctionColumnConstraint(function, relationType, term);
}

@Override
public long serializedSize(FunctionColumnConstraint columnConstraint, Version version)
{
int argsSizes = 0;
for (String arg : columnConstraint.function.args)
argsSizes += TypeSizes.sizeof(arg);

return TypeSizes.sizeof(columnConstraint.function.getClass().getName())
+ TypeSizes.sizeof(columnConstraint.function.args.size())
+ argsSizes
+ TypeSizes.sizeof(columnConstraint.columnName.toCQLString())
+ TypeSizes.sizeof(columnConstraint.term)
+ Operator.serializedSize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.nio.ByteBuffer;
import java.util.List;

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;
Expand All @@ -37,9 +36,9 @@ public class JsonConstraint extends UnaryConstraintFunction

public static final String FUNCTION_NAME = "JSON";

public JsonConstraint(ColumnIdentifier columnName)
public JsonConstraint(List<String> args)
{
super(columnName, FUNCTION_NAME);
super(FUNCTION_NAME, args);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.nio.ByteBuffer;
import java.util.List;

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;
Expand All @@ -35,9 +34,9 @@ public class LengthConstraint extends ConstraintFunction
private static final String NAME = "LENGTH";
private static final List<AbstractType<?>> SUPPORTED_TYPES = List.of(BytesType.instance, UTF8Type.instance, AsciiType.instance);

public LengthConstraint(ColumnIdentifier columnName)
public LengthConstraint(List<String> args)
{
super(columnName, NAME);
super(NAME, args);
}

@Override
Expand Down
Loading