Skip to content

Commit 9ac5da6

Browse files
authored
Enable validating SqlCreateView and SqlCreateMaterializedView through ValidationService (#144)
1 parent 0127600 commit 9ac5da6

File tree

3 files changed

+113
-9
lines changed

3 files changed

+113
-9
lines changed

hoptimator-jdbc/src/main/java/com/linkedin/hoptimator/jdbc/HoptimatorDdlExecutor.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
*/
2020
package com.linkedin.hoptimator.jdbc;
2121

22+
import com.linkedin.hoptimator.Database;
23+
import com.linkedin.hoptimator.MaterializedView;
24+
import com.linkedin.hoptimator.Pipeline;
25+
import com.linkedin.hoptimator.View;
26+
import com.linkedin.hoptimator.util.DeploymentService;
27+
import com.linkedin.hoptimator.util.planner.HoptimatorJdbcTable;
28+
import com.linkedin.hoptimator.util.planner.PipelineRel;
2229
import java.io.Reader;
2330
import java.sql.SQLException;
2431
import java.util.ArrayList;
@@ -27,7 +34,6 @@
2734
import java.util.List;
2835
import java.util.Objects;
2936
import java.util.Properties;
30-
3137
import org.apache.calcite.jdbc.CalcitePrepare;
3238
import org.apache.calcite.jdbc.CalciteSchema;
3339
import org.apache.calcite.rel.RelRoot;
@@ -62,14 +68,6 @@
6268
import org.apache.calcite.util.Pair;
6369
import org.apache.calcite.util.Util;
6470

65-
import com.linkedin.hoptimator.Database;
66-
import com.linkedin.hoptimator.MaterializedView;
67-
import com.linkedin.hoptimator.Pipeline;
68-
import com.linkedin.hoptimator.View;
69-
import com.linkedin.hoptimator.util.DeploymentService;
70-
import com.linkedin.hoptimator.util.planner.HoptimatorJdbcTable;
71-
import com.linkedin.hoptimator.util.planner.PipelineRel;
72-
7371

7472
public final class HoptimatorDdlExecutor extends ServerDdlExecutor {
7573

@@ -100,6 +98,12 @@ public DdlExecutor getDdlExecutor() {
10098
/** Executes a {@code CREATE VIEW} command. */
10199
@Override
102100
public void execute(SqlCreateView create, CalcitePrepare.Context context) {
101+
try {
102+
ValidationService.validateOrThrow(create);
103+
} catch (SQLException e) {
104+
throw new DdlException(create, e.getMessage(), e);
105+
}
106+
103107
final Pair<CalciteSchema, String> pair = schema(context, true, create.name);
104108
if (pair.left == null) {
105109
throw new DdlException(create, "Schema for " + create.name + " not found.");
@@ -118,6 +122,7 @@ public void execute(SqlCreateView create, CalcitePrepare.Context context) {
118122
pair.left.removeFunction(pair.right);
119123
}
120124
}
125+
121126
final SqlNode q = renameColumns(create.columnList, create.query);
122127
final String sql = q.toSqlString(CalciteSqlDialect.DEFAULT).getSql();
123128
List<String> schemaPath = pair.left.path(null);
@@ -146,6 +151,12 @@ public void execute(SqlCreateView create, CalcitePrepare.Context context) {
146151
/** Executes a {@code CREATE MATERIALIZED VIEW} command. */
147152
@Override
148153
public void execute(SqlCreateMaterializedView create, CalcitePrepare.Context context) {
154+
try {
155+
ValidationService.validateOrThrow(create);
156+
} catch (SQLException e) {
157+
throw new DdlException(create, e.getMessage(), e);
158+
}
159+
149160
final Pair<CalciteSchema, String> pair = schema(context, true, create.name);
150161
if (pair.left == null) {
151162
throw new DdlException(create, "Schema for " + create.name + " not found.");

hoptimator-jdbc/src/test/java/com/linkedin/hoptimator/jdbc/TestSqlScripts.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
package com.linkedin.hoptimator.jdbc;
22

3+
import com.linkedin.hoptimator.Validator;
4+
import com.linkedin.hoptimator.ValidatorProvider;
5+
import java.io.IOException;
6+
import java.net.URL;
7+
import java.net.URLClassLoader;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.util.Collection;
11+
import java.util.List;
12+
import org.apache.calcite.sql.ddl.SqlCreateMaterializedView;
13+
import org.apache.calcite.sql.ddl.SqlCreateView;
14+
import org.junit.jupiter.api.Assertions;
315
import org.junit.jupiter.api.Test;
16+
import org.opentest4j.AssertionFailedError;
417

518

619
public class TestSqlScripts extends QuidemTestBase {
@@ -9,4 +22,72 @@ public class TestSqlScripts extends QuidemTestBase {
922
public void basicDdlScript() throws Exception {
1023
run("basic-ddl.id");
1124
}
25+
26+
@Test
27+
public void createViewWithAValidatorRejectingCreateViewThrowsException() throws Exception {
28+
// Runs the test in a separate thread to isolate the context class loader changes.
29+
Thread testThread = new Thread(() -> {
30+
useTestValidatorsUnchecked();
31+
32+
AssertionFailedError exception = Assertions.assertThrows(AssertionFailedError.class, () -> run("basic-ddl.id"));
33+
Assertions.assertTrue(exception.getMessage().contains(SqlCreateViewValidator.ERROR_MESSAGE),
34+
"Expected error message not found: " + exception.getMessage());
35+
});
36+
testThread.start();
37+
testThread.join(); // Wait for the thread to finish
38+
}
39+
40+
@Test
41+
public void createMaterializedViewWithAValidatorRejectingCreateViewThrowsException() throws Exception {
42+
// Runs the test in a separate thread to isolate the context class loader changes.
43+
Thread testThread = new Thread(() -> {
44+
useTestValidatorsUnchecked();
45+
46+
AssertionFailedError exception =
47+
Assertions.assertThrows(AssertionFailedError.class, () -> run("create-materialized-view-ddl.id"));
48+
Assertions.assertTrue(exception.getMessage().contains(SqlCreateViewValidator.ERROR_MESSAGE),
49+
"Expected error message not found: " + exception.getMessage());
50+
});
51+
testThread.start();
52+
testThread.join(); // Wait for the thread to finish
53+
}
54+
55+
private void useTestValidatorsUnchecked() {
56+
try {
57+
useTestValidators();
58+
} catch (IOException e) {
59+
throw new RuntimeException("Failed to set up test validators", e);
60+
}
61+
}
62+
63+
private void useTestValidators() throws IOException {
64+
Path tempDir = Files.createTempDirectory("spi-test");
65+
Path servicesDir = tempDir.resolve("META-INF/services");
66+
Files.createDirectories(servicesDir);
67+
Files.writeString(servicesDir.resolve("com.linkedin.hoptimator.ValidatorProvider"),
68+
"com.linkedin.hoptimator.jdbc.TestSqlScripts$CreateViewValidatorProvider\n");
69+
70+
URLClassLoader cl = new URLClassLoader(new URL[]{tempDir.toUri().toURL()}, getClass().getClassLoader());
71+
Thread.currentThread().setContextClassLoader(cl);
72+
}
73+
74+
@SuppressWarnings("unused")
75+
public static class CreateViewValidatorProvider implements ValidatorProvider {
76+
@Override
77+
public <T> Collection<Validator> validators(T obj) {
78+
if (obj instanceof SqlCreateView || obj instanceof SqlCreateMaterializedView) {
79+
return List.of(new SqlCreateViewValidator());
80+
}
81+
return List.of();
82+
}
83+
}
84+
85+
static class SqlCreateViewValidator implements Validator {
86+
static final String ERROR_MESSAGE = "Create view is not allowed in this test.";
87+
88+
@Override
89+
public void validate(Issues issues) {
90+
issues.error(ERROR_MESSAGE);
91+
}
92+
}
1293
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!set outputformat mysql
2+
!use util
3+
4+
create table foo (id VARCHAR(128), a VARCHAR, c INT);
5+
(0 rows modified)
6+
7+
!update
8+
9+
create materialized view foo_edge as select * from foo;
10+
(0 rows modified)
11+
12+
!update

0 commit comments

Comments
 (0)