com.sun.el.
sqrl.el.
diff --git a/sqrl-cli/src/main/java/com/datasqrl/cli/AbstractCompileCmd.java b/sqrl-cli/src/main/java/com/datasqrl/cli/AbstractCompileCmd.java
index 95d13985cd..7b13592363 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/cli/AbstractCompileCmd.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/cli/AbstractCompileCmd.java
@@ -32,13 +32,13 @@
import com.datasqrl.util.ConfigLoaderUtils;
import com.datasqrl.util.FlinkCompileException;
import com.datasqrl.util.SqrlInjector;
-import com.google.inject.Guice;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public abstract class AbstractCompileCmd extends BasePackageConfCmd {
@@ -60,23 +60,23 @@ protected void compile(ErrorCollector errors) {
errors.checkFatal(
Files.isDirectory(cli.rootDir), "Not a valid root directory: %s", cli.rootDir);
- var injector =
- Guice.createInjector(
- new SqrlInjector(
- errors,
- cli.rootDir,
- getTargetFolder(),
- sqrlConfig,
- getGoal(),
- cli.internalTestExec));
-
- var engineHolder = injector.getInstance(ExecutionEnginesHolder.class);
+ var context = new AnnotationConfigApplicationContext();
+ context.registerBean(ErrorCollector.class, () -> errors);
+ context.registerBean("rootDir", Path.class, () -> cli.rootDir);
+ context.registerBean("targetDir", Path.class, () -> getTargetFolder());
+ context.registerBean(PackageJson.class, () -> sqrlConfig);
+ context.registerBean(ExecutionGoal.class, () -> getGoal());
+ context.registerBean("internalTestExec", Boolean.class, () -> cli.internalTestExec);
+ context.register(SqrlInjector.class);
+ context.refresh();
+
+ var engineHolder = context.getBean(ExecutionEnginesHolder.class);
engineHolder.initEnabledEngines();
if (getGoal() == ExecutionGoal.COMPILE) {
formatter.phaseStart("Processing dependencies");
}
- var packager = injector.getInstance(Packager.class);
+ var packager = context.getBean(Packager.class);
packager.preprocess(errors.withLocation(ErrorPrefix.CONFIG.resolve(PACKAGE_JSON)));
if (errors.hasErrors()) {
return;
@@ -85,7 +85,7 @@ protected void compile(ErrorCollector errors) {
if (getGoal() == ExecutionGoal.COMPILE) {
formatter.phaseStart("Compiling SQRL script");
}
- var compilationProcess = injector.getInstance(CompilationProcess.class);
+ var compilationProcess = context.getBean(CompilationProcess.class);
var testDir = sqrlConfig.getTestConfig().getTestDir(cli.rootDir);
testDir.ifPresent(this::validateTestPath);
diff --git a/sqrl-cli/src/main/java/com/datasqrl/cli/BaseCmd.java b/sqrl-cli/src/main/java/com/datasqrl/cli/BaseCmd.java
index a0821e3294..75dbcd80cc 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/cli/BaseCmd.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/cli/BaseCmd.java
@@ -19,10 +19,10 @@
import com.datasqrl.error.CollectedException;
import com.datasqrl.error.ErrorCollector;
import com.datasqrl.error.ErrorPrinter;
-import com.google.inject.ProvisionException;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.SneakyThrows;
+import org.springframework.beans.factory.BeanCreationException;
import picocli.CommandLine.IExitCodeGenerator;
import picocli.CommandLine.ParentCommand;
@@ -44,7 +44,7 @@ public void run() {
runInternal(collector);
cli.statusHook.onSuccess(collector);
- } catch (ProvisionException | CollectedException e) {
+ } catch (BeanCreationException | CollectedException e) {
var ce = unwrapCollectedException(e);
if (ce.isInternalError()) {
ce.printStackTrace();
@@ -87,26 +87,25 @@ public int getExitCode() {
}
/**
- * Unwraps {@link CollectedException} from Guice {@link ProvisionException} wrappers to provide
- * clean error messages.
+ * Unwraps {@link CollectedException} from Spring {@link BeanCreationException} wrappers to
+ * provide clean error messages.
*
* Validation errors during dependency injection (e.g., in pipeline configuration) are thrown
- * as {@link CollectedException} with clear messages. Guice wraps them in {@link
- * ProvisionException}, obscuring the original error with verbose DI stack traces.
+ * as {@link CollectedException} with clear messages. Spring wraps them in multiple layers of
+ * {@link BeanCreationException}, obscuring the original error with verbose DI stack traces.
*
* @param e the runtime exception that may be a CollectedException or contain one as a cause
* @return the unwrapped CollectedException
* @throws RuntimeException the original exception if it doesn't contain a CollectedException
*/
private CollectedException unwrapCollectedException(RuntimeException e) {
- if (e instanceof CollectedException ce) {
- return ce;
- }
-
- if (e.getCause() instanceof CollectedException ce) {
- return ce;
+ Throwable current = e;
+ while (current != null) {
+ if (current instanceof CollectedException ce) {
+ return ce;
+ }
+ current = current.getCause();
}
-
throw e;
}
}
diff --git a/sqrl-cli/src/main/java/com/datasqrl/cli/DatasqrlRun.java b/sqrl-cli/src/main/java/com/datasqrl/cli/DatasqrlRun.java
index bbadc13504..bfb77b64e3 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/cli/DatasqrlRun.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/cli/DatasqrlRun.java
@@ -21,20 +21,14 @@
import static com.datasqrl.env.EnvVariableNames.POSTGRES_USERNAME;
import com.datasqrl.config.PackageJson;
-import com.datasqrl.engine.server.VertxEngineFactory;
+import com.datasqrl.env.GlobalEnvironmentStore;
import com.datasqrl.flinkrunner.EnvVarResolver;
import com.datasqrl.flinkrunner.SqrlRunner;
-import com.datasqrl.graphql.HttpServerVerticle;
import com.datasqrl.graphql.SqrlObjectMapper;
-import com.datasqrl.graphql.config.ServerConfigUtil;
+import com.datasqrl.graphql.SqrlServerApplication;
import com.datasqrl.graphql.server.ModelContainer;
import com.datasqrl.util.ConfigLoaderUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
-import io.micrometer.prometheusmetrics.PrometheusConfig;
-import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;
-import io.vertx.core.Vertx;
-import io.vertx.core.VertxOptions;
-import io.vertx.micrometer.MicrometerMetricsFactory;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -69,6 +63,8 @@
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
@Slf4j
public class DatasqrlRun {
@@ -82,8 +78,8 @@ public class DatasqrlRun {
private final ObjectMapper mapper;
@Nullable private final CountDownLatch shutdownLatch;
- private Vertx vertx;
private TableResult tableResult;
+ private ConfigurableApplicationContext springContext;
private DatasqrlRun(
Path planDir,
@@ -113,7 +109,7 @@ public TableResult run() {
initPostgres();
initKafka();
- startVertx();
+ startServer();
tableResult = runFlinkJob();
if (shutdownLatch != null) {
@@ -166,8 +162,9 @@ private void closeCommon(Runnable closeFlinkJob) {
// allow failure if job already ended
}
}
- if (vertx != null) {
- vertx.close();
+
+ if (springContext != null) {
+ springContext.close();
}
// Signal shutdown to release the hold
@@ -274,7 +271,7 @@ private String getenv(String key) {
}
@SneakyThrows
- private void startVertx() {
+ private void startServer() {
var vertxJson = planDir.resolve("vertx.json").toFile();
if (!vertxJson.exists()) {
return;
@@ -282,36 +279,20 @@ private void startVertx() {
var rootGraphqlModel = mapper.readValue(vertxJson, ModelContainer.class).models;
if (rootGraphqlModel == null || rootGraphqlModel.isEmpty()) {
- return; // no graphql server queries
+ return;
}
- var vertxConfigJson = planDir.resolve("vertx-config.json").toFile();
- if (!vertxConfigJson.exists()) {
- throw new IllegalStateException(
- "Server config JSON '%s' does not exist".formatted(vertxConfigJson));
- }
+ GlobalEnvironmentStore.putAll(env);
- Map json = mapper.readValue(vertxConfigJson, Map.class);
- var baseServerConfig = ServerConfigUtil.fromConfigMap(json);
-
- var serverConfig = ServerConfigUtil.mergeConfigs(baseServerConfig, vertxConfig());
- var serverVerticle = new HttpServerVerticle(serverConfig, rootGraphqlModel, planDir);
- var prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
- var metricsOptions =
- new MicrometerMetricsFactory(prometheusMeterRegistry).newOptions().setEnabled(true);
-
- vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(metricsOptions));
- vertx
- .deployVerticle(serverVerticle)
- .onComplete(
- res -> {
- if (res.succeeded()) {
- log.info("Vertx deployment succeeded. ID: {}", res.result());
- } else {
- log.error("Vertx deployment failed", res.cause());
- vertx.close().onComplete(v -> System.exit(1));
- }
- });
+ var app = new SpringApplication(SqrlServerApplication.class);
+ app.setDefaultProperties(
+ Map.of(
+ "spring.main.banner-mode", "off",
+ "logging.level.root", "WARN",
+ "logging.level.com.datasqrl", "INFO"));
+
+ springContext = app.run();
+ log.info("Spring Boot GraphQL server started");
}
@SneakyThrows
@@ -351,12 +332,4 @@ private Tuple2 attachCreationTime(Path path) {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
return Tuple2.of(path, attrs.creationTime());
}
-
- private Map vertxConfig() {
- return sqrlConfig
- .getEngines()
- .getEngineConfig(VertxEngineFactory.ENGINE_NAME)
- .map(PackageJson.EngineConfig::getConfig)
- .orElse(null);
- }
}
diff --git a/sqrl-cli/src/main/java/com/datasqrl/cli/SubscriptionClient.java b/sqrl-cli/src/main/java/com/datasqrl/cli/SubscriptionClient.java
index 0ff4dc80eb..29124c839e 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/cli/SubscriptionClient.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/cli/SubscriptionClient.java
@@ -17,22 +17,20 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import io.vertx.core.Future;
-import io.vertx.core.MultiMap;
-import io.vertx.core.Vertx;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.core.http.WebSocket;
-import io.vertx.core.http.WebSocketConnectOptions;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.WebSocket;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
-// Simplified example WebSocket client code using Vert.x
@RequiredArgsConstructor
@Slf4j
public class SubscriptionClient implements AutoCloseable {
@@ -40,7 +38,7 @@ public class SubscriptionClient implements AutoCloseable {
private static final long INITIAL_DELAY_MS = 100;
private final ObjectMapper objectMapper = new ObjectMapper();
- private final Vertx vertx = Vertx.vertx();
+ private final HttpClient httpClient = HttpClient.newHttpClient();
private final CompletableFuture connectedFuture = new CompletableFuture<>();
@Getter private final List messages = new ArrayList<>();
@@ -73,88 +71,72 @@ private void attemptConnection(int attempt) {
MAX_RETRIES,
name,
delay);
- vertx.setTimer(delay, id -> connectWebSocket(attempt));
- } else {
- connectWebSocket(attempt);
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ connectedFuture.completeExceptionally(e);
+ return;
+ }
}
+
+ connectWebSocket(attempt);
}
private void connectWebSocket(int attempt) {
- /* 1. Collect handshake headers */
- var headerMap = MultiMap.caseInsensitiveMultiMap();
- headers.forEach(headerMap::add);
-
- /* 2. Describe the connection */
- var opts =
- new WebSocketConnectOptions()
- .setHost("localhost")
- .setPort(8888)
- .setURI("/%s/graphql".formatted(version))
- .addSubProtocol("graphql-transport-ws") // or "graphql-ws"
- .setHeaders(headerMap);
-
- /* 3. Open the socket with the new WebSocketClient API */
- var wsClient = vertx.createWebSocketClient();
- wsClient
- .connect(opts)
- .onSuccess(
- ws -> {
- this.webSocket = ws;
- log.info("WebSocket opened for subscription: {}", name);
-
- // Set a message handler for incoming messages
- ws.handler(this::handleTextMessage);
-
- // Send initial connection message
- sendConnectionInit()
- .onComplete(success -> connectedFuture.complete(null))
- .onFailure(connectedFuture::completeExceptionally);
- })
- .onFailure(
- throwable -> {
- if (attempt < MAX_RETRIES - 1) {
- log.warn(
- "Failed to open WebSocket for subscription: {} (attempt {}/{}), retrying...",
- name,
- attempt + 1,
- MAX_RETRIES,
- throwable);
- attemptConnection(attempt + 1);
+ var uri = URI.create("ws://localhost:8888/%s/graphql".formatted(version));
+ var builder = httpClient.newWebSocketBuilder();
+ builder.subprotocols("graphql-transport-ws");
+ headers.forEach(builder::header);
+
+ builder
+ .buildAsync(uri, new WebSocketListener())
+ .whenComplete(
+ (ws, throwable) -> {
+ if (throwable != null) {
+ if (attempt < MAX_RETRIES - 1) {
+ log.warn(
+ "Failed to open WebSocket for subscription: {} (attempt {}/{}), retrying...",
+ name,
+ attempt + 1,
+ MAX_RETRIES,
+ throwable);
+ attemptConnection(attempt + 1);
+ } else {
+ log.error(
+ "Failed to open WebSocket for subscription: {} after {} attempts",
+ name,
+ MAX_RETRIES,
+ throwable);
+ connectedFuture.completeExceptionally(throwable);
+ }
} else {
- log.error(
- "Failed to open WebSocket for subscription: {} after {} attempts",
- name,
- MAX_RETRIES,
- throwable);
- connectedFuture.completeExceptionally(throwable);
+ this.webSocket = ws;
+ log.info("WebSocket opened for subscription: {}", name);
+ sendConnectionInit();
}
});
}
- private Future sendConnectionInit() {
- return sendMessage(Map.of("type", "connection_init"));
+ private void sendConnectionInit() {
+ sendMessage(Map.of("type", "connection_init"));
}
- private Future sendSubscribe() {
- Map payload =
- Map.of(
- // "operationName", "breakMe",
- "query", query);
+ private void sendSubscribe() {
+ Map payload = Map.of("query", query);
Map message =
Map.of("id", System.nanoTime(), "type", "subscribe", "payload", payload);
- return sendMessage(message);
+ sendMessage(message);
}
@SneakyThrows
- private Future sendMessage(Map message) {
+ private void sendMessage(Map message) {
String json = objectMapper.writeValueAsString(message);
System.out.println("Sending: " + json);
- return webSocket.writeTextMessage(json);
+ webSocket.sendText(json, true);
}
- private void handleTextMessage(Buffer buffer) {
- var data = buffer.toString();
- // Handle the incoming messages
+ private void handleTextMessage(String data) {
System.out.println("Data: " + data);
Map message;
try {
@@ -175,13 +157,11 @@ private void handleTextMessage(Buffer buffer) {
var type = (String) message.get("type");
if ("connection_ack".equals(type)) {
- // Connection acknowledged, send the subscribe message
sendSubscribe();
connectedFuture.complete(null);
} else if ("complete".equals(type)) {
// Subscription complete
} else if ("error".equals(type)) {
- // Handle error
System.err.println("Error message received: " + data);
throw new RuntimeException("Error data: " + data);
} else {
@@ -191,16 +171,46 @@ private void handleTextMessage(Buffer buffer) {
@Override
public void close() throws Exception {
- if (webSocket != null && !webSocket.isClosed()) {
- // Send 'complete' message to close the subscription properly
- waitCompletion(sendMessage(Map.of("id", System.nanoTime(), "type", "complete")));
- waitCompletion(webSocket.close());
+ if (webSocket != null) {
+ sendMessage(Map.of("id", System.nanoTime(), "type", "complete"));
+ webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "closing").join();
}
- waitCompletion(vertx.close());
}
- @SneakyThrows
- private void waitCompletion(Future> future) {
- future.toCompletionStage().toCompletableFuture().get();
+ private class WebSocketListener implements WebSocket.Listener {
+ private final StringBuilder textBuffer = new StringBuilder();
+
+ @Override
+ public void onOpen(WebSocket webSocket) {
+ webSocket.request(1);
+ }
+
+ @Override
+ public CompletionStage> onText(WebSocket webSocket, CharSequence data, boolean last) {
+ textBuffer.append(data);
+ if (last) {
+ handleTextMessage(textBuffer.toString());
+ textBuffer.setLength(0);
+ }
+ webSocket.request(1);
+ return null;
+ }
+
+ @Override
+ public CompletionStage> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
+ webSocket.request(1);
+ return null;
+ }
+
+ @Override
+ public CompletionStage> onClose(WebSocket webSocket, int statusCode, String reason) {
+ log.info("WebSocket closed: {} - {}", statusCode, reason);
+ return null;
+ }
+
+ @Override
+ public void onError(WebSocket webSocket, Throwable error) {
+ log.error("WebSocket error for subscription: {}", name, error);
+ }
}
}
diff --git a/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java b/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java
index 4777b2c237..6a7b33ff32 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/compile/CompilationProcess.java
@@ -35,13 +35,17 @@
import com.datasqrl.planner.Sqrl2FlinkSQLTranslator;
import com.datasqrl.planner.dag.DAGPlanner;
import com.datasqrl.util.ServiceLoaderDiscovery;
-import com.google.inject.Inject;
+import jakarta.inject.Inject;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+@Component
+@Lazy
@AllArgsConstructor(onConstructor_ = @Inject)
public class CompilationProcess {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/compile/DagWriter.java b/sqrl-cli/src/main/java/com/datasqrl/compile/DagWriter.java
index d57f95b044..2a7d40ea28 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/compile/DagWriter.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/compile/DagWriter.java
@@ -22,7 +22,7 @@
import com.datasqrl.planner.dag.PipelineDAG;
import com.datasqrl.serializer.Deserializer;
import com.google.common.io.Resources;
-import com.google.inject.Inject;
+import jakarta.inject.Inject;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -32,7 +32,9 @@
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
+@Component
@AllArgsConstructor(onConstructor_ = @Inject)
public class DagWriter {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/packager/FilePreprocessingPipeline.java b/sqrl-cli/src/main/java/com/datasqrl/packager/FilePreprocessingPipeline.java
index 31e974cdb2..d76c19d032 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/packager/FilePreprocessingPipeline.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/packager/FilePreprocessingPipeline.java
@@ -25,7 +25,7 @@
import com.datasqrl.packager.preprocess.Preprocessor;
import com.datasqrl.util.FilenameAnalyzer;
import com.datasqrl.util.NameUtil;
-import com.google.inject.Inject;
+import jakarta.inject.Inject;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
@@ -42,12 +42,14 @@
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
+import org.springframework.stereotype.Component;
/**
* Helps to preprocess files which means 1) copying all relevant files into the build directory
* (preserving relative paths) 2) running all registered preprocessors for more elaborate
* preprocessing than just copying files.
*/
+@Component
@SuppressWarnings("NullableProblems")
@AllArgsConstructor(onConstructor_ = @Inject)
public class FilePreprocessingPipeline {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/packager/Packager.java b/sqrl-cli/src/main/java/com/datasqrl/packager/Packager.java
index d6a1d4be8a..01f0516e6b 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/packager/Packager.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/packager/Packager.java
@@ -35,7 +35,7 @@
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
-import com.google.inject.Inject;
+import jakarta.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
@@ -53,7 +53,9 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+@Component
@Getter
@AllArgsConstructor(onConstructor_ = @Inject)
public class Packager {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/CopyStaticDataPreprocessor.java b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/CopyStaticDataPreprocessor.java
index 6935049b69..02dbbb435d 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/CopyStaticDataPreprocessor.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/CopyStaticDataPreprocessor.java
@@ -32,11 +32,13 @@
import java.util.Set;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
/**
* Copies {@code .jsonl}, {@code .csv}, and {@code .avro} files (optionally with compression) to the
* data folder, so they can be useb by Flink.
*/
+@Component
@Slf4j
public class CopyStaticDataPreprocessor implements Preprocessor {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JBangPreprocessor.java b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JBangPreprocessor.java
index 2813aa6264..bb5253c601 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JBangPreprocessor.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JBangPreprocessor.java
@@ -17,7 +17,7 @@
import com.datasqrl.packager.FilePreprocessingPipeline;
import com.datasqrl.util.JBangRunner;
-import com.google.inject.Inject;
+import jakarta.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -27,7 +27,9 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.io.FilenameUtils;
+import org.springframework.stereotype.Component;
+@Component
@RequiredArgsConstructor(onConstructor_ = @Inject)
@Slf4j
public class JBangPreprocessor extends UdfManifestPreprocessor {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JarPreprocessor.java b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JarPreprocessor.java
index ce5b61df34..3a440f497a 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JarPreprocessor.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/packager/preprocess/JarPreprocessor.java
@@ -18,10 +18,12 @@
import com.datasqrl.packager.FilePreprocessingPipeline;
import java.nio.file.Path;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
/*
* Reads a jar and creates sqrl manifest entries in the build directory
*/
+@Component
@Slf4j
public class JarPreprocessor extends UdfManifestPreprocessor {
diff --git a/sqrl-cli/src/main/java/com/datasqrl/util/SqrlInjector.java b/sqrl-cli/src/main/java/com/datasqrl/util/SqrlInjector.java
index 7004d54f33..b3838836eb 100644
--- a/sqrl-cli/src/main/java/com/datasqrl/util/SqrlInjector.java
+++ b/sqrl-cli/src/main/java/com/datasqrl/util/SqrlInjector.java
@@ -15,133 +15,71 @@
*/
package com.datasqrl.util;
-import com.datasqrl.MainScriptImpl;
-import com.datasqrl.calcite.type.TypeFactory;
import com.datasqrl.canonicalizer.NameCanonicalizer;
-import com.datasqrl.config.ConnectorFactoryFactory;
-import com.datasqrl.config.ConnectorFactoryFactoryImpl;
+import com.datasqrl.config.BuildPath;
import com.datasqrl.config.ExecutionEnginesHolder;
import com.datasqrl.config.PackageJson;
-import com.datasqrl.config.PackageJson.CompilerConfig;
-import com.datasqrl.config.QueryEngineConfigConverter;
-import com.datasqrl.config.QueryEngineConfigConverterImpl;
-import com.datasqrl.config.SqrlCompilerConfiguration;
-import com.datasqrl.config.SqrlConfigPipeline;
+import com.datasqrl.config.RootPath;
import com.datasqrl.config.SqrlConstants;
-import com.datasqrl.engine.pipeline.ExecutionPipeline;
-import com.datasqrl.error.ErrorCollector;
-import com.datasqrl.loaders.ModuleLoader;
-import com.datasqrl.loaders.ModuleLoaderImpl;
+import com.datasqrl.config.TargetPath;
import com.datasqrl.loaders.resolver.FileResourceResolver;
import com.datasqrl.loaders.resolver.ResourceResolver;
-import com.datasqrl.packager.preprocess.CopyStaticDataPreprocessor;
-import com.datasqrl.packager.preprocess.JBangPreprocessor;
-import com.datasqrl.packager.preprocess.JarPreprocessor;
-import com.datasqrl.packager.preprocess.Preprocessor;
-import com.datasqrl.plan.MainScript;
import com.datasqrl.plan.validate.ExecutionGoal;
-import com.google.inject.AbstractModule;
-import com.google.inject.Injector;
-import com.google.inject.Provides;
-import com.google.inject.Singleton;
-import com.google.inject.multibindings.Multibinder;
-import com.google.inject.name.Named;
import java.nio.file.Path;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-
-public class SqrlInjector extends AbstractModule {
-
- private final ErrorCollector errors;
- private final Path rootDir;
- private final Path buildDir;
- private final Path targetDir;
- private final PackageJson sqrlConfig;
- private final ExecutionGoal goal;
- private final boolean internalTestExec;
-
- public SqrlInjector(
- ErrorCollector errors,
- Path rootDir,
- Path targetDir,
- PackageJson sqrlConfig,
- ExecutionGoal goal,
- boolean internalTestExec) {
- this.errors = errors;
- this.rootDir = rootDir;
- this.buildDir = rootDir.resolve(SqrlConstants.BUILD_DIR_NAME);
- this.targetDir = targetDir;
- this.sqrlConfig = sqrlConfig;
- this.goal = goal;
- this.internalTestExec = internalTestExec;
- }
-
- @Override
- public void configure() {
- bind(RelDataTypeFactory.class).to(TypeFactory.class);
- bind(MainScript.class).to(MainScriptImpl.class);
- bind(ExecutionPipeline.class).to(SqrlConfigPipeline.class);
- bind(ModuleLoader.class).to(ModuleLoaderImpl.class);
- bind(CompilerConfig.class).to(SqrlCompilerConfiguration.class);
- bind(ConnectorFactoryFactory.class).to(ConnectorFactoryFactoryImpl.class);
- bind(QueryEngineConfigConverter.class).to(QueryEngineConfigConverterImpl.class);
-
- Multibinder binder = Multibinder.newSetBinder(binder(), Preprocessor.class);
- binder.addBinding().to(CopyStaticDataPreprocessor.class);
- binder.addBinding().to(JBangPreprocessor.class);
- binder.addBinding().to(JarPreprocessor.class);
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ComponentScan(basePackages = "com.datasqrl")
+public class SqrlInjector {
+
+ @Bean
+ @Qualifier("buildDir")
+ public Path buildDir(@Qualifier("rootDir") Path rootDir) {
+ return rootDir.resolve(SqrlConstants.BUILD_DIR_NAME);
}
- @Provides
- @Named("buildDir")
- public Path provideBuildDir() {
- return buildDir;
+ @Bean
+ public BuildPath buildPath(
+ @Qualifier("buildDir") Path buildDir, @Qualifier("targetDir") Path targetDir) {
+ return new BuildPath(buildDir, targetDir);
}
- @Provides
- @Named("rootDir")
- public Path provideRootDir() {
- return rootDir;
- }
-
- @Provides
- @Named("targetDir")
- public Path provideTargetDir() {
- return targetDir;
- }
-
- @Provides
- public ResourceResolver provideResourceResolver() {
+ @Bean
+ public ResourceResolver resourceResolver(@Qualifier("buildDir") Path buildDir) {
return new FileResourceResolver(buildDir);
}
- @Provides
- public NameCanonicalizer provideNameCanonicalizer() {
+ @Bean
+ public NameCanonicalizer nameCanonicalizer() {
return NameCanonicalizer.SYSTEM;
}
- @Provides
- @Singleton
- public JBangRunner provideJBangRunner() {
+ @Bean
+ public JBangRunner jBangRunner(@Qualifier("internalTestExec") Boolean internalTestExec) {
return internalTestExec ? JBangRunner.disabled() : JBangRunner.create();
}
- @Provides
- public PackageJson provideSqrlConfig() {
- return sqrlConfig;
- }
-
- @Provides
- public ExecutionGoal provideExecutionGoal() {
- return goal;
+ @Bean
+ public ExecutionEnginesHolder executionEnginesHolder(
+ com.datasqrl.error.ErrorCollector errors,
+ ApplicationContext applicationContext,
+ PackageJson sqrlConfig,
+ ExecutionGoal goal) {
+ return new ExecutionEnginesHolder(
+ errors, applicationContext, sqrlConfig, goal == ExecutionGoal.TEST);
}
- @Provides
- public ErrorCollector provideErrorCollector() {
- return errors;
+ @Bean
+ public RootPath rootPath(@Qualifier("rootDir") Path rootDir) {
+ return new RootPath(rootDir);
}
- @Provides
- public ExecutionEnginesHolder provideExecutionEnginesHolder(Injector injector) {
- return new ExecutionEnginesHolder(errors, injector, sqrlConfig, goal == ExecutionGoal.TEST);
+ @Bean
+ public TargetPath targetPath(@Qualifier("targetDir") Path targetDir) {
+ return new TargetPath(targetDir);
}
}
diff --git a/sqrl-cli/src/test/java/com/datasqrl/engine/server/GenericJavaServerEngineTest.java b/sqrl-cli/src/test/java/com/datasqrl/engine/server/GenericJavaServerEngineTest.java
deleted file mode 100644
index 6d586d5e8a..0000000000
--- a/sqrl-cli/src/test/java/com/datasqrl/engine/server/GenericJavaServerEngineTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright © 2021 DataSQRL (contact@datasqrl.com)
- *
- * Licensed 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 com.datasqrl.engine.server;
-
-import static com.datasqrl.graphql.SqrlObjectMapper.MAPPER;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.datasqrl.config.PackageJson.EmptyEngineConfig;
-import com.datasqrl.config.QueryEngineConfigConverter;
-import com.datasqrl.graphql.config.ServerConfigUtil;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import java.util.List;
-import java.util.Map;
-import lombok.SneakyThrows;
-import org.junit.jupiter.api.Test;
-
-class GenericJavaServerEngineTest {
-
- GenericJavaServerEngine underTest =
- new GenericJavaServerEngine("", new EmptyEngineConfig(""), new DummyConverter()) {};
-
- @Test
- void test() {
- // innermost object: a single pub-/sec key
- var config = getConfigMap();
-
- var defaultConfig = underTest.readDefaultConfig();
- assertThat(defaultConfig.getJwtAuth()).isNull();
-
- var result = ServerConfigUtil.mergeConfigs(defaultConfig, config);
-
- assertThat(result).isNotNull();
- assertThat(result.getJwtAuth()).isNotNull();
- assertThat(result.getJwtAuth().getPubSecKeys()).isNotNull().isNotEmpty();
- assertThat(result.getJwtAuth().getJWTOptions()).isNotNull();
- assertThat(result.getJwtAuth().getJWTOptions().getIssuer()).isEqualTo("my-test-issuer");
- }
-
- @Test
- @SneakyThrows
- void givenJwtConfiguration_whenConfigMerged_thenBuffersAreStringValues() {
- // Create JWT configuration with buffer as simple string (not complex object)
- var config = getConfigMap();
-
- // Test the configuration merging that would happen during serverConfig() generation
- var defaultConfig = underTest.readDefaultConfig();
- var mergedConfig = ServerConfigUtil.mergeConfigs(defaultConfig, config);
-
- // Serialize the merged configuration to JSON string and parse back
- var serializedJson = MAPPER.writeValueAsString(mergedConfig);
- JsonNode configNode = MAPPER.readTree(serializedJson);
- JsonNode pubSecKeysNode = configNode.path("jwtAuth").path("pubSecKeys");
-
- assertThat(pubSecKeysNode.isArray()).isTrue();
- assertThat(pubSecKeysNode.size()).isEqualTo(1);
-
- JsonNode firstKey = pubSecKeysNode.get(0);
- JsonNode bufferNode = firstKey.path("buffer");
-
- // Verify buffer is a simple string value, not a complex object with "bytes" field
- assertThat(bufferNode.isTextual()).isTrue();
- // Note: VertxModule may process base64 buffers, but it should still be a string, not a complex
- // object
- assertThat(bufferNode.asText()).contains("dGVzdFNlY3JldA");
-
- // Ensure buffer is not a complex object (would have been corrupted by old VertxModule usage)
- assertThat(bufferNode.isObject()).isFalse();
- assertThat(bufferNode.has("bytes")).isFalse();
- }
-
- private static Map getConfigMap() {
- var pubSecKey =
- Map.of(
- "algorithm", "HS256",
- "buffer", "dGVzdFNlY3JldA==");
-
- var jwtOptions =
- Map.of(
- "issuer",
- "my-test-issuer",
- "audience",
- List.of("my-test-audience"),
- "expiresInSeconds",
- 3600,
- "leeway",
- 30);
-
- var jwtAuth = Map.of("pubSecKeys", List.of(pubSecKey), "jwtOptions", jwtOptions);
-
- return Map.of("jwtAuth", jwtAuth);
- }
-
- private static class DummyConverter implements QueryEngineConfigConverter {
-
- @Override
- public List convertConfigsToJson() {
- return List.of();
- }
- }
-}
diff --git a/sqrl-cli/src/test/java/com/datasqrl/packager/config/ServerConfigTemplateTest.java b/sqrl-cli/src/test/java/com/datasqrl/packager/config/ServerConfigTemplateTest.java
deleted file mode 100644
index 0c079f4197..0000000000
--- a/sqrl-cli/src/test/java/com/datasqrl/packager/config/ServerConfigTemplateTest.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright © 2021 DataSQRL (contact@datasqrl.com)
- *
- * Licensed 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 com.datasqrl.packager.config;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.datasqrl.graphql.SqrlObjectMapper;
-import com.datasqrl.graphql.config.ServerConfigUtil;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import java.io.File;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.Objects;
-import lombok.SneakyThrows;
-import org.junit.jupiter.api.Test;
-
-class ServerConfigTemplateTest {
- private static final File TEMPLATE = new File("src/main/resources/templates/server-config.json");
-
- private static ObjectMapper mapper = SqrlObjectMapper.MAPPER;
-
- @SuppressWarnings("unchecked")
- @Test
- @SneakyThrows
- void test() {
- var original = mapper.readValue(TEMPLATE, Map.class);
- var afterParsing = ServerConfigUtil.fromConfigMap(original);
- assertThat(mapper.convertValue(afterParsing, Map.class))
- .usingRecursiveComparison()
- .withComparatorForType(numberComparatorIgnoringType(), Number.class)
- .isEqualTo(original);
- }
-
- private static Comparator numberComparatorIgnoringType() {
- return (n1, n2) -> {
- if (n1 == null && n2 == null) return 0;
- if (n1 == null) return -1;
- if (n2 == null) return 1;
- return Double.compare(n1.doubleValue(), n2.doubleValue());
- };
- }
-
- @SneakyThrows
- public static void main(String[] args) {
- var original = mapper.readValue(TEMPLATE, Map.class);
- var afterParsing = ServerConfigUtil.fromConfigMap(original);
-
- if (!Objects.equals(original, mapper.convertValue(afterParsing, Map.class))) {
- mapper
- .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
- .writerWithDefaultPrettyPrinter()
- .writeValue(TEMPLATE, afterParsing);
- }
- }
-}
diff --git a/sqrl-planner/pom.xml b/sqrl-planner/pom.xml
index a2375781fc..7a56dcc2af 100644
--- a/sqrl-planner/pom.xml
+++ b/sqrl-planner/pom.xml
@@ -37,6 +37,11 @@
jakarta.annotation-api
${jakarta.annotation.version}