IGNITE-26706 Migration Tools: Allow coping with unnecessary third-party classes from SqlFunctionClasses (#6768)
diff --git a/migration-tools/modules/migration-tools-commons-tests/build.gradle b/migration-tools/modules/migration-tools-commons-tests/build.gradle
index 214e1f2..1f2ee83 100644
--- a/migration-tools/modules/migration-tools-commons-tests/build.gradle
+++ b/migration-tools/modules/migration-tools-commons-tests/build.gradle
@@ -23,6 +23,7 @@
configurations {
unpackDependencies { transitive = false}
+ fullSampleClusterDependencies
}
dependencies {
@@ -37,6 +38,8 @@
implementation files(layout.buildDirectory.dir("generated/unpackClassesFromDependencies"))
unpackDependencies libs.ignite2.core
+
+ fullSampleClusterDependencies project(':migration-tools-e2e-implementations-custom-classes')
}
def unpackTask = tasks.register('unpackClassesFromDependencies', Copy) {
@@ -53,10 +56,30 @@
"org/apache/ignite/cache/query/annotations/QuerySqlField\$Group.class"
}
+def createFullSampleClusterResources = tasks.register('createFullSampleClusterResources') {
+ description = 'Generates the "fullsamplecluster" resource file.'
+
+ def outputFile = file("$buildDir/resources/main/fullsamplecluster")
+ outputs.file(outputFile)
+
+ doLast {
+ def depList = configurations.fullSampleClusterDependencies.resolvedConfiguration.resolvedArtifacts.collect { artifact ->
+ artifact.file.path
+ }
+
+ outputFile.parentFile.mkdirs()
+ outputFile.text = depList.join('\n')
+ }
+}
+
compileJava {
dependsOn unpackTask
}
+processResources {
+ dependsOn createFullSampleClusterResources
+}
+
/*sourceSets {
main {
java {
diff --git a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/clusters/FullSampleCluster.java b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/clusters/FullSampleCluster.java
index daad32a..3543092 100644
--- a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/clusters/FullSampleCluster.java
+++ b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/clusters/FullSampleCluster.java
@@ -17,10 +17,19 @@
package org.apache.ignite.migrationtools.tests.clusters;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.ignite.migrationtools.tests.containers.Ignite2ClusterContainer;
import org.apache.ignite.migrationtools.tests.containers.Ignite2ClusterWithSamples;
+import org.jetbrains.annotations.Nullable;
+import org.testcontainers.utility.MountableFile;
/** Cluster with all the samples from all the caches. */
public class FullSampleCluster extends Ignite2ClusterWithSamples {
@@ -42,10 +51,32 @@
@Override
protected Ignite2ClusterContainer createClusterContainers() {
- return new Ignite2ClusterContainer(
+ var cluster = new Ignite2ClusterContainer(
CLUSTER_CFG_PATH,
TEST_CLUSTER_PATH,
clusterNodeIds
);
+
+ List<String> dependencies;
+ @Nullable InputStream rs = FullSampleCluster.class.getResourceAsStream("/fullsamplecluster");
+ if (rs == null) {
+ throw new IllegalStateException("Could not find required resource for loading dependencies.");
+ }
+
+ try (
+ rs;
+ InputStreamReader irs = new InputStreamReader(rs, UTF_8);
+ BufferedReader r = new BufferedReader(irs)
+ ) {
+ dependencies = r.lines().map(String::trim).collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ for (var path : dependencies) {
+ cluster.withFileInClasspath(MountableFile.forHostPath(path));
+ }
+
+ return cluster;
}
}
diff --git a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterContainer.java b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterContainer.java
index 1597a2f..d46f1ed 100644
--- a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterContainer.java
+++ b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterContainer.java
@@ -40,6 +40,7 @@
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.Startable;
import org.testcontainers.lifecycle.Startables;
+import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile;
/** Container of an Ignite 2 cluster. */
@@ -53,6 +54,8 @@
private final boolean storagePathMappedToExternal;
+ private final String igniteHome;
+
/**
* Port on host which binds container's 10800.
*/
@@ -80,11 +83,18 @@
this.containers = new ArrayList<>(nodeIds.size());
this.storagePathMappedToExternal = storagePathOnHost != null;
+ String dockerImageName = System.getProperty("ignite2.docker.image");
+ assert dockerImageName != null : "ignite2.docker.image must be defined";
+ DockerImageName dockerImage = DockerImageName.parse(dockerImageName);
+
+ this.igniteHome = "/opt/ignite/apache-ignite";
+
for (int i = 0; i < nodeIds.size(); i++) {
String hostname = "node" + (1 + i);
String nodeId = nodeIds.get(i);
var nodeContainer = createIgnite2Container(
+ dockerImage,
hostname,
nodeId,
cfgFilePath,
@@ -103,6 +113,7 @@
}
private GenericContainer<?> createIgnite2Container(
+ DockerImageName dockerImage,
String hostName,
String nodeId,
Path cfgFilePath,
@@ -110,10 +121,8 @@
) {
Consumer<OutputFrame> logConsumer = new CheckpointerLogConsumer();
String heapSize = System.getProperty("ai2.sampleCluster.Xmx", "10g");
- String ignite2DockerImage = System.getProperty("ignite2.docker.image");
- assert ignite2DockerImage != null : "ignite2.docker.image must be defined";
- GenericContainer<?> container = new GenericContainer<>(ignite2DockerImage);
+ GenericContainer<?> container = new GenericContainer<>(dockerImage);
if (storagePathMappedToExternal) {
container.withFileSystemBind(storagePathOnHost.toString(), "/storage", BindMode.READ_WRITE)
@@ -251,6 +260,17 @@
return dockerHost;
}
+ /**
+ * Copies the supplied file to all the containers classpath.
+ *
+ * @param fileToCopy File to copy.
+ */
+ public void withFileInClasspath(MountableFile fileToCopy) {
+ for (var container : this.containers) {
+ container.withCopyFileToContainer(fileToCopy, this.igniteHome + "/libs/");
+ }
+ }
+
private static class CheckpointerLogConsumer implements Consumer<OutputFrame> {
private List<Runnable> listeners = new CopyOnWriteArrayList<>();
diff --git a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterWithSamples.java b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterWithSamples.java
index 68066c4..519f3dc 100644
--- a/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterWithSamples.java
+++ b/migration-tools/modules/migration-tools-commons-tests/src/main/java/org/apache/ignite/migrationtools/tests/containers/Ignite2ClusterWithSamples.java
@@ -28,11 +28,9 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
-import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.OutputFrame;
-import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.MountableFile;
/** Ignite2ClusterWithSamples. */
@@ -59,22 +57,6 @@
return false;
}
- private static GenericContainer createIgnite2Container(String nodeId, String nodeName, Network network,
- Consumer<OutputFrame> logConsumer) {
- return new GenericContainer<>("apacheignite/ignite:2.15.0-jdk11")
- .withLabel("ai2.sample-cluster.node", nodeName)
- .withNetwork(network)
- .withNetworkAliases(nodeName)
- .withCopyFileToContainer(MountableFile.forHostPath(FullSampleCluster.CLUSTER_CFG_PATH), "/config-file.xml")
- .withFileSystemBind(FullSampleCluster.TEST_CLUSTER_PATH.toString(), "/storage", BindMode.READ_WRITE)
- .withEnv("CONFIG_URI", "/config-file.xml")
- .withEnv("IGNITE_WORK_DIR", "/storage")
- .withEnv("IGNITE_QUIET", "false")
- .withEnv("IGNITE_NODE_NAME", nodeId)
- .withLogConsumer(logConsumer)
- .waitingFor(Wait.forLogMessage(".*Node started .*", 1));
- }
-
protected abstract Ignite2ClusterContainer createClusterContainers();
private void recreateClusterFolder() throws InterruptedException, IOException {
diff --git a/migration-tools/modules/migration-tools-persistence/build.gradle b/migration-tools/modules/migration-tools-persistence/build.gradle
index f8ff15b..2e2da91 100644
--- a/migration-tools/modules/migration-tools-persistence/build.gradle
+++ b/migration-tools/modules/migration-tools-persistence/build.gradle
@@ -32,6 +32,7 @@
implementation libs.commons.collections4
implementation libs.jackson.databind
implementation libs.slf4j.api
+ implementation libs.bytebuddy
compileOnly libs.spotbugs.annotations
testImplementation project(":migration-tools-commons-tests")
diff --git a/migration-tools/modules/migration-tools-persistence/src/integrationTest/java/org/apache/ignite/migrationtools/persistence/MarshallerTest.java b/migration-tools/modules/migration-tools-persistence/src/integrationTest/java/org/apache/ignite/migrationtools/persistence/MarshallerTest.java
new file mode 100644
index 0000000..ee6d105
--- /dev/null
+++ b/migration-tools/modules/migration-tools-persistence/src/integrationTest/java/org/apache/ignite/migrationtools/persistence/MarshallerTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ignite.migrationtools.persistence;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.CacheType;
+import org.apache.ignite.internal.processors.cache.DynamicCacheDescriptor;
+import org.apache.ignite.migrationtools.tests.clusters.FullSampleCluster;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.FieldSource;
+
+@ExtendWith(FullSampleCluster.class)
+class MarshallerTest {
+ private static final List<String> CACHE_NAMES = List.of(
+ "MySimpleMap",
+ "MyPersonPojoCache",
+ "MyOrganizations",
+ "MyIntArrCache",
+ "MyListArrCache",
+ "MyBinaryPersonPojoCache",
+ "MyBinaryOrganizationCache",
+ "MyBinaryTestCache"
+ );
+
+ @ExtendWith(BasePersistentTestContext.class)
+ private List<MigrationKernalContext> nodeContexts;
+
+ private MigrationKernalContext nodeCtx;
+
+ @BeforeEach
+ void beforeEach() throws IgniteCheckedException {
+ nodeCtx = nodeContexts.get(0);
+ nodeCtx.start();
+ }
+
+ @Test
+ void loadAllTest() throws IgniteCheckedException {
+ ((MigrationCacheProcessor) nodeCtx.cache()).loadAllDescriptors();
+
+ Collection<DynamicCacheDescriptor> descriptors = nodeCtx.cache().persistentCaches().stream()
+ .filter(c -> c.cacheType() == CacheType.USER)
+ .collect(Collectors.toList());
+
+ assertThat(descriptors).hasSize(8);
+
+ assertThat(descriptors)
+ .map(DynamicCacheDescriptor::cacheConfiguration)
+ .doesNotContainNull();
+ }
+
+ @ParameterizedTest
+ @FieldSource("CACHE_NAMES")
+ void loadEachTest(String cacheName) {
+ var cacheDescriptor = nodeCtx.cache().cacheDescriptor(cacheName);
+ assertThat(cacheDescriptor).isNotNull();
+ assertThat(cacheDescriptor.cacheConfiguration()).isNotNull();
+ }
+}
diff --git a/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/MigrationKernalContext.java b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/MigrationKernalContext.java
index 9f40393..23ecb5b 100644
--- a/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/MigrationKernalContext.java
+++ b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/MigrationKernalContext.java
@@ -63,6 +63,7 @@
import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.migrationtools.persistence.marshallers.ForeignJdkMarshaller;
import org.apache.ignite.spi.deployment.local.LocalDeploymentSpi;
import org.apache.ignite.spi.encryption.noop.NoopEncryptionSpi;
import org.apache.ignite.spi.eventstorage.NoopEventStorageSpi;
@@ -82,6 +83,8 @@
private static final Field MARSH_CTX_FIELD;
+ private static final Field JDK_MARSHALLER_FIELD;
+
static {
try {
CFG_FIELD = GridKernalContextImpl.class.getDeclaredField("cfg");
@@ -92,6 +95,9 @@
MARSH_CTX_FIELD = GridKernalContextImpl.class.getDeclaredField("marshCtx");
MARSH_CTX_FIELD.setAccessible(true);
+
+ JDK_MARSHALLER_FIELD = MarshallerContextImpl.class.getDeclaredField("jdkMarsh");
+ JDK_MARSHALLER_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
@@ -127,6 +133,7 @@
try {
CFG_FIELD.set(this, adaptedConfiguration);
+ JDK_MARSHALLER_FIELD.set(marshCtx, new ForeignJdkMarshaller());
MARSH_CTX_FIELD.set(this, marshCtx);
// Unnecessarily required by CacheObjectBinaryProcessorImpl & by GridCacheDefaultAffinityKeyMapper#ignite
diff --git a/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignJdkMarshaller.java b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignJdkMarshaller.java
new file mode 100644
index 0000000..d21d6e8
--- /dev/null
+++ b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignJdkMarshaller.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ignite.migrationtools.persistence.marshallers;
+
+import java.io.InputStream;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.marshaller.jdk.JdkMarshaller;
+import org.jetbrains.annotations.Nullable;
+
+/** {@link JdkMarshaller} implementation which uses {@link ForeignObjectInputStream}. */
+public class ForeignJdkMarshaller extends JdkMarshaller {
+ @Override
+ protected <T> T unmarshal0(InputStream in, @Nullable ClassLoader clsLdr) throws IgniteCheckedException {
+ // Essentially the same impl as JdkMarshaller but with a different underlying ObjectInputStream.
+ assert in != null;
+
+ ClassLoader localClassLoader = (clsLdr != null) ? clsLdr : getClass().getClassLoader();
+
+ try (var objIn = new ForeignObjectInputStream(in, localClassLoader)) {
+ return (T) objIn.readObject();
+ } catch (ClassNotFoundException e) {
+ throw new IgniteCheckedException("Failed to find class with given class loader for unmarshalling "
+ + "[clsLdr=" + localClassLoader + ", cls=" + e.getMessage() + "]", e);
+ } catch (Exception e) {
+ throw new IgniteCheckedException("Failed to deserialize object with given class loader: " + localClassLoader, e);
+ }
+ }
+}
diff --git a/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignObjectInputStream.java b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignObjectInputStream.java
new file mode 100644
index 0000000..a15a610
--- /dev/null
+++ b/migration-tools/modules/migration-tools-persistence/src/main/java/org/apache/ignite/migrationtools/persistence/marshallers/ForeignObjectInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * 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.ignite.migrationtools.persistence.marshallers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.util.ArrayList;
+import java.util.List;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.modifier.Visibility;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default;
+import net.bytebuddy.implementation.MethodCall;
+import net.bytebuddy.implementation.SuperMethodCall;
+
+/**
+ * {@link ObjectInputStream} implementation which prevents {@link ClassNotFoundException}s from custom client libraries.
+ */
+public final class ForeignObjectInputStream extends ObjectInputStream {
+ private final List<Class<?>> dummyClasses;
+
+ private final ClassLoader publicClassloader;
+
+ /**
+ * Constructor.
+ *
+ * @param in Base input stream.
+ * @param classLoader classloader.
+ * @throws IOException may be thrown.
+ */
+ public ForeignObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException {
+ super(in);
+ this.enableResolveObject(true);
+ this.dummyClasses = new ArrayList<>();
+ this.publicClassloader = classLoader;
+ }
+
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+ Class<?> c;
+ try {
+ c = super.resolveClass(desc);
+ } catch (ClassNotFoundException ex) {
+ String className = desc.getName();
+ if (!className.startsWith("org.apache.ignite.")) {
+ throw ex;
+ }
+
+ c = new ByteBuddy()
+ .subclass(Object.class, Default.NO_CONSTRUCTORS)
+ .name(className)
+ .defineConstructor(Visibility.PRIVATE)
+ .intercept(SuperMethodCall.INSTANCE.andThen(MethodCall.run(() -> {
+ throw new RuntimeException(String.format("Cannot instantiate dummy object for class '%s'."
+ + " This class could not be found and a placeholder was generated.", className));
+ })))
+ .make()
+ .load(publicClassloader, ClassLoadingStrategy.Default.WRAPPER)
+ .getLoaded();
+
+ this.dummyClasses.add(c);
+ }
+
+ return c;
+ }
+
+ @Override
+ protected Object resolveObject(Object obj) {
+ for (Class<?> klass : this.dummyClasses) {
+ if (klass.isInstance(obj)) {
+ // Found instance of a dummy object. This should not happen.
+ throw new IllegalStateException("Found instance of dummy object: " + klass.getName());
+ }
+
+ // We could also replace the dummy object with null.
+ }
+
+ return obj;
+ }
+}
diff --git a/migration-tools/resources/configs-custom/ignite-config.0.xml b/migration-tools/resources/configs-custom/ignite-config.0.xml
index 1a9a390..eae91c6 100644
--- a/migration-tools/resources/configs-custom/ignite-config.0.xml
+++ b/migration-tools/resources/configs-custom/ignite-config.0.xml
@@ -58,6 +58,8 @@
<!-- Setting schema to PUBLIC. Otherwise, you need to add the cache name as a schema name in your
queries -->
<property name="sqlSchema" value="PUBLIC"/>
+ <!-- Setting a custom SQL function class -->
+ <property name="SqlFunctionClasses" value="org.apache.ignite.migrationtools.tests.e2e.custom.MySqlFunctions"/>
<!-- Setting QueryEntities -->
<property name="queryEntities">
diff --git a/migration-tools/tools/e2e-tests-framework/ai2-runner/build.gradle b/migration-tools/tools/e2e-tests-framework/ai2-runner/build.gradle
index 16ff19b..c0cb2d4 100644
--- a/migration-tools/tools/e2e-tests-framework/ai2-runner/build.gradle
+++ b/migration-tools/tools/e2e-tests-framework/ai2-runner/build.gradle
@@ -29,6 +29,7 @@
dependencies {
implementation project(":migration-tools-e2e-core")
implementation project(":migration-tools-e2e-implementations")
+ implementation project(":migration-tools-e2e-implementations-custom-classes")
implementation runtimeApacheIgnite2.ignite2.core
diff --git a/migration-tools/tools/e2e-tests-framework/custom-classes/build.gradle b/migration-tools/tools/e2e-tests-framework/custom-classes/build.gradle
new file mode 100644
index 0000000..3496841
--- /dev/null
+++ b/migration-tools/tools/e2e-tests-framework/custom-classes/build.gradle
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+apply from: "$rootDir/buildscripts/java-core.gradle"
+apply from: "$rootDir/buildscripts/publishing.gradle"
+
+description = 'migration-tools-e2e-implementations-custom-classes'
+
+dependencies {
+ compileOnly libs.ignite2.core
+}
diff --git a/migration-tools/tools/e2e-tests-framework/custom-classes/src/main/java/org/apache/ignite/migrationtools/tests/e2e/custom/MySqlFunctions.java b/migration-tools/tools/e2e-tests-framework/custom-classes/src/main/java/org/apache/ignite/migrationtools/tests/e2e/custom/MySqlFunctions.java
new file mode 100644
index 0000000..cfda659
--- /dev/null
+++ b/migration-tools/tools/e2e-tests-framework/custom-classes/src/main/java/org/apache/ignite/migrationtools/tests/e2e/custom/MySqlFunctions.java
@@ -0,0 +1,28 @@
+/*
+ * 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.ignite.migrationtools.tests.e2e.custom;
+
+import org.apache.ignite.cache.query.annotations.QuerySqlFunction;
+
+/** Custom Sql function. */
+public class MySqlFunctions {
+ @QuerySqlFunction
+ public static int sqr(int x) {
+ return x * x;
+ }
+}
diff --git a/migration-tools/tools/e2e-tests-framework/implementations/build.gradle b/migration-tools/tools/e2e-tests-framework/implementations/build.gradle
index c4eddc9..bdf873e 100644
--- a/migration-tools/tools/e2e-tests-framework/implementations/build.gradle
+++ b/migration-tools/tools/e2e-tests-framework/implementations/build.gradle
@@ -25,6 +25,7 @@
annotationProcessor libs.auto.service
compileOnly project(":ignite-api")
+ compileOnly project(":migration-tools-e2e-implementations-custom-classes")
compileOnly libs.ignite2.core
compileOnly libs.ignite2.spring
compileOnly libs.spotbugs.annotations
diff --git a/migration-tools/tools/e2e-tests-framework/implementations/src/main/java/org/apache/ignite/migrationtools/tests/e2e/impl/MySimpleMapCacheTest.java b/migration-tools/tools/e2e-tests-framework/implementations/src/main/java/org/apache/ignite/migrationtools/tests/e2e/impl/MySimpleMapCacheTest.java
index 8840f30..1d49f2b 100644
--- a/migration-tools/tools/e2e-tests-framework/implementations/src/main/java/org/apache/ignite/migrationtools/tests/e2e/impl/MySimpleMapCacheTest.java
+++ b/migration-tools/tools/e2e-tests-framework/implementations/src/main/java/org/apache/ignite/migrationtools/tests/e2e/impl/MySimpleMapCacheTest.java
@@ -24,6 +24,7 @@
import java.sql.SQLException;
import java.util.Map;
import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.migrationtools.tests.e2e.custom.MySqlFunctions;
import org.apache.ignite.migrationtools.tests.e2e.framework.core.ExampleBasedCacheTest;
import org.jetbrains.annotations.Nullable;
@@ -40,6 +41,7 @@
public CacheConfiguration<String, Integer> cacheConfiguration() {
CacheConfiguration<String, Integer> cfg = super.cacheConfiguration();
cfg.setSqlSchema(SCHEMA_NAME_CASED);
+ cfg.setSqlFunctionClasses(MySqlFunctions.class);
return cfg;
}
diff --git a/settings.gradle b/settings.gradle
index 9726370..21f1459 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -191,6 +191,7 @@
// End 2 End test stuff
include(":migration-tools-e2e-core")
include(":migration-tools-e2e-implementations")
+include(":migration-tools-e2e-implementations-custom-classes")
include(":migration-tools-e2e-ai2-runner")
include(":migration-tools-e2e-ai3-tests")
@@ -206,6 +207,7 @@
project(":migration-tools-packaging-cli").projectDir = file('migration-tools/packaging/cli')
project(":migration-tools-e2e-core").projectDir = file('migration-tools/tools/e2e-tests-framework/framework-core')
project(":migration-tools-e2e-implementations").projectDir = file('migration-tools/tools/e2e-tests-framework/implementations')
+project(":migration-tools-e2e-implementations-custom-classes").projectDir = file('migration-tools/tools/e2e-tests-framework/custom-classes')
project(":migration-tools-e2e-ai2-runner").projectDir = file('migration-tools/tools/e2e-tests-framework/ai2-runner')
project(":migration-tools-e2e-ai3-tests").projectDir = file('migration-tools/modules/e2e-ai3-tests')