Skip to content

Commit d0e1cca

Browse files
committed
encapsulate ddml validation
1 parent b37ee42 commit d0e1cca

File tree

4 files changed

+127
-11
lines changed

4 files changed

+127
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.substrait.util;
2+
3+
public final class NoException extends RuntimeException {
4+
private NoException() {}
5+
}

isthmus/src/main/java/io/substrait/isthmus/SubstraitRelNodeConverter.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public class SubstraitRelNodeConverter
9494
protected final RelBuilder relBuilder;
9595
protected final RexBuilder rexBuilder;
9696
private final TypeConverter typeConverter;
97+
private final SubstraitRelNodeConverterDdmlValidator substraitRelNodeConverterDdmlValidator;
9798

9899
public SubstraitRelNodeConverter(
99100
SimpleExtension.ExtensionCollection extensions,
@@ -142,6 +143,7 @@ public SubstraitRelNodeConverter(
142143
this.aggregateFunctionConverter = aggregateFunctionConverter;
143144
this.expressionRexConverter = expressionRexConverter;
144145
this.expressionRexConverter.setRelNodeConverter(this);
146+
this.substraitRelNodeConverterDdmlValidator = new SubstraitRelNodeConverterDdmlValidator(relBuilder, this);
145147
}
146148

147149
public static RelNode convert(
@@ -554,8 +556,10 @@ public RelNode visit(NamedUpdate update, Context context) {
554556

555557
@Override
556558
public RelNode visit(NamedDdl namedDdl, Context context) {
557-
if (namedDdl.getViewDefinition().isEmpty()) {
558-
throw new IllegalArgumentException("no view definition found");
559+
final ValidationResult validationResult = namedDdl.accept(substraitRelNodeConverterDdmlValidator, context);
560+
if (!validationResult.isValid()) {
561+
throw new IllegalArgumentException(
562+
String.join(System.lineSeparator(), validationResult.getMessages()));
559563
}
560564
Rel viewDefinition = namedDdl.getViewDefinition().get();
561565
RelNode relNode = viewDefinition.accept(this, context);
@@ -565,7 +569,6 @@ public RelNode visit(NamedDdl namedDdl, Context context) {
565569

566570
@Override
567571
public RelNode visit(VirtualTableScan virtualTableScan, Context context) {
568-
569572
final RelDataType typeInfoOnly =
570573
typeConverter.toCalcite(typeFactory, virtualTableScan.getInitialSchema().struct());
571574

@@ -609,6 +612,11 @@ private RelNode handleCreateTableAs(NamedWrite namedWrite, Context context) {
609612

610613
@Override
611614
public RelNode visit(NamedWrite write, Context context) {
615+
final ValidationResult validationResult = write.accept(substraitRelNodeConverterDdmlValidator, context);
616+
if (!validationResult.isValid()) {
617+
throw new IllegalArgumentException(
618+
String.join(System.lineSeparator(), validationResult.getMessages()));
619+
}
612620
RelNode input = write.getInput().accept(this, context);
613621
assert relBuilder.getRelOptSchema() != null;
614622
final RelOptTable targetTable =
@@ -625,16 +633,12 @@ public RelNode visit(NamedWrite write, Context context) {
625633
case CTAS:
626634
return handleCreateTableAs(write, context);
627635
default:
628-
throw new UnsupportedOperationException(
629-
"Write operation '"
630-
+ write.getOperation()
631-
+ "' is not supported by the NamedWrite visitor. "
632-
+ "Check if a more specific relation type (e.g., NamedUpdate) should be used.");
636+
// checked by validation
637+
throw new IllegalArgumentException("Couldn't determine operation");
633638
}
634639

635-
if (targetTable == null) {
636-
throw new IllegalStateException("Table not found in Calcite catalog: " + write.getNames());
637-
}
640+
// checked by validation
641+
assert targetTable != null;
638642

639643
return LogicalTableModify.create(
640644
targetTable,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.substrait.isthmus;
2+
3+
import io.substrait.relation.AbstractDdlRel;
4+
import io.substrait.relation.AbstractRelVisitor;
5+
import io.substrait.relation.AbstractWriteRel;
6+
import io.substrait.relation.NamedDdl;
7+
import io.substrait.relation.NamedWrite;
8+
import io.substrait.relation.Rel;
9+
import io.substrait.util.NoException;
10+
import java.util.LinkedList;
11+
import java.util.List;
12+
import java.util.Set;
13+
import org.apache.calcite.tools.RelBuilder;
14+
15+
public class SubstraitRelNodeConverterDdmlValidator
16+
extends AbstractRelVisitor<ValidationResult, SubstraitRelNodeConverter.Context, NoException> {
17+
private static final Set<AbstractWriteRel.WriteOp> NAMED_WRITE_OPERATIONS_SUPPORTED =
18+
Set.of(
19+
AbstractWriteRel.WriteOp.INSERT,
20+
AbstractWriteRel.WriteOp.DELETE,
21+
AbstractWriteRel.WriteOp.UPDATE,
22+
AbstractWriteRel.WriteOp.CTAS);
23+
24+
private final RelBuilder relBuilder;
25+
private final SubstraitRelNodeConverter substraitRelNodeConverter;
26+
27+
public SubstraitRelNodeConverterDdmlValidator(
28+
final RelBuilder relBuilder, SubstraitRelNodeConverter substraitRelNodeConverter) {
29+
this.relBuilder = relBuilder;
30+
this.substraitRelNodeConverter = substraitRelNodeConverter;
31+
}
32+
33+
@Override
34+
public ValidationResult visitFallback(Rel rel, SubstraitRelNodeConverter.Context context) {
35+
return ValidationResult.ok();
36+
}
37+
38+
@Override
39+
public ValidationResult visit(NamedDdl namedDdl, SubstraitRelNodeConverter.Context ctx) {
40+
final List<String> validationMessages = new LinkedList<>();
41+
if (namedDdl.getViewDefinition().isEmpty()) {
42+
validationMessages.add("No view definition found");
43+
}
44+
if (!AbstractDdlRel.DdlObject.VIEW.equals(namedDdl.getObject())) {
45+
validationMessages.add("Unsupported object: " + namedDdl.getObject().name());
46+
}
47+
if (!AbstractDdlRel.DdlOp.CREATE.equals(namedDdl.getOperation())) {
48+
validationMessages.add("Unsupported operation: " + namedDdl.getOperation().name());
49+
}
50+
return validationMessages.isEmpty()
51+
? ValidationResult.SUCCESS
52+
: ValidationResult.error(validationMessages);
53+
}
54+
55+
@Override
56+
public ValidationResult visit(NamedWrite namedWrite, SubstraitRelNodeConverter.Context ctx) {
57+
final List<String> validationMessages = new LinkedList<>();
58+
if (!NAMED_WRITE_OPERATIONS_SUPPORTED.contains(namedWrite.getOperation())) {
59+
validationMessages.add(
60+
"Write operation '"
61+
+ namedWrite.getOperation()
62+
+ "' is not supported by the NamedWrite visitor. "
63+
+ "Check if a more specific relation type (e.g., NamedUpdate) should be used.");
64+
}
65+
if (relBuilder.getRelOptSchema() == null
66+
|| relBuilder.getRelOptSchema().getTableForMember(namedWrite.getNames()) == null) {
67+
validationMessages.add("Table not found in Calcite catalog: " + namedWrite.getNames());
68+
}
69+
70+
if (namedWrite.getInput().accept(substraitRelNodeConverter, ctx) == null) {
71+
validationMessages.add("Only write operations with input supported");
72+
}
73+
return validationMessages.isEmpty()
74+
? ValidationResult.SUCCESS
75+
: ValidationResult.error(validationMessages);
76+
}
77+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.substrait.isthmus;
2+
3+
import java.util.List;
4+
5+
public class ValidationResult {
6+
public static final ValidationResult SUCCESS = ok();
7+
private final boolean isValid;
8+
private final List<String> messages;
9+
10+
public ValidationResult(boolean isValid, List<String> messages) {
11+
this.isValid = isValid;
12+
this.messages = messages;
13+
}
14+
15+
public static ValidationResult ok() {
16+
return new ValidationResult(true, List.of("OK"));
17+
}
18+
19+
public static ValidationResult error(List<String> messages) {
20+
return new ValidationResult(false, messages);
21+
}
22+
23+
public boolean isValid() {
24+
return isValid;
25+
}
26+
27+
public List<String> getMessages() {
28+
return messages;
29+
}
30+
}

0 commit comments

Comments
 (0)