From 37af507c8db80ceac2104093eb60e4888cb65b22 Mon Sep 17 00:00:00 2001 From: An Phi Date: Mon, 8 Jul 2024 11:07:13 -0400 Subject: [PATCH] REPL bug fixes and cleanup for DataCube (#2945) * repl: fix a bug with relational DB table completer * repl: fix a bug tabbing when input is empty crashing the repl * repl: make sure SIGINT and SIGTERM not crash the app * repl: support loading table with specific name * repl: support dropping table * repl: add command description to help * repl: make opening brace part of function completer * repl: add README with quickstart and development guide * repl: refactor DataCube API for REPL server * relation: fix bug with relation store access not properly handling Date type * relation: support empty col spec array parsing to be consistent with Pure * relation: bug fixes for selecting column with spaces for duckdb * repl: fix tests --- .../legend-engine-repl/README.md | 72 +++ .../docs/repl-debug-setup.png | Bin 0 -> 112459 bytes .../docs/repl-webapp-dev-setup.png | Bin 0 -> 125276 bytes .../engine/repl/autocomplete/Completer.java | 13 +- .../legend/engine/repl/client/Client.java | 77 +++- .../repl/client/jline3/JLine3Parser.java | 2 +- .../legend/engine/repl/core/Command.java | 5 + .../legend/engine/repl/core/Helpers.java | 3 + .../engine/repl/core/ReplExtension.java | 2 - .../engine/repl/core/commands/Debug.java | 6 + .../engine/repl/core/commands/Execute.java | 37 +- .../legend/engine/repl/core/commands/Ext.java | 6 + .../engine/repl/core/commands/Graph.java | 8 +- .../engine/repl/core/commands/Help.java | 16 +- .../core/legend/LocalLegendInterface.java | 4 +- .../legend/engine/repl/TestCompleter.java | 8 +- .../legend-engine-repl-relational/pom.xml | 131 +++++- .../relational/RelationalReplExtension.java | 24 +- .../RelationalCompleterExtension.java | 4 + .../repl/relational/commands/Cache.java | 11 +- .../engine/repl/relational/commands/DB.java | 6 + .../engine/repl/relational/commands/Drop.java | 122 +++++ .../engine/repl/relational/commands/Load.java | 14 +- .../engine/repl/relational/commands/Show.java | 47 +- .../relational/httpServer/ReplGridServer.java | 421 ------------------ .../relational/server/DataCubeHelpers.java | 166 +++++++ .../repl/relational/server/REPLServer.java | 104 +++++ .../relational/server/REPLServerHelpers.java | 278 ++++++++++++ .../handler/DataCubeInfrastructure.java | 98 ++++ .../server/handler/DataCubeQueryBuilder.java | 217 +++++++++ .../server/handler/DataCubeQueryExecutor.java | 62 +++ .../server/model/DataCubeExecutionInput.java | 22 + .../server/model/DataCubeExecutionResult.java | 20 + .../model/DataCubeGetBaseQueryResult.java | 25 ++ .../model/DataCubeGetQueryCodeBatchInput.java | 25 ++ .../DataCubeGetQueryCodeBatchResult.java | 22 + .../model/DataCubeGetQueryCodeInput.java | 23 + ...taCubeGetQueryRelationReturnTypeInput.java | 22 + .../server/model/DataCubeParseQueryInput.java | 21 + .../server/model/DataCubeQuery.java | 31 ++ .../server/model/DataCubeQueryColumn.java | 27 ++ ...ataCubeQueryConfigurationDeserializer.java | 32 ++ .../server/model/DataCubeQuerySource.java | 31 ++ .../DataCubeQuerySourceREPLExecutedQuery.java | 19 + .../model/DataCubeQueryTypeaheadInput.java | 21 + .../legend/engine/repl/TestCompleter.java | 20 +- .../engine/repl/TestDataCubeHelpers.java | 287 ++++++++++++ .../legend/engine/repl/TestGridServer.java | 144 ------ .../from/antlr4/core/M3ParserGrammar.g4 | 2 +- ...lational_DuckDB_RelationFunctions_PCT.java | 6 +- .../driver/vendors/duckdb/DuckDBCommands.java | 6 + .../sqlQueryToString/duckdbExtension.pure | 17 + .../commands/RelationalDatabaseCommands.java | 5 + .../RelationalCompilerExtension.java | 12 + 54 files changed, 2137 insertions(+), 667 deletions(-) create mode 100644 legend-engine-config/legend-engine-repl/README.md create mode 100644 legend-engine-config/legend-engine-repl/docs/repl-debug-setup.png create mode 100644 legend-engine-config/legend-engine-repl/docs/repl-webapp-dev-setup.png create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Drop.java delete mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/httpServer/ReplGridServer.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/DataCubeHelpers.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServer.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServerHelpers.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeInfrastructure.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryBuilder.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryExecutor.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionResult.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetBaseQueryResult.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchResult.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryRelationReturnTypeInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeParseQueryInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuery.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryColumn.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryConfigurationDeserializer.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySource.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySourceREPLExecutedQuery.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryTypeaheadInput.java create mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestDataCubeHelpers.java delete mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestGridServer.java diff --git a/legend-engine-config/legend-engine-repl/README.md b/legend-engine-config/legend-engine-repl/README.md new file mode 100644 index 00000000000..94a981e854f --- /dev/null +++ b/legend-engine-config/legend-engine-repl/README.md @@ -0,0 +1,72 @@ +# Legend REPL + +## Quick Start + +Run the REPL either in IDE (IntelliJ) or assembling a JAR and run the JAR. Within the REPL, to start + +> For autocomplete to work properly, it is recommended to run the REPL in a terminal; integrated terminal +> in IDE often override hotkeys / keybindings that are used by the REPL. _Developers are also recommended +> to work in a terminal to better test the interactions._ + +```sh +help # to see the list of commands + +# first load a CSV file into local DuckDB +load data.csv local::DuckDuckConnection test1 +# then show the data +#>{local::DuckDuckDatabase.test1}#->sort([])->from(local::DuckDuckRuntime) + +# to show the result grid in GUI mode +show +# to debug when error occurs, toggle debug mode +debug +``` + +## Configuration + +```shell +java \ + # Specify ag-grid license key for full enterprise functionalities support + -Dlegend.repl.dataCube.gridLicenseKey=YOUR_LICENSE_KEY \ + + # [DEVELOPMENT] Specify the base URL for the development instance of the web application + # this is needed to bypass CORS + -Dlegend.repl.dataCube.devWebAppBaseUrl=http://localhost:9005 \ + + # [DEVELOPMENT] By default, the port is randomized, but for development, the port needs + # to be fixed to allow the web application to connect to the REPL + -Dlegend.repl.dataCube.devPort=9006 \ + + -jar legend-engine-repl.jar +``` + +## Developer Guide + +### REPL Development Setup + +To debug the REPL, you can either run it in Debug mode in IntelliJ, which would compromise certain features, such as autocomplete +or you can run the REPL in a terminal. You can then setup a Remote JVM Debugger from IntelliJ to attach to the REPL instance. +The exact command to run it can be copied from the command run by IntelliJ (and removing some IntelliJ specifics from the classpath), for example: + +```shell +# NOTE: this command has a very long classpath! +java -Dfile.encoding=UTF-8 -classpath ... -Xdebug -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 org.finos.legend.engine.repl.relational.client.RClient +``` + +Then, in IntelliJ, start the Remote JVM Debug session with the specified port (5005 like in command above). + +![img.png](docs/repl-debug-setup.png) + +### DataCube Development Setup + +Configure `legend.repl.dataCube.devWebAppBaseUrl` and `legend.repl.dataCube.devPort`. See screenshot below for an example of +how to configure the REPL in IntelliJ + +![img.png](docs/repl-webapp-dev-setup.png) + +Or if in a terminal + +```shell +# NOTE: this command has a very long classpath! +java -Dfile.encoding=UTF-8 -classpath ... -Dlegend.repl.devPort=9006 -Dlegend.repl.devWebAppBaseUrl=http://localhost:9005 org.finos.legend.engine.repl.relational.client.RClient +``` \ No newline at end of file diff --git a/legend-engine-config/legend-engine-repl/docs/repl-debug-setup.png b/legend-engine-config/legend-engine-repl/docs/repl-debug-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..fc4c1b10910d319d80c5525f84bfa347fd6b1ac3 GIT binary patch literal 112459 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�vHp_)d`|LFXTPR%<<>h}(7-*ai{+`~}$mU|Ar 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 ZiRCjCR}B~X-=%?of_8AVe`*&%_zx5@hCKiP literal 0 HcmV?d00001 diff --git a/legend-engine-config/legend-engine-repl/docs/repl-webapp-dev-setup.png b/legend-engine-config/legend-engine-repl/docs/repl-webapp-dev-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2616a63b6285a0017d37db3195fae9342dcd92 GIT binary patch literal 125276 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 extens buildCodeContext + "\n###Pure\n" + "import meta::pure::functions::relation::*;\n" + - "function _pierre::func():Any[*]{\n"; + "function " + REPL_RUN_FUNCTION_SIGNATURE + "{\n"; this.lineOffset = StringUtils.countMatches(header, "\n") + 1; this.handlers = Lists.mutable.with( new FilterHandler(), @@ -158,7 +161,7 @@ else if (topExpression instanceof AppliedFunction) if (currentExpression == topExpression) { // The top function name is being written, propose candidates - return new CompletionResult(getFunctionCandidates(leftCompiledVS, pureModel, null).select(c -> c.startsWith(currentlyTypeFunctionName)).collect(c -> new CompletionItem(c, c))); + return new CompletionResult(getFunctionCandidates(leftCompiledVS, pureModel, null).select(c -> c.startsWith(currentlyTypeFunctionName)).collect(c -> new CompletionItem(c, c + "("))); } else if (handler != null) { @@ -238,7 +241,7 @@ private ValueSpecification parseValueSpecification(String value) { String code = header + value + "\n" + "\n}"; PureModelContextData pureModelContextData = PureGrammarParser.newInstance().parseModel(code); - Function func = (Function) ListIterate.select(pureModelContextData.getElements(), s -> s.getPath().equals("_pierre::func__Any_MANY_")).getFirst(); + Function func = (Function) ListIterate.select(pureModelContextData.getElements(), s -> s.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH)).getFirst(); return func.body.get(0); } @@ -260,14 +263,14 @@ else if (leftType._rawType().getName().equals("String")) } else { - return Lists.mutable.with("count"); + return Lists.mutable.with("count", "joinStrings"); } } else if (org.finos.legend.pure.m3.navigation.type.Type.subTypeOf(leftType._rawType(), pureModel.getType(M3Paths.Number), pureModel.getExecutionSupport().getProcessorSupport())) { if (org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity.isToOne(multiplicity)) { - return Lists.mutable.with("sqrt", "pow", "exp"); + return Lists.mutable.with("abs", "pow", "sqrt", "exp"); } else { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java index 5a933d5a380..8d8f87172e5 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java @@ -24,21 +24,22 @@ import org.finos.legend.engine.repl.client.jline3.JLine3Parser; import org.finos.legend.engine.repl.core.Command; import org.finos.legend.engine.repl.core.ReplExtension; -import org.finos.legend.engine.repl.core.commands.Debug; -import org.finos.legend.engine.repl.core.commands.Execute; -import org.finos.legend.engine.repl.core.commands.Ext; -import org.finos.legend.engine.repl.core.commands.Graph; -import org.finos.legend.engine.repl.core.commands.Help; +import org.finos.legend.engine.repl.core.commands.*; import org.finos.legend.engine.repl.core.legend.LegendInterface; import org.finos.legend.engine.repl.core.legend.LocalLegendInterface; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; +import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; import org.jline.reader.LineReaderBuilder; +import org.jline.reader.UserInterruptException; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; +import static org.jline.jansi.Ansi.ansi; +import static org.jline.reader.LineReader.BLINK_MATCHING_PAREN; + public class Client { private final LegendInterface legendInterface = new LocalLegendInterface(); @@ -50,7 +51,6 @@ public class Client private ModelState state; private final PlanExecutor planExecutor; - public static void main(String[] args) throws Exception { new Client(Lists.mutable.empty(), Lists.mutable.empty(), PlanExecutor.newPlanExecutorBuilder().withAvailableStoreExecutors().build()).loop(); @@ -61,17 +61,14 @@ public static void main(String[] args) throws Exception public Client(MutableList replExtensions, MutableList completerExtensions, PlanExecutor planExecutor) throws Exception { this.replExtensions = replExtensions; - this.completerExtensions = completerExtensions; - this.planExecutor = planExecutor; - this.state = new ModelState(this.legendInterface, this.replExtensions); + this.terminal = TerminalBuilder.terminal(); replExtensions.forEach(e -> e.initialize(this)); - this.terminal = TerminalBuilder.terminal(); - + this.terminal.writer().println(ansi().fgBrightBlack().a("Welcome to the Legend REPL! Press 'Enter' or type 'help' to see the list of available commands.").reset()); this.terminal.writer().println("\n" + Logos.logos.get((int) (Logos.logos.size() * Math.random())) + "\n"); this.commands = replExtensions @@ -89,8 +86,17 @@ public Client(MutableList replExtensions, MutableList at the beginning of line will insert a tab instead of triggering a completion + // which will cause error since the completer doesn't handle such case + // See https://github.com/jline/jline3/wiki/Completion + .option(LineReader.Option.INSERT_TAB, true) + // Make sure word navigation works properly with Alt + (left/right) arrow key + .variable(LineReader.WORDCHARS, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-$") .highlighter(new JLine3Highlighter()) - .parser(new JLine3Parser())//new DefaultParser().quoteChars(new char[]{'"'})) + .parser(new JLine3Parser()) .completer(new JLine3Completer(this.commands)) .build(); @@ -105,16 +111,16 @@ public void loop() { while (true) { - String line = this.reader.readLine("> "); - if (line == null || line.equalsIgnoreCase("exit")) + try { - break; - } + String line = this.reader.readLine("> "); + if (line == null || line.equalsIgnoreCase("exit")) + { + break; + } - this.reader.getHistory().add(line); + this.reader.getHistory().add(line); - try - { this.commands.detect(new CheckedPredicate() { @Override @@ -126,14 +132,34 @@ public boolean safeAccept(Command c) throws Exception } catch (EngineException e) { - printError(e, line); + printError(e, this.reader.getBuffer().toString()); + } + // handle Ctrl + C: if the input is not empty, start a new line; otherwise, exit + catch (UserInterruptException e) + { + String lineContent = this.reader.getBuffer().toString(); + if (lineContent.isEmpty()) + { + System.exit(0); + break; + } + else + { + this.loop(); + } } - catch (Exception ee) + // handle Ctrl + D: exit + catch (EndOfFileException e) { - this.terminal.writer().println(ee.getMessage()); + System.exit(0); + break; + } + catch (Exception e) + { + this.terminal.writer().println(ansi().fgRed().a(e.getMessage()).reset()); if (this.debug) { - ee.printStackTrace(); + e.printStackTrace(); } } } @@ -204,4 +230,9 @@ public MutableList getCompleterExtensions() { return this.completerExtensions; } + + public Execute getExecuteCommand() + { + return (Execute) this.commands.detect(c -> c instanceof Execute); + } } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/jline3/JLine3Parser.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/jline3/JLine3Parser.java index 0fef62df797..1ad76691464 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/jline3/JLine3Parser.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/jline3/JLine3Parser.java @@ -45,7 +45,7 @@ public MyParsedLine(ParserResult result) public String word() { int index = wordIndex(); - if (result.words.size() > index) + if (index >= 0 && result.words.size() > index) { return result.words.get(index); } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Command.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Command.java index 3982a58ab54..e1dfa3e6875 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Command.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Command.java @@ -25,5 +25,10 @@ public interface Command public String documentation(); + public default String description() + { + return ""; + } + public MutableList complete(String cmd, LineReader lineReader, ParsedLine parsedLine); } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Helpers.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Helpers.java index fcae097b867..25edbc2a623 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Helpers.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/Helpers.java @@ -20,6 +20,9 @@ public class Helpers { + public static final String REPL_RUN_FUNCTION_QUALIFIED_PATH = "repl::__internal__::run__Any_MANY_"; + public static final String REPL_RUN_FUNCTION_SIGNATURE = "repl::__internal__::run():Any[*]"; + public static Identity resolveIdentityFromLocalSubject(Client client) { try diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java index 8a2a58f70bd..7717197fd6a 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java @@ -42,8 +42,6 @@ default MutableList typeGroup() MutableList getExtraCommands(); -// MutableList getExtraState(); - boolean supports(Result res); String print(Result res); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Debug.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Debug.java index a3caf0a0081..fb44d8503f6 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Debug.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Debug.java @@ -36,6 +36,12 @@ public String documentation() return "debug ()"; } + @Override + public String description() + { + return "toggle debug mode"; + } + @Override public boolean process(String line) throws Exception { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java index e00fc880553..a7584b33698 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java @@ -44,14 +44,14 @@ import java.util.HashMap; +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_SIGNATURE; + public class Execute implements Command { private static final ObjectMapper objectMapper = new ObjectMapper(); private final Client client; - private final PlanExecutor planExecutor; - - private PureModelContextData currentPMCD; + private ExecuteResult lastExecuteResult; public Execute(Client client, PlanExecutor planExecutor) { @@ -59,9 +59,9 @@ public Execute(Client client, PlanExecutor planExecutor) this.planExecutor = planExecutor; } - public PureModelContextData getCurrentPMCD() + public ExecuteResult getLastExecuteResult() { - return currentPMCD; + return this.lastExecuteResult; } @Override @@ -115,10 +115,9 @@ private Candidate buildCandidate(CompletionItem s) public String execute(String txt) { String code = "###Pure\n" + - "function a::b::c::d():Any[*]\n{\n" + txt + ";\n}"; + "function " + REPL_RUN_FUNCTION_SIGNATURE + "\n{\n" + txt + ";\n}"; PureModelContextData d = this.client.getModelState().parseWithTransient(code); - this.currentPMCD = d; if (this.client.isDebug()) { @@ -150,7 +149,13 @@ public String execute(String txt) // Execute Identity identity = Helpers.resolveIdentityFromLocalSubject(this.client); - Result res = this.planExecutor.execute((SingleExecutionPlan) PlanExecutor.readExecutionPlan(planStr), new HashMap<>(), identity.getName(), identity, null); + SingleExecutionPlan execPlan = (SingleExecutionPlan) PlanExecutor.readExecutionPlan(planStr); + Result res = this.planExecutor.execute(execPlan, new HashMap<>(), identity.getName(), identity, null); + + // Store these infos for commands that need to access data from the latest execute + this.lastExecuteResult = new ExecuteResult(d, pureModel, res, execPlan); + + // Show result if (res instanceof ConstantResult) { return ((ConstantResult) res).getValue().toString(); @@ -173,4 +178,20 @@ public PlanExecutor getPlanExecutor() { return this.planExecutor; } + + public static class ExecuteResult + { + public final PureModelContextData pureModelContextData; + public final PureModel pureModel; + public final Result result; + public final SingleExecutionPlan plan; + + public ExecuteResult(PureModelContextData pureModelContextData, PureModel pureModel, Result result, SingleExecutionPlan plan) + { + this.pureModelContextData = pureModelContextData; + this.pureModel = pureModel; + this.result = result; + this.plan = plan; + } + } } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Ext.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Ext.java index 3d87eb7a7c7..b8c0ecf7330 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Ext.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Ext.java @@ -39,6 +39,12 @@ public String documentation() return "ext"; } + @Override + public String description() + { + return "show loaded extensions"; + } + @Override public boolean process(String line) throws Exception { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Graph.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Graph.java index 2e3315bae17..4939186aa09 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Graph.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Graph.java @@ -45,7 +45,13 @@ public Graph(Client client) @Override public String documentation() { - return "graph ()"; + return "graph ()"; + } + + @Override + public String description() + { + return "show graph element definition in Pure"; } @Override diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Help.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Help.java index 0811ab5cf94..5c398163436 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Help.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Help.java @@ -21,6 +21,9 @@ import org.jline.reader.LineReader; import org.jline.reader.ParsedLine; +import java.util.Collections; +import java.util.Comparator; + public class Help implements Command { private final MutableList commands; @@ -38,12 +41,23 @@ public String documentation() return "help"; } + @Override + public String description() + { + return "show available commands and their usage"; + } + @Override public boolean process(String cmd) throws Exception { if (cmd.isEmpty() || cmd.equals("help")) { - this.client.getTerminal().writer().println(this.commands.collect(c -> " " + c.documentation()).makeString("\n")); + int maxDocLength = this.commands.maxBy(c -> c.documentation().length()).documentation().length(); + this.client.getTerminal().writer().println(this.commands + .sortThis(Comparator.comparing(Command::documentation)) + // pad right to align the command description + .collect(c -> " " + c.documentation() + String.join("", Collections.nCopies(maxDocLength - c.documentation().length() + 2, " ")) + c.description()) + .makeString("\n")); return true; } return false; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LocalLegendInterface.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LocalLegendInterface.java index 42bc61c61af..a14a2c8afbd 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LocalLegendInterface.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LocalLegendInterface.java @@ -30,6 +30,8 @@ import java.net.URL; +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_QUALIFIED_PATH; + public class LocalLegendInterface implements LegendInterface { @Override @@ -82,7 +84,7 @@ public PureModel compile(PureModelContextData pureModelContextData) public Root_meta_pure_executionPlan_ExecutionPlan generatePlan(PureModel pureModel, boolean debug) { RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); - Pair res = PlanGenerator.generateExecutionPlanAsPure(pureModel.getConcreteFunctionDefinition_safe("a::b::c::d__Any_MANY_"), null, pureModel, PlanPlatform.JAVA, "", debug, extensions); + Pair res = PlanGenerator.generateExecutionPlanAsPure(pureModel.getConcreteFunctionDefinition_safe(REPL_RUN_FUNCTION_QUALIFIED_PATH), null, pureModel, PlanPlatform.JAVA, "", debug, extensions); if (debug) { System.out.println(res.getTwo()); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java index 2f0ca46a6f6..f96c88df161 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java @@ -24,15 +24,15 @@ public class TestCompleter @Test public void testPrimitives() { - Assert.assertEquals("[sqrt , sqrt], [pow , pow], [exp , exp]", checkResultNoException(new Completer("").complete("1->"))); - Assert.assertEquals("[contains , contains], [startsWith , startsWith], [endsWith , endsWith], [toLower , toLower], [toUpper , toUpper], [lpad , lpad], [rpad , rpad], [parseInteger , parseInteger], [parseFloat , parseFloat]", checkResultNoException(new Completer("").complete("'a'->"))); - Assert.assertEquals("[sum , sum], [mean , mean], [average , average], [min , min], [max , max], [count , count], [percentile , percentile], [variancePopulation , variancePopulation], [varianceSample , varianceSample], [stdDevPopulation , stdDevPopulation], [stdDevSample , stdDevSample]", checkResultNoException(new Completer("").complete("[1,2]->"))); + Assert.assertEquals("[abs , abs(], [pow , pow(], [sqrt , sqrt(], [exp , exp(]", checkResultNoException(new Completer("").complete("1->"))); + Assert.assertEquals("[contains , contains(], [startsWith , startsWith(], [endsWith , endsWith(], [toLower , toLower(], [toUpper , toUpper(], [lpad , lpad(], [rpad , rpad(], [parseInteger , parseInteger(], [parseFloat , parseFloat(]", checkResultNoException(new Completer("").complete("'a'->"))); + Assert.assertEquals("[sum , sum(], [mean , mean(], [average , average(], [min , min(], [max , max(], [count , count(], [percentile , percentile(], [variancePopulation , variancePopulation(], [varianceSample , varianceSample(], [stdDevPopulation , stdDevPopulation(], [stdDevSample , stdDevSample(]", checkResultNoException(new Completer("").complete("[1,2]->"))); } @Test public void testArrowOnType() { - Assert.assertEquals("[project , project]", checkResultNoException(new Completer("Class x::A{name:String[1];other:Integer[1];}").complete("x::A.all()->"))); + Assert.assertEquals("[project , project(]", checkResultNoException(new Completer("Class x::A{name:String[1];other:Integer[1];}").complete("x::A.all()->"))); Assert.assertEquals("", checkResultNoException(new Completer("Class x::A{name:String[1];other:Integer[1];}").complete("x::A.all()->fu"))); } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml index 2c25c30576c..34c572c680f 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml @@ -179,6 +179,10 @@ org.finos.legend.engine legend-engine-identity-core + + org.finos.legend.engine + legend-engine-xt-identity-kerberos + org.jline @@ -199,11 +203,14 @@ eclipse-collections-api + + com.fasterxml.jackson.core + jackson-core + com.fasterxml.jackson.core jackson-databind - com.fasterxml.jackson.core jackson-annotations @@ -219,4 +226,126 @@ jline + + + + repl-dev + + true + + + + org.slf4j + slf4j-nop + runtime + ${slf4j.version} + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-web-content + process-resources + + copy-resources + + + ${project.build.directory}/classes/web-content + + + ${project.basedir}/../../../temp/web-content + + + + + + + + maven-shade-plugin + + + package + + shade + + + true + false + + + *:* + + module-info.class + META-INF/**/module-info.class + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + org.finos.legend.engine.repl.relational.client.RClient + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + default + + enforce + + + + + ${maven.enforcer.requireJavaVersion} + + + ${maven.enforcer.requireMavenVersion} + + + + + + log4j:*:*:*:compile + log4j:*:*:*:runtime + org.slf4j:*:*:*:compile + org.slf4j:*:*:*:runtime + commons-logging + javax.mail + + + + + org.slf4j:slf4j-nop:${slf4j.version} + org.slf4j:jul-to-slf4j:${slf4j.version} + org.slf4j:slf4j-api:${slf4j.version} + org.slf4j:jcl-over-slf4j:${slf4j.version} + org.slf4j:slf4j-jdk14:${slf4j.version} + org.slf4j:slf4j-log4j12:${slf4j.version} + org.slf4j:slf4j-ext:${slf4j.version} + log4j:log4j:${log4j.version} + log4j:apache-log4j-extras:${log4j.version} + + + + + + + + + + + diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java index 8c7646ade1f..72738eb14ed 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java @@ -22,14 +22,11 @@ import org.finos.legend.engine.repl.client.Client; import org.finos.legend.engine.repl.core.Command; import org.finos.legend.engine.repl.core.ReplExtension; -import org.finos.legend.engine.repl.relational.commands.Cache; -import org.finos.legend.engine.repl.relational.commands.DB; -import org.finos.legend.engine.repl.relational.commands.Load; +import org.finos.legend.engine.repl.relational.commands.*; import org.finos.legend.engine.repl.relational.local.LocalConnectionManagement; import org.finos.legend.engine.repl.relational.local.LocalConnectionType; -import org.finos.legend.engine.repl.relational.commands.Show; -import org.finos.legend.engine.repl.relational.httpServer.ReplGridServer; +import org.finos.legend.engine.repl.relational.server.REPLServer; import java.awt.*; import java.sql.SQLException; @@ -39,7 +36,7 @@ public class RelationalReplExtension implements ReplExtension { private Client client; - public ReplGridServer replGridServer; + public REPLServer REPLServer; private LocalConnectionManagement localConnectionManagement; @@ -77,8 +74,8 @@ public void initialize(Client client) try { - this.replGridServer = new ReplGridServer(this.client); - this.replGridServer.initializeServer(); + this.REPLServer = new REPLServer(this.client); + this.REPLServer.initialize(); } catch (Exception e) { @@ -95,10 +92,13 @@ public MutableList generateDynamicContent(String code) @Override public MutableList getExtraCommands() { - MutableList extraCommands = Lists.mutable.with(new DB(this.client, this), new Load(this.client, this)); - extraCommands.add(new Show(this.client, this.replGridServer)); - extraCommands.add(new Cache(this.client, this.client.getPlanExecutor())); - return extraCommands; + return Lists.mutable.with( + new DB(this.client, this), + new Load(this.client, this), + new Drop(this.client), + new Show(this.client, this.REPLServer), + new Cache(this.client, this.client.getPlanExecutor()) + ); } @Override diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java index 188d580528b..2f220470bf0 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java @@ -62,6 +62,10 @@ public CompletionResult extraClassInstanceProcessor(Object islandExpr, PureModel private static boolean nameMatch(PackageableElement c, String writtenPath) { String path = org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c); + if (path.isEmpty()) // NOTE: handle an edge case where stub store is added to the graph + { + return false; + } if (path.length() > writtenPath.length()) { return path.startsWith(writtenPath); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Cache.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Cache.java index f4d34b90d5d..b72f3dfd458 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Cache.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Cache.java @@ -41,9 +41,9 @@ import org.finos.legend.engine.repl.core.Helpers; import org.finos.legend.engine.repl.relational.shared.ConnectionHelper; import org.finos.legend.engine.shared.core.identity.Identity; -import org.finos.legend.pure.generated.core_pure_executionPlan_executionPlan_print; import org.finos.legend.pure.generated.Root_meta_pure_executionPlan_ExecutionPlan; import org.finos.legend.pure.generated.Root_meta_pure_extension_Extension; +import org.finos.legend.pure.generated.core_pure_executionPlan_executionPlan_print; import org.jline.reader.Candidate; import org.jline.reader.LineReader; import org.jline.reader.ParsedLine; @@ -54,6 +54,7 @@ import java.sql.Statement; import java.util.HashMap; +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_SIGNATURE; import static org.finos.legend.engine.repl.relational.schema.MetadataReader.getTables; public class Cache implements Command @@ -73,6 +74,12 @@ public String documentation() return "cache "; } + @Override + public String description() + { + return "cache the result of the last executed query into a table"; + } + @Override public boolean process(String line) throws Exception { @@ -90,7 +97,7 @@ public boolean process(String line) throws Exception DatabaseConnection databaseConnection = ConnectionHelper.getDatabaseConnection(this.client.getModelState().parse(), connectionPath); String code = "###Pure\n" + - "function a::b::c::d():Any[*]\n{\n" + expression + ";\n}"; + "function " + REPL_RUN_FUNCTION_SIGNATURE + "\n{\n" + expression + ";\n}"; PureModelContextData parsed = this.client.getModelState().parseWithTransient(code); PureModel pureModel = this.client.getLegendInterface().compile(parsed); RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/DB.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/DB.java index dab9a8c902c..0135bb4a691 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/DB.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/DB.java @@ -50,6 +50,12 @@ public String documentation() return "db "; } + @Override + public String description() + { + return "show schema summary of the database"; + } + @Override public boolean process(String line) throws Exception { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Drop.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Drop.java new file mode 100644 index 00000000000..4d332313748 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Drop.java @@ -0,0 +1,122 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.commands; + +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.impl.factory.Lists; +import org.eclipse.collections.impl.utility.ListIterate; +import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerUtility; +import org.finos.legend.engine.plan.execution.stores.relational.connection.driver.DatabaseManager; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.DatabaseConnection; +import org.finos.legend.engine.repl.client.Client; +import org.finos.legend.engine.repl.core.Command; +import org.finos.legend.engine.repl.relational.RelationalReplExtension; +import org.finos.legend.engine.repl.relational.shared.ConnectionHelper; +import org.jline.builtins.Completers; +import org.jline.reader.Candidate; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +import java.io.File; +import java.sql.Connection; +import java.sql.Statement; + +import static org.finos.legend.engine.repl.relational.schema.MetadataReader.getTables; + +public class Drop implements Command +{ + private final Client client; + private final Completers.FilesCompleter completer = new Completers.FilesCompleter(new File("/")); + + public Drop(Client client) + { + this.client = client; + } + + @Override + public String documentation() + { + return "drop "; + } + + @Override + public boolean process(String line) throws Exception + { + if (line.startsWith("drop")) + { + String[] tokens = line.split(" "); + if (tokens.length != 3) + { + throw new RuntimeException("Error, drop should be used as '" + this.documentation() + "'"); + } + + DatabaseConnection databaseConnection = ConnectionHelper.getDatabaseConnection(this.client.getModelState().parse(), tokens[1]); + + try (Connection connection = ConnectionHelper.getConnection(databaseConnection, client.getPlanExecutor())) + { + String tableName = tokens[2]; + try (Statement statement = connection.createStatement()) + { + statement.executeUpdate(DatabaseManager.fromString(databaseConnection.type.name()).relationalDatabaseSupport().dropTable(tableName, tokens[1])); + this.client.getTerminal().writer().println("Dropped table: '" + tableName + "'"); + } + } + + return true; + } + return false; + } + + @Override + public MutableList complete(String inScope, LineReader lineReader, ParsedLine parsedLine) + { + if (inScope.startsWith("drop ")) + { + MutableList words = Lists.mutable.withAll(parsedLine.words()).drop(2); + if (!words.contains(" ")) + { + String start = words.get(0); + PureModelContextData d = this.client.getModelState().parse(); + return ListIterate.select(d.getElementsOfType(PackageableConnection.class), c -> !c._package.equals("__internal__")) + .collect(c -> PureGrammarComposerUtility.convertPath(c.getPath())) + .select(c -> c.startsWith(start)) + .collect(Candidate::new); + } + else + { + String connectionPath = words.subList(0, words.indexOf(" ") + 1).makeString("").trim(); + String start = words.subList(words.indexOf(" ") + 1, words.size()).get(0); + PureModelContextData d = this.client.getModelState().parse(); + MutableList foundConnections = ListIterate.select(d.getElementsOfType(PackageableConnection.class), c -> !c._package.equals("__internal__")) + .select(c -> PureGrammarComposerUtility.convertPath(c.getPath()).equals(connectionPath)); + if (!foundConnections.isEmpty() && foundConnections.getFirst().connectionValue instanceof DatabaseConnection) + { + try (Connection connection = ConnectionHelper.getConnection((DatabaseConnection) foundConnections.getFirst().connectionValue, client.getPlanExecutor())) + { + return getTables(connection).select(c -> c.name.startsWith(start)).collect(c -> c.name).collect(Candidate::new); + } + catch (Exception e) + { + // do nothing + } + } + return null; + } + } + return null; + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Load.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Load.java index 52bb911829b..b711df2c34b 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Load.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Load.java @@ -53,7 +53,13 @@ public Load(Client client, RelationalReplExtension relationalReplExtension) @Override public String documentation() { - return "load "; + return "load (
)"; + } + + @Override + public String description() + { + return "load CSV file into table"; } @Override @@ -62,16 +68,16 @@ public boolean process(String line) throws Exception if (line.startsWith("load")) { String[] tokens = line.split(" "); - if (tokens.length != 3) + if (tokens.length != 3 && tokens.length != 4) { - throw new RuntimeException("Error, load should be used as 'load '"); + throw new RuntimeException("Error, load should be used as '" + this.documentation() + "'"); } DatabaseConnection databaseConnection = ConnectionHelper.getDatabaseConnection(this.client.getModelState().parse(), tokens[2]); try (Connection connection = ConnectionHelper.getConnection(databaseConnection, client.getPlanExecutor())) { - String tableName = "test" + (getTables(connection).size() + 1); + String tableName = tokens.length == 4 ? tokens[3] : ("test" + (getTables(connection).size() + 1)); try (Statement statement = connection.createStatement()) { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Show.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Show.java index d0cb0cf3397..3fea57f989c 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Show.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/commands/Show.java @@ -15,28 +15,29 @@ package org.finos.legend.engine.repl.relational.commands; import org.eclipse.collections.api.list.MutableList; -import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; import org.finos.legend.engine.repl.client.Client; import org.finos.legend.engine.repl.core.Command; import org.finos.legend.engine.repl.core.commands.Execute; -import org.finos.legend.engine.repl.relational.httpServer.ReplGridServer; +import org.finos.legend.engine.repl.relational.server.REPLServer; import org.jline.reader.Candidate; import org.jline.reader.LineReader; import org.jline.reader.ParsedLine; -import java.awt.Desktop; +import java.awt.*; import java.net.URI; +import static org.jline.jansi.Ansi.ansi; + public class Show implements Command { - private Client client; + private final Client client; - public ReplGridServer replGridServer; + public REPLServer REPLServer; - public Show(Client client, ReplGridServer replGridServer) + public Show(Client client, REPLServer REPLServer) { this.client = client; - this.replGridServer = replGridServer; + this.REPLServer = REPLServer; } @Override @@ -45,33 +46,39 @@ public String documentation() return "show"; } + @Override + public String description() + { + return "show the result for the last executed query in GUI mode (DataCube)"; + } + @Override public boolean process(String line) { if (line.startsWith("show")) { - PureModelContextData currentPMCD = ((Execute) this.client.commands.getLast()).getCurrentPMCD(); - if (currentPMCD == null) + Execute.ExecuteResult lastExecuteResult = this.client.getExecuteCommand().getLastExecuteResult(); + if (lastExecuteResult == null) { - this.client.getTerminal().writer().println("Unable to show repl grid, no query has been executed"); + this.client.getTerminal().writer().println("Can't show result grid in DataCube. Try to run a query in REPL first..."); } else { + this.REPLServer.setExecuteResult(lastExecuteResult); try { - this.replGridServer.updateGridState(currentPMCD); - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) - { - Desktop.getDesktop().browse(URI.create(replGridServer.getGridUrl())); - } - else - { - this.client.getTerminal().writer().println(replGridServer.getGridUrl()); - } + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) + { + Desktop.getDesktop().browse(URI.create(REPLServer.getUrl())); + } + else + { + this.client.getTerminal().writer().println(REPLServer.getUrl()); + } } catch (Exception e) { - this.client.getTerminal().writer().println(e.getMessage()); + this.client.getTerminal().writer().println(ansi().fgRed().a(e.getMessage()).reset()); } } return true; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/httpServer/ReplGridServer.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/httpServer/ReplGridServer.java deleted file mode 100644 index a26fda2c0a7..00000000000 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/httpServer/ReplGridServer.java +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright 2024 Goldman Sachs -// -// 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 org.finos.legend.engine.repl.relational.httpServer; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.apache.commons.io.IOUtils; -import org.eclipse.collections.api.RichIterable; -import org.eclipse.collections.api.factory.Lists; -import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; -import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; -import org.finos.legend.engine.language.pure.grammar.to.DEPRECATED_PureGrammarComposerCore; -import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposer; -import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerContext; -import org.finos.legend.engine.plan.execution.PlanExecutor; -import org.finos.legend.engine.plan.execution.result.Result; -import org.finos.legend.engine.plan.execution.result.serialization.SerializationFormat; -import org.finos.legend.engine.plan.execution.stores.relational.result.RelationalResult; -import org.finos.legend.engine.plan.generation.PlanGenerator; -import org.finos.legend.engine.plan.generation.transformers.LegendPlanTransformers; -import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.application.AppliedFunction; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.CInteger; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; -import org.finos.legend.engine.pure.code.core.PureCoreExtensionLoader; -import org.finos.legend.engine.repl.autocomplete.Completer; -import org.finos.legend.engine.repl.autocomplete.CompletionResult; -import org.finos.legend.engine.repl.client.Client; -import org.finos.legend.engine.repl.core.legend.LegendInterface; -import org.finos.legend.engine.repl.relational.autocomplete.RelationalCompleterExtension; -import org.finos.legend.engine.shared.core.api.grammar.RenderStyle; -import org.finos.legend.engine.shared.core.operational.errorManagement.ExceptionError; -import org.finos.legend.pure.generated.Root_meta_pure_executionPlan_ExecutionPlan; -import org.finos.legend.pure.generated.Root_meta_pure_extension_Extension; - -public class ReplGridServer -{ - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final PlanExecutor planExecutor = PlanExecutor.newPlanExecutorBuilder().withAvailableStoreExecutors().build(); - private PureModelContextData currentPMCD; - private final Client client; - private int port; - - public ReplGridServer(Client client) - { - this.client = client; - } - - public String getGridUrl() - { - return "http://localhost:" + this.port + "/repl/grid"; - } - - public static class GridServerResult - { - private final String currentQuery; - private final String result; - - public GridServerResult(@JsonProperty("currentQuery") String currentQuery, @JsonProperty("result") String result) - { - this.currentQuery = currentQuery; - this.result = result; - } - - public String getResult() - { - return this.result; - } - - public String getCurrentQuery() - { - return this.currentQuery; - } - } - - public void updateGridState(PureModelContextData pmcd) - { - this.currentPMCD = pmcd; - } - - public void initializeServer() throws Exception - { - InetSocketAddress serverPortAddress = new InetSocketAddress(0); - HttpServer server = HttpServer.create(serverPortAddress, 0); - - server.createContext("/licenseKey", new HttpHandler() - { - @Override - public void handle(HttpExchange exchange) throws IOException - { - if ("GET".equals(exchange.getRequestMethod())) - { - try - { - String licenseKey = System.getProperty("legend.repl.grid.licenseKey") == null ? "" : System.getProperty("legend.repl.grid.licenseKey"); - String key = objectMapper.writeValueAsString(licenseKey); - handleResponse(exchange, 200, key); - } - catch (Exception e) - { - OutputStream os = exchange.getResponseBody(); - exchange.sendResponseHeaders(500, e.getMessage().length()); - os.write(e.getMessage().getBytes(StandardCharsets.UTF_8)); - os.close(); - } - - } - } - }); - - server.createContext("/repl/", exchange -> - { - if ("GET".equals(exchange.getRequestMethod())) - { - String[] path = exchange.getRequestURI().getPath().split("/repl/"); - String resourcePath = "/web-content/package/dist/repl/" + (path[1].equals("grid") ? "index.html" : path[1]); - try (OutputStream os = exchange.getResponseBody(); - InputStream is = ReplGridServer.class.getResourceAsStream(resourcePath) - ) - { - if (is == null) - { - exchange.sendResponseHeaders(404, -1); - } - else - { - if (resourcePath.endsWith(".html")) - { - exchange.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8"); - } - if (resourcePath.endsWith(".js")) - { - exchange.getResponseHeaders().add("Content-Type", "text/javascript; charset=utf-8"); - } - else if (resourcePath.endsWith(".css")) - { - exchange.getResponseHeaders().add("Content-Type", "text/css; charset=utf-8"); - } - - exchange.sendResponseHeaders(200, 0); - IOUtils.copy(is, os); - } - } - catch (Exception e) - { - handleResponse(exchange, 500, e.getMessage()); - } - } - }); - - server.createContext("/initialLambda", exchange -> - { - if ("GET".equals(exchange.getRequestMethod())) - { - try - { - Function func = (Function) currentPMCD.getElements().stream().filter(e -> e.getPath().equals("a::b::c::d__Any_MANY_")).collect(Collectors.toList()).get(0); - Lambda lambda = new Lambda(); - lambda.body = func.body; - String response = objectMapper.writeValueAsString(lambda); - handleResponse(exchange, 200, response); - } - catch (Exception e) - { - handleResponse(exchange, 500, e.getMessage()); - } - } - }); - - server.createContext("/executeLambda", exchange -> - { - if ("POST".equals(exchange.getRequestMethod())) - { - try - { - InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); - BufferedReader bufferReader = new BufferedReader(inputStreamReader); - String requestBody = bufferReader.lines().collect(Collectors.joining()); - AppliedFunction body = (AppliedFunction) PureGrammarParser.newInstance().parseValueSpecification(requestBody, "", 0, 0, true); - List newBody = Lists.mutable.of(body); - if (checkIfPaginationIsEnabled(exchange.getRequestURI().getQuery())) - { - applySliceFunction(newBody); - } - Function func = (Function) currentPMCD.getElements().stream().filter(e -> e.getPath().equals("a::b::c::d__Any_MANY_")).collect(Collectors.toList()).get(0); - func.body = newBody; - String response = executeLambda(client.getLegendInterface(), currentPMCD, func, newBody.get(0)); - handleResponse(exchange, 200, response); - } - catch (Exception e) - { - handleResponse(exchange, 500, e.getMessage()); - } - } - }); - - server.createContext("/typeahead", exchange -> - { - if ("POST".equals(exchange.getRequestMethod())) - { - try - { - InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); - BufferedReader bufferReader = new BufferedReader(inputStreamReader); - String requestBody = bufferReader.lines().collect(Collectors.joining()); - String buildCodeContext = PureGrammarComposer.newInstance(PureGrammarComposerContext.Builder.newInstance().build()).renderPureModelContextData(currentPMCD); - CompletionResult result = new Completer(buildCodeContext, Lists.mutable.with(new RelationalCompleterExtension())).complete(requestBody); - if (result.getEngineException() != null) - { - handleResponse(exchange, 500, result.getEngineException().toPretty()); - } - else - { - handleResponse(exchange, 200, objectMapper.writeValueAsString(result.getCompletion())); - } - } - catch (Exception e) - { - handleResponse(exchange, 500, e.getMessage()); - } - } - }); - - server.createContext("/parseQuery", exchange -> - { - if ("POST".equals(exchange.getRequestMethod())) - { - try - { - InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); - BufferedReader bufferReader = new BufferedReader(inputStreamReader); - String requestBody = bufferReader.lines().collect(Collectors.joining("\n")); - PureGrammarParser.newInstance().parseValueSpecification(requestBody, "", 0, 0, true); - exchange.sendResponseHeaders(200, -1); - } - catch (Exception e) - { - handleResponse(exchange, 400, objectMapper.writeValueAsString(new ExceptionError(-1, e))); - } - } - }); - - server.createContext("/gridResult", exchange -> - { - if ("GET".equals(exchange.getRequestMethod())) - { - ValueSpecification funcBody = null; - Function func = null; - try - { - func = (Function) currentPMCD.getElements().stream().filter(e -> e.getPath().equals("a::b::c::d__Any_MANY_")).collect(Collectors.toList()).get(0); - funcBody = func.body.get(0); - - List newBody = Lists.mutable.of(funcBody); - if (checkIfPaginationIsEnabled(exchange.getRequestURI().getQuery())) - { - applySliceFunction(newBody); - } - func.body = newBody; - - String response = executeLambda(client.getLegendInterface(), currentPMCD, func, funcBody); - handleResponse(exchange, 200, response); - } - catch (Exception e) - { - System.out.println(e.getMessage()); - if (func != null) - { - func.body = Lists.mutable.of(funcBody); - } - handleResponse(exchange, 500, e.getMessage()); - } - - } - else if ("POST".equals(exchange.getRequestMethod())) - { - ValueSpecification funcBody = null; - Function func = null; - try - { - InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); - BufferedReader bufferReader = new BufferedReader(inputStreamReader); - String requestBody = bufferReader.lines().collect(Collectors.joining()); - Lambda request = objectMapper.readValue(requestBody, Lambda.class); - func = (Function) currentPMCD.getElements().stream().filter(e -> e.getPath().equals("a::b::c::d__Any_MANY_")).collect(Collectors.toList()).get(0); - funcBody = func.body.get(0); - func.body = request.body; - String response = executeLambda(client.getLegendInterface(), currentPMCD, func, funcBody); - handleResponse(exchange, 200, response); - } - catch (Exception e) - { - System.out.println(e.getMessage()); - if (func != null) - { - func.body = Lists.mutable.of(funcBody); - } - handleResponse(exchange, 500, e.getMessage()); - } - } - }); - - server.setExecutor(null); - server.start(); - this.port = server.getAddress().getPort(); - System.out.println("REPL Grid Server has started at port " + this.port); - } - - public static String executeLambda(LegendInterface legendInterface, PureModelContextData currentRequestPMCD, Function func, ValueSpecification funcBody) throws IOException - { - Lambda lambda = new Lambda(); - lambda.body = func.body; - String lambdaString = lambda.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().withRenderStyle(RenderStyle.PRETTY).build()); - PureModel pureModel = legendInterface.compile(currentRequestPMCD); - RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); - - // Plan - Root_meta_pure_executionPlan_ExecutionPlan plan = legendInterface.generatePlan(pureModel, false); - String planStr = PlanGenerator.serializeToJSON(plan, "vX_X_X", pureModel, extensions, LegendPlanTransformers.transformers); - - // Execute - Result res = planExecutor.execute(planStr); - func.body = Lists.mutable.of(funcBody); - if (res instanceof RelationalResult) - { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ((RelationalResult) res).getSerializer(SerializationFormat.DEFAULT).stream(byteArrayOutputStream); - GridServerResult result = new GridServerResult(lambdaString, byteArrayOutputStream.toString()); - return objectMapper.writeValueAsString(result); - } - throw new RuntimeException("Expected return type of Lambda execution is RelationalResult, but returned " + res.getClass().getName()); - } - - private void handleResponse(HttpExchange exchange, int responseCode, String response) - { - try - { - OutputStream os = exchange.getResponseBody(); - byte[] byteResponse = response.getBytes(StandardCharsets.UTF_8); - exchange.sendResponseHeaders(responseCode, byteResponse.length); - os.write(byteResponse); - os.close(); - } - catch (IOException e) - { - System.out.println(e.getMessage()); - } - } - - public static void applySliceFunction(List body) - { - CInteger startValue = new CInteger(0); - CInteger endValue = new CInteger(100); - ValueSpecification currentExpression = body.get(0); - while (currentExpression instanceof AppliedFunction) - { - if (((AppliedFunction) currentExpression).function.equals("from")) - { - ValueSpecification childExpression = ((AppliedFunction) currentExpression).parameters.get(0); - if (childExpression instanceof AppliedFunction && ((AppliedFunction) childExpression).function.equals("slice")) - { - ((AppliedFunction) childExpression).parameters = Lists.mutable.of(((AppliedFunction) childExpression).parameters.get(0), startValue, endValue); - break; - } - AppliedFunction sliceFunction = new AppliedFunction(); - sliceFunction.function = "slice"; - sliceFunction.parameters = Lists.mutable.of(((AppliedFunction) currentExpression).parameters.get(0), startValue, endValue); - ((AppliedFunction) currentExpression).parameters.set(0, sliceFunction); - break; - } - currentExpression = ((AppliedFunction) currentExpression).parameters.get(0); - } - } - - private static boolean checkIfPaginationIsEnabled(String queryParamsString) - { - Map queryParams = new HashMap<>(); - for (String param : queryParamsString.split("&")) - { - String[] entry = param.split("="); - if (entry.length > 1) - { - queryParams.put(entry[0], entry[1]); - } - else - { - queryParams.put(entry[0], ""); - } - } - return queryParams.get("isPaginationEnabled").equals("true"); - } -} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/DataCubeHelpers.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/DataCubeHelpers.java new file mode 100644 index 00000000000..090a050a8f9 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/DataCubeHelpers.java @@ -0,0 +1,166 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server; + +import org.eclipse.collections.api.RichIterable; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.tuple.Pair; +import org.eclipse.collections.impl.tuple.Tuples; +import org.eclipse.collections.impl.utility.ListIterate; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.RelationTypeHelper; +import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; +import org.finos.legend.engine.language.pure.grammar.to.DEPRECATED_PureGrammarComposerCore; +import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposer; +import org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerContext; +import org.finos.legend.engine.plan.execution.PlanExecutor; +import org.finos.legend.engine.plan.execution.result.Result; +import org.finos.legend.engine.plan.execution.result.serialization.SerializationFormat; +import org.finos.legend.engine.plan.execution.stores.relational.result.RelationalResult; +import org.finos.legend.engine.plan.generation.PlanGenerator; +import org.finos.legend.engine.plan.generation.transformers.LegendPlanTransformers; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.executionPlan.SingleExecutionPlan; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function; +import org.finos.legend.engine.protocol.pure.v1.model.relationType.RelationType; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; +import org.finos.legend.engine.pure.code.core.PureCoreExtensionLoader; +import org.finos.legend.engine.repl.autocomplete.Completer; +import org.finos.legend.engine.repl.autocomplete.CompletionResult; +import org.finos.legend.engine.repl.core.legend.LegendInterface; +import org.finos.legend.engine.repl.relational.autocomplete.RelationalCompleterExtension; +import org.finos.legend.engine.repl.relational.server.model.DataCubeExecutionResult; +import org.finos.legend.engine.shared.core.api.grammar.RenderStyle; +import org.finos.legend.engine.shared.core.identity.Identity; +import org.finos.legend.engine.shared.core.kerberos.SubjectTools; +import org.finos.legend.pure.generated.Root_meta_pure_executionPlan_ExecutionPlan; +import org.finos.legend.pure.generated.Root_meta_pure_extension_Extension; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; + +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_QUALIFIED_PATH; + +public class DataCubeHelpers +{ + public static DataCubeExecutionResult executeQuery(LegendInterface legendInterface, PlanExecutor planExecutor, PureModelContextData data) throws IOException + { + PureModel pureModel = legendInterface.compile(data); + RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); + + // Plan + Root_meta_pure_executionPlan_ExecutionPlan _plan = legendInterface.generatePlan(pureModel, false); + String planStr = PlanGenerator.serializeToJSON(_plan, "vX_X_X", pureModel, extensions, LegendPlanTransformers.transformers); + + // Execute + Identity identity; + try + { + identity = Identity.makeIdentity(SubjectTools.getLocalSubject()); + } + catch (Exception e) + { + // Can't resolve identity from local subject + identity = Identity.getAnonymousIdentity(); + } + + SingleExecutionPlan plan = (SingleExecutionPlan) PlanExecutor.readExecutionPlan(planStr); + + try (Result execResult = planExecutor.execute(plan, new HashMap<>(), identity.getName(), identity, null)) + { + if (execResult instanceof RelationalResult) + { + DataCubeExecutionResult result = new DataCubeExecutionResult(); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ((RelationalResult) execResult).getSerializer(SerializationFormat.DEFAULT).stream(byteArrayOutputStream); + result.result = byteArrayOutputStream.toString(); + return result; + } + throw new RuntimeException("Expected execution result of type 'RelationalResult', but got '" + execResult.getClass().getName() + "'"); + } + } + + public static RelationType getRelationReturnType(LegendInterface legendInterface, PureModelContextData data) + { + PureModel pureModel = legendInterface.compile(data); + return RelationTypeHelper.convert((org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType) pureModel.getConcreteFunctionDefinition(REPL_RUN_FUNCTION_QUALIFIED_PATH, null)._expressionSequence().getLast()._genericType()._typeArguments().getFirst()._rawType()); + } + + public static ValueSpecification parseQuery(String code, Boolean returnSourceInformation) + { + return PureGrammarParser.newInstance().parseValueSpecification(code, "", 0, 0, returnSourceInformation != null && returnSourceInformation); + } + + public static String getQueryCode(ValueSpecification valueSpecification, Boolean pretty) + { + return valueSpecification.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().withRenderStyle(pretty != null && pretty ? RenderStyle.PRETTY : RenderStyle.STANDARD).build()); + } + + public static CompletionResult getCodeTypeahead(String code, Boolean isPartial, PureModelContextData data) + { + try + { + PureModelContextData newData = PureModelContextData.newBuilder() + .withOrigin(data.getOrigin()) + .withSerializer(data.getSerializer()) + .withElements(ListIterate.select(data.getElements(), el -> !el.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH))) + .build(); + String graphCode = PureGrammarComposer.newInstance(PureGrammarComposerContext.Builder.newInstance().build()).renderPureModelContextData(newData); + String queryCode = code; + if (isPartial != null && isPartial) + { + Function func = (Function) ListIterate.select(data.getElements(), el -> el.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH)).getFirst(); + String existingCode = func.body.get(0).accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); + queryCode = existingCode + code; + } + CompletionResult result = new Completer(graphCode, Lists.mutable.with(new RelationalCompleterExtension())).complete(queryCode); + if (result.getEngineException() != null) + { + return new CompletionResult(Lists.mutable.empty()); + } + return result; + } + catch (Exception e) + { + return new CompletionResult(Lists.mutable.empty()); + } + } + + /** + * Replace the magic function in the given graph data by a new function with the body of the specified lambda + */ + public static Pair injectNewFunction(PureModelContextData originalData, Lambda lambda) + { + Function originalFunction = (Function) ListIterate.select(originalData.getElements(), e -> e.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH)).getFirst(); + Function func = new Function(); + func.name = originalFunction.name; + func._package = originalFunction._package; + func.parameters = originalFunction.parameters; + func.returnType = originalFunction.returnType; + func.returnMultiplicity = originalFunction.returnMultiplicity; + func.body = lambda != null ? lambda.body : func.body; // if no lambda is specified, we'll just use the original function + + PureModelContextData data = PureModelContextData.newBuilder() + .withOrigin(originalData.getOrigin()) + .withSerializer(originalData.getSerializer()) + .withElements(ListIterate.select(originalData.getElements(), el -> !el.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH))) + .withElement(func) + .build(); + + return Tuples.pair(data, func); + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServer.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServer.java new file mode 100644 index 00000000000..844ba61504e --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServer.java @@ -0,0 +1,104 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpServer; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.impl.factory.Maps; +import org.finos.legend.engine.plan.execution.PlanExecutor; +import org.finos.legend.engine.repl.client.Client; +import org.finos.legend.engine.repl.core.commands.Execute; +import org.finos.legend.engine.repl.relational.server.handler.DataCubeInfrastructure; +import org.finos.legend.engine.repl.relational.server.handler.DataCubeQueryBuilder; +import org.finos.legend.engine.repl.relational.server.handler.DataCubeQueryExecutor; +import org.finos.legend.engine.shared.core.ObjectMapperFactory; + +import java.net.InetSocketAddress; +import java.util.List; + +import static org.finos.legend.engine.repl.relational.server.REPLServerHelpers.DEV__CORSFilter; +import static org.jline.jansi.Ansi.ansi; + +public class REPLServer +{ + private static final ObjectMapper objectMapper = ObjectMapperFactory.getNewStandardObjectMapperWithPureProtocolExtensionSupports(); + private static final PlanExecutor planExecutor = PlanExecutor.newPlanExecutorBuilder().withAvailableStoreExecutors().build(); + private final Client client; + private final REPLServerHelpers.REPLServerState state; + + private int port; + private String webAppDevBaseUrl; + + public REPLServer(Client client) + { + this.client = client; + this.state = new REPLServerHelpers.REPLServerState(objectMapper, planExecutor, client.getLegendInterface()); + this.state.setClient(client); + } + + public void setExecuteResult(Execute.ExecuteResult executeResult) + { + this.state.initializeWithREPLExecutedQuery(executeResult); + } + + public String getUrl() + { + String dynamicBaseUrl = System.getenv("LEGEND_REPL_DATA_CUBE_BASE_URL"); + String baseUrl = dynamicBaseUrl != null ? dynamicBaseUrl : this.webAppDevBaseUrl; + return (baseUrl != null ? baseUrl : "http://localhost:" + this.port) + "/repl/dataCube"; + } + + public void initialize() throws Exception + { + InetSocketAddress serverPortAddress = new InetSocketAddress(System.getProperty("legend.repl.dataCube.devPort") != null ? Integer.parseInt(System.getProperty("legend.repl.dataCube.devPort")) : 0); + HttpServer server = HttpServer.create(serverPortAddress, 0); + + // register handlers + MutableList contexts = Maps.mutable.empty() + .withKeyValue("/repl/", new DataCubeInfrastructure.StaticContent()) + .withKeyValue("/api/dataCube/gridLicenseKey", new DataCubeInfrastructure.GridLicenseKey()) + .withKeyValue("/api/dataCube/typeahead", new DataCubeQueryBuilder.QueryTypeahead()) + .withKeyValue("/api/dataCube/parseQuery", new DataCubeQueryBuilder.ParseQuery()) + .withKeyValue("/api/dataCube/getQueryCode", new DataCubeQueryBuilder.GetQueryCode()) + .withKeyValue("/api/dataCube/getQueryCode/batch", new DataCubeQueryBuilder.GetQueryCodeBatch()) + .withKeyValue("/api/dataCube/getBaseQuery", new DataCubeQueryBuilder.GetBaseQuery()) + .withKeyValue("/api/dataCube/getRelationReturnType", new DataCubeQueryBuilder.GetRelationReturnType()) + .withKeyValue("/api/dataCube/executeQuery", new DataCubeQueryExecutor.ExecuteQuery()) + .keyValuesView().collect(config -> server.createContext(config.getOne(), config.getTwo().getHandler(this.state))).toList(); + + // CORS filter + // only needed if we're not serving the webapp from the same server, e.g. in development + if (System.getProperty("legend.repl.dataCube.devWebAppBaseUrl") != null) + { + this.webAppDevBaseUrl = System.getProperty("legend.repl.dataCube.devWebAppBaseUrl"); + List filters = Lists.mutable.empty(); + filters.add(new DEV__CORSFilter(Lists.mutable.with(this.webAppDevBaseUrl))); + contexts.forEach(context -> context.getFilters().addAll(filters)); + } + + server.setExecutor(null); + server.start(); + this.port = server.getAddress().getPort(); + if (this.webAppDevBaseUrl != null) + { + this.client.getTerminal().writer().println(ansi().fgBrightBlack().a("[DEV] DataCube expects webapp at: " + webAppDevBaseUrl).reset()); + } + this.client.getTerminal().writer().println(ansi().fgBrightBlack().a("[DEV] DataCube has started at port: " + this.port).reset()); + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServerHelpers.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServerHelpers.java new file mode 100644 index 00000000000..96f91bcdb63 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/REPLServerHelpers.java @@ -0,0 +1,278 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.sun.net.httpserver.Filter; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import org.eclipse.collections.api.factory.Lists; +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.impl.utility.ListIterate; +import org.finos.legend.engine.language.pure.grammar.to.DEPRECATED_PureGrammarComposerCore; +import org.finos.legend.engine.plan.execution.PlanExecutor; +import org.finos.legend.engine.plan.execution.result.builder.tds.TDSBuilder; +import org.finos.legend.engine.plan.execution.stores.relational.result.RelationalResult; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.executionPlan.nodes.SQLExecutionNode; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.application.AppliedFunction; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.ClassInstance; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.PackageableElementPtr; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.classInstance.relation.ColSpec; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.classInstance.relation.ColSpecArray; +import org.finos.legend.engine.repl.client.Client; +import org.finos.legend.engine.repl.core.commands.Execute; +import org.finos.legend.engine.repl.core.legend.LegendInterface; +import org.finos.legend.engine.repl.relational.server.model.DataCubeQuery; +import org.finos.legend.engine.repl.relational.server.model.DataCubeQueryColumn; +import org.finos.legend.engine.repl.relational.server.model.DataCubeQuerySourceREPLExecutedQuery; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_QUALIFIED_PATH; + +public class REPLServerHelpers +{ + public static void handleResponse(HttpExchange exchange, int responseCode, String response, REPLServerState state) + { + try + { + OutputStream os = exchange.getResponseBody(); + byte[] byteResponse = response != null ? response.getBytes(StandardCharsets.UTF_8) : new byte[0]; + exchange.sendResponseHeaders(responseCode, byteResponse.length); + os.write(byteResponse); + os.close(); + } + catch (IOException e) + { + state.log(e.getMessage()); + } + } + + public static Map getQueryParams(HttpExchange exchange) + { + String query = exchange.getRequestURI().getQuery(); + Map result = new HashMap<>(); + if (query == null) + { + return result; + } + for (String param : query.split("&")) + { + String[] entry = param.split("="); + if (entry.length > 1) + { + result.put(entry[0], entry[1]); + } + else + { + result.put(entry[0], ""); + } + } + return result; + } + + public static class REPLServerState + { + public final ObjectMapper objectMapper; + public final PlanExecutor planExecutor; + public final LegendInterface legendInterface; + public Long startTime; + + private Client client; + private PureModelContextData currentPureModelContextData; + private DataCubeQuery query; + + public REPLServerState(ObjectMapper objectMapper, PlanExecutor planExecutor, LegendInterface legendInterface) + { + this.objectMapper = objectMapper; + this.planExecutor = planExecutor; + this.legendInterface = legendInterface; + } + + public void initializeWithREPLExecutedQuery(Execute.ExecuteResult executeResult) + { + this.currentPureModelContextData = executeResult.pureModelContextData; + + this.startTime = System.currentTimeMillis(); + this.query = new DataCubeQuery(); + this.query.name = "New Report"; + this.query.configuration = null; // initially, the config is not initialized + + // process source + DataCubeQuerySourceREPLExecutedQuery source = new DataCubeQuerySourceREPLExecutedQuery(); + + if (!(executeResult.result instanceof RelationalResult) || !(((RelationalResult) executeResult.result).builder instanceof TDSBuilder)) + { + throw new RuntimeException("Can't initialize DataCube. Last executed query did not produce a TDS (i.e. data-grid), try a different query..."); + } + + RelationType relationType; + try + { + relationType = (RelationType) executeResult.pureModel.getConcreteFunctionDefinition(REPL_RUN_FUNCTION_QUALIFIED_PATH, null)._expressionSequence().getLast()._genericType()._typeArguments().getFirst()._rawType(); + } + catch (Exception e) + { + throw new RuntimeException("Can't initialize DataCube. Last executed query must return a relation type, try a different query..."); + } + + RelationalResult result = (RelationalResult) executeResult.result; + source.columns = ListIterate.collect(((TDSBuilder) result.builder).columns, col -> new DataCubeQueryColumn(col.name, col.type)); + + // try to extract the runtime for the query + // remove any usage of multiple from(), only add one to the end + // TODO: we might need to account for other variants of ->from(), such as when mapping is specified + Function function = (Function) ListIterate.select(executeResult.pureModelContextData.getElements(), e -> e.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH)).getFirst(); + String runtime = null; + MutableList fns = Lists.mutable.empty(); + ValueSpecification currentExpression = function.body.get(0); + while (currentExpression instanceof AppliedFunction) + { + AppliedFunction fn = (AppliedFunction) currentExpression; + if (fn.function.equals("from")) + { + String newRuntime = ((PackageableElementPtr) fn.parameters.get(1)).fullPath; + if (runtime != null && !runtime.equals(newRuntime)) + { + throw new RuntimeException("Can't initialize DataCube. Source query contains multiple different ->from(), only one is expected"); + } + else + { + runtime = newRuntime; + } + } + else + { + fns.add(fn); + } + currentExpression = fn.parameters.get(0); + } + for (AppliedFunction fn : fns) + { + fn.parameters.set(0, currentExpression); + currentExpression = fn; + } + + this.query.partialQuery = ""; + source.query = currentExpression.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); + source.runtime = runtime; + this.query.source = source; + + // build the partial query + // NOTE: for this, the initial query is going to be a select all + AppliedFunction partialFn = new AppliedFunction(); + partialFn.function = "select"; + ColSpecArray colSpecArray = new ColSpecArray(); + colSpecArray.colSpecs = ListIterate.collect(source.columns, col -> + { + ColSpec colSpec = new ColSpec(); + colSpec.name = col.name; + return colSpec; + }); + partialFn.parameters = Lists.mutable.with(new ClassInstance("colSpecArray", colSpecArray, null)); + this.query.partialQuery = partialFn.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); + + // build the full query + AppliedFunction fullFn = new AppliedFunction(); + fullFn.function = "from"; + fullFn.parameters = Lists.mutable.with(partialFn, new PackageableElementPtr(runtime)); + partialFn.parameters = Lists.mutable.with(currentExpression).withAll(partialFn.parameters); + this.query.query = fullFn.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); + } + + public PureModelContextData getCurrentPureModelContextData() + { + PureModelContextData data = this.currentPureModelContextData; + if (data == null) + { + throw new RuntimeException("Can't retrieve current graph data. Try to load or run a query in REPL before launching DataCube..."); + } + return data; + } + + public DataCubeQuery getQuery() + { + return this.query; + } + + public void setClient(Client client) + { + this.client = client; + } + + public void log(String message) + { + if (this.client != null) + { + this.client.getTerminal().writer().println(message); + } + } + } + + public interface DataCubeServerHandler + { + HttpHandler getHandler(REPLServerState state); + } + + public static class DEV__CORSFilter extends Filter + { + private final MutableList allowedOrigins; + + DEV__CORSFilter(MutableList allowedOrigins) + { + super(); + if (allowedOrigins.isEmpty()) + { + throw new IllegalArgumentException("Can't configure CORS filter: Allowed origins cannot be empty"); + } + this.allowedOrigins = allowedOrigins; + } + + @Override + public void doFilter(HttpExchange exchange, Chain chain) throws IOException + { + Headers headers = exchange.getResponseHeaders(); + headers.add("Access-Control-Allow-Origin", allowedOrigins.makeString(",")); + headers.add("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS"); + headers.add("Access-Control-Allow-Credentials", "true"); + headers.add("chainPreflight", "false"); + headers.add("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers"); + + if (exchange.getRequestMethod().equalsIgnoreCase("OPTIONS")) + { + exchange.sendResponseHeaders(204, -1); + } + else + { + chain.doFilter(exchange); + } + } + + @Override + public String description() + { + return "CORSFilter"; + } + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeInfrastructure.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeInfrastructure.java new file mode 100644 index 00000000000..b46c1a86736 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeInfrastructure.java @@ -0,0 +1,98 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.handler; + +import com.sun.net.httpserver.HttpHandler; +import org.apache.commons.io.IOUtils; +import org.finos.legend.engine.repl.relational.server.REPLServer; + +import java.io.InputStream; +import java.io.OutputStream; + +import static org.finos.legend.engine.repl.relational.server.REPLServerHelpers.*; + +public class DataCubeInfrastructure +{ + public static class GridLicenseKey implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("GET".equals(exchange.getRequestMethod())) + { + try + { + String licenseKey = System.getProperty("legend.repl.dataCube.gridLicenseKey") == null ? "" : System.getProperty("legend.repl.dataCube.gridLicenseKey"); + String key = state.objectMapper.writeValueAsString(licenseKey); + handleResponse(exchange, 200, key, state); + } + catch (Exception e) + { + handleResponse(exchange, 500, e.getMessage(), state); + } + } + }; + } + } + + public static class StaticContent implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("GET".equals(exchange.getRequestMethod())) + { + String[] path = exchange.getRequestURI().getPath().split("/repl/"); + String resourcePath = "/web-content/dist/repl/" + (path[1].equals("dataCube") ? "index.html" : path[1]); + try (OutputStream os = exchange.getResponseBody(); + InputStream is = REPLServer.class.getResourceAsStream(resourcePath) + ) + { + if (is == null) + { + exchange.sendResponseHeaders(404, -1); + } + else + { + if (resourcePath.endsWith(".html")) + { + exchange.getResponseHeaders().add("Content-Type", "text/html; charset=utf-8"); + } + if (resourcePath.endsWith(".js")) + { + exchange.getResponseHeaders().add("Content-Type", "text/javascript; charset=utf-8"); + } + else if (resourcePath.endsWith(".css")) + { + exchange.getResponseHeaders().add("Content-Type", "text/css; charset=utf-8"); + } + + exchange.sendResponseHeaders(200, 0); + IOUtils.copy(is, os); + } + } + catch (Exception e) + { + handleResponse(exchange, 500, e.getMessage(), state); + } + } + }; + } + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryBuilder.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryBuilder.java new file mode 100644 index 00000000000..21250f141cb --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryBuilder.java @@ -0,0 +1,217 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.handler; + +import com.sun.net.httpserver.HttpHandler; +import org.eclipse.collections.impl.map.mutable.MapAdapter; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; +import org.finos.legend.engine.repl.autocomplete.CompletionResult; +import org.finos.legend.engine.repl.relational.server.DataCubeHelpers; +import org.finos.legend.engine.repl.relational.server.model.*; +import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import static org.finos.legend.engine.repl.relational.server.REPLServerHelpers.*; + +public class DataCubeQueryBuilder +{ + public static class ParseQuery implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeParseQueryInput input = state.objectMapper.readValue(requestBody, DataCubeParseQueryInput.class); + ValueSpecification result = DataCubeHelpers.parseQuery(input.code, input.returnSourceInformation); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + } + catch (Exception e) + { + handleResponse(exchange, 400, e instanceof EngineException ? state.objectMapper.writeValueAsString(e) : e.getMessage(), state); + } + } + }; + } + } + + public static class GetQueryCode implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeGetQueryCodeInput input = state.objectMapper.readValue(requestBody, DataCubeGetQueryCodeInput.class); + handleResponse(exchange, 200, DataCubeHelpers.getQueryCode(input.query, input.pretty), state); + } + catch (Exception e) + { + handleResponse(exchange, 400, e.getMessage(), state); + } + } + }; + } + } + + public static class GetQueryCodeBatch implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeGetQueryCodeBatchInput input = state.objectMapper.readValue(requestBody, DataCubeGetQueryCodeBatchInput.class); + DataCubeGetQueryCodeBatchResult result = new DataCubeGetQueryCodeBatchResult(); + MapAdapter.adapt(input.queries).forEachKeyValue((key, value) -> + { + try + { + result.queries.put(key, DataCubeHelpers.getQueryCode(value, input.pretty)); + } + catch (Exception e) + { + result.queries.put(key, null); + } + }); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + } + catch (Exception e) + { + handleResponse(exchange, 400, e.getMessage(), state); + } + } + }; + } + } + + public static class QueryTypeahead implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeQueryTypeaheadInput input = state.objectMapper.readValue(requestBody, DataCubeQueryTypeaheadInput.class); + PureModelContextData data = state.getCurrentPureModelContextData(); + CompletionResult result = DataCubeHelpers.getCodeTypeahead(input.code, input.isPartial, data); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result.getCompletion()), state); + } + catch (Exception e) + { + handleResponse(exchange, 500, e.getMessage(), state); + } + } + }; + } + } + + public static class GetRelationReturnType implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeGetQueryRelationReturnTypeInput input = state.objectMapper.readValue(requestBody, DataCubeGetQueryRelationReturnTypeInput.class); + Lambda lambda = input.query; // if no lambda is specified, we're executing the initial query + PureModelContextData data = DataCubeHelpers.injectNewFunction(state.getCurrentPureModelContextData(), lambda).getOne(); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(DataCubeHelpers.getRelationReturnType(state.legendInterface, data)), state); + } + catch (Exception e) + { + handleResponse(exchange, 500, e instanceof EngineException ? state.objectMapper.writeValueAsString(e) : e.getMessage(), state); + } + } + }; + } + } + + public static class GetBaseQuery implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("GET".equals(exchange.getRequestMethod())) + { + try + { + DataCubeQuery query = state.getQuery(); + if (query != null) + { + DataCubeGetBaseQueryResult result = new DataCubeGetBaseQueryResult(); + result.timestamp = state.startTime; + result.query = query; + result.partialQuery = DataCubeHelpers.parseQuery(query.partialQuery, false); + result.sourceQuery = DataCubeHelpers.parseQuery(query.source.query, false); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + } + else + { + throw new RuntimeException("DataCube base query has not been set!"); + } + } + catch (Exception e) + { + handleResponse(exchange, 500, e.getMessage(), state); + } + } + }; + } + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryExecutor.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryExecutor.java new file mode 100644 index 00000000000..bcd5a03cc6c --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/handler/DataCubeQueryExecutor.java @@ -0,0 +1,62 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.handler; + +import com.sun.net.httpserver.HttpHandler; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; +import org.finos.legend.engine.repl.relational.server.DataCubeHelpers; +import org.finos.legend.engine.repl.relational.server.model.DataCubeExecutionInput; +import org.finos.legend.engine.repl.relational.server.model.DataCubeExecutionResult; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +import static org.finos.legend.engine.repl.relational.server.DataCubeHelpers.executeQuery; +import static org.finos.legend.engine.repl.relational.server.REPLServerHelpers.*; + +public class DataCubeQueryExecutor +{ + public static class ExecuteQuery implements DataCubeServerHandler + { + @Override + public HttpHandler getHandler(REPLServerState state) + { + return exchange -> + { + if ("POST".equals(exchange.getRequestMethod())) + { + try + { + InputStreamReader inputStreamReader = new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8); + BufferedReader bufferReader = new BufferedReader(inputStreamReader); + String requestBody = bufferReader.lines().collect(Collectors.joining()); + DataCubeExecutionInput input = state.objectMapper.readValue(requestBody, DataCubeExecutionInput.class); + Lambda lambda = input.query; + PureModelContextData data = DataCubeHelpers.injectNewFunction(state.getCurrentPureModelContextData(), lambda).getOne(); + DataCubeExecutionResult result = executeQuery(state.legendInterface, state.planExecutor, data); + handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + } + catch (Exception e) + { + handleResponse(exchange, 500, e.getMessage(), state); + } + } + }; + } + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionInput.java new file mode 100644 index 00000000000..2df93d37859 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionInput.java @@ -0,0 +1,22 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; + +public class DataCubeExecutionInput +{ + public Lambda query; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionResult.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionResult.java new file mode 100644 index 00000000000..857f9ed0dc2 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeExecutionResult.java @@ -0,0 +1,20 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +public class DataCubeExecutionResult +{ + public String result; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetBaseQueryResult.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetBaseQueryResult.java new file mode 100644 index 00000000000..21ab4acbdde --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetBaseQueryResult.java @@ -0,0 +1,25 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; + +public class DataCubeGetBaseQueryResult +{ + public DataCubeQuery query; + public Long timestamp; + public ValueSpecification partialQuery; + public ValueSpecification sourceQuery; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchInput.java new file mode 100644 index 00000000000..04825178200 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchInput.java @@ -0,0 +1,25 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; + +import java.util.Map; + +public class DataCubeGetQueryCodeBatchInput +{ + public Map queries; + public Boolean pretty; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchResult.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchResult.java new file mode 100644 index 00000000000..2b3a8879284 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeBatchResult.java @@ -0,0 +1,22 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import java.util.Map; + +public class DataCubeGetQueryCodeBatchResult +{ + public Map queries; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeInput.java new file mode 100644 index 00000000000..0f43bcc4d41 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryCodeInput.java @@ -0,0 +1,23 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; + +public class DataCubeGetQueryCodeInput +{ + public ValueSpecification query; + public Boolean pretty; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryRelationReturnTypeInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryRelationReturnTypeInput.java new file mode 100644 index 00000000000..e44383e2cd3 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeGetQueryRelationReturnTypeInput.java @@ -0,0 +1,22 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; + +public class DataCubeGetQueryRelationReturnTypeInput +{ + public Lambda query; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeParseQueryInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeParseQueryInput.java new file mode 100644 index 00000000000..5fad8067b2a --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeParseQueryInput.java @@ -0,0 +1,21 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +public class DataCubeParseQueryInput +{ + public String code; + public Boolean returnSourceInformation; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuery.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuery.java new file mode 100644 index 00000000000..da9635b6507 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuery.java @@ -0,0 +1,31 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import com.fasterxml.jackson.annotation.JsonRawValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; + +public class DataCubeQuery +{ + public String name; + public String query; + public String partialQuery; + public DataCubeQuerySource source; + + // NOTE: we don't need to process the config, so we will leave it as raw JSON + @JsonRawValue + @JsonDeserialize(using = DataCubeQueryConfigurationDeserializer.class) + public String configuration; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryColumn.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryColumn.java new file mode 100644 index 00000000000..7b7bf40d0e2 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryColumn.java @@ -0,0 +1,27 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +public class DataCubeQueryColumn +{ + public String name; + public String type; + + public DataCubeQueryColumn(String name, String type) + { + this.name = name; + this.type = type; + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryConfigurationDeserializer.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryConfigurationDeserializer.java new file mode 100644 index 00000000000..812eb87b4b5 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryConfigurationDeserializer.java @@ -0,0 +1,32 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; + +import java.io.IOException; + +public class DataCubeQueryConfigurationDeserializer extends JsonDeserializer +{ + @Override + public String deserialize(JsonParser jp, DeserializationContext ctx) throws IOException + { + TreeNode tree = jp.getCodec().readTree(jp); + return tree.toString(); + } +} \ No newline at end of file diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySource.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySource.java new file mode 100644 index 00000000000..d6239b9e754 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySource.java @@ -0,0 +1,31 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.util.List; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DataCubeQuerySourceREPLExecutedQuery.class, name = "REPLExecutedQuery") +}) +public abstract class DataCubeQuerySource +{ + public String query; + public String runtime; + public List columns; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySourceREPLExecutedQuery.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySourceREPLExecutedQuery.java new file mode 100644 index 00000000000..c958c05bc89 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQuerySourceREPLExecutedQuery.java @@ -0,0 +1,19 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +public class DataCubeQuerySourceREPLExecutedQuery extends DataCubeQuerySource +{ +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryTypeaheadInput.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryTypeaheadInput.java new file mode 100644 index 00000000000..ca3afec59e5 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/server/model/DataCubeQueryTypeaheadInput.java @@ -0,0 +1,21 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl.relational.server.model; + +public class DataCubeQueryTypeaheadInput +{ + public String code; + public Boolean isPartial; +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java index a6d4c822ffb..172a4d63162 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java @@ -47,21 +47,21 @@ public void testAutocompleteFunctionParameter() @Test public void testArrowOnFunction() { - Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|$x.col == 'oo')->"))); + Assert.assertEquals("[distinct , distinct(], [drop , drop(], [select , select(], [extend , extend(], [filter , filter(], [from , from(], [groupBy , groupBy(], [join , join(], [limit , limit(], [rename , rename(], [size , size(], [slice , slice(], [sort , sort(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|$x.col == 'oo')->"))); Assert.assertEquals("PARSER error at [6:1-23]: parsing error", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->limit(10)-").getEngineException().toPretty()); } @Test public void testArrowRelation() { - Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->"))); - Assert.assertEquals("[select , select], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->s"))); + Assert.assertEquals("[distinct , distinct(], [drop , drop(], [select , select(], [extend , extend(], [filter , filter(], [from , from(], [groupBy , groupBy(], [join , join(], [limit , limit(], [rename , rename(], [size , size(], [slice , slice(], [sort , sort(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->"))); + Assert.assertEquals("[select , select(], [size , size(], [slice , slice(], [sort , sort(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->s"))); } @Test public void testArrowDeep() { - Assert.assertEquals("[contains , contains], [startsWith , startsWith], [endsWith , endsWith], [toLower , toLower], [toUpper , toUpper], [lpad , lpad], [rpad , rpad], [parseInteger , parseInteger], [parseFloat , parseFloat]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(f|$f.col->"))); + Assert.assertEquals("[contains , contains(], [startsWith , startsWith(], [endsWith , endsWith(], [toLower , toLower(], [toUpper , toUpper(], [lpad , lpad(], [rpad , rpad(], [parseInteger , parseInteger(], [parseFloat , parseFloat(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(f|$f.col->"))); } @Test @@ -82,13 +82,13 @@ public void testDeepWithCompilationError() @Test public void testArrowPostCol() { - Assert.assertEquals("[ascending , ascending], [descending , descending]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->sort(~col->"))); + Assert.assertEquals("[ascending , ascending(], [descending , descending(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->sort(~col->"))); } @Test public void testMultiLevelFunction() { - Assert.assertEquals("[select , select]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->selec"))); + Assert.assertEquals("[select , select(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->selec"))); Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->select(~"))); } @@ -104,7 +104,6 @@ public void testDotInFilterDeepRelation() Assert.assertEquals("['na col' , 'na col']", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"na col\" VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|$x.'na"))); } - //-------- // Rename //-------- @@ -141,10 +140,9 @@ public void testGroupBy() Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~[col,"))); Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~z:x|$x."))); Assert.assertEquals("[val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.v"))); - Assert.assertEquals("[sum , sum], [mean , mean], [average , average], [min , min], [max , max], [count , count], [percentile , percentile], [variancePopulation , variancePopulation], [varianceSample , varianceSample], [stdDevPopulation , stdDevPopulation], [stdDevSample , stdDevSample]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.val:y|$y->"))); + Assert.assertEquals("[sum , sum(], [mean , mean(], [average , average(], [min , min(], [max , max(], [count , count(], [percentile , percentile(], [variancePopulation , variancePopulation(], [varianceSample , varianceSample(], [stdDevPopulation , stdDevPopulation(], [stdDevSample , stdDevSample(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.val:y|$y->"))); } - //------ // Join //------ @@ -169,8 +167,8 @@ public void testSelect() { Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~c"))); Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col,"))); - Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~'col space')->fro"))); - Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~['col space'])->fro"))); + Assert.assertEquals("[from , from(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~'col space')->fro"))); + Assert.assertEquals("[from , from(]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~['col space'])->fro"))); } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestDataCubeHelpers.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestDataCubeHelpers.java new file mode 100644 index 00000000000..39b51301e05 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestDataCubeHelpers.java @@ -0,0 +1,287 @@ +// Copyright 2024 Goldman Sachs +// +// 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 org.finos.legend.engine.repl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.finos.legend.engine.plan.execution.PlanExecutor; +import org.finos.legend.engine.plan.execution.stores.relational.serialization.RelationalResultToJsonDefaultSerializer; +import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.Lambda; +import org.finos.legend.engine.repl.core.legend.LegendInterface; +import org.finos.legend.engine.repl.core.legend.LocalLegendInterface; +import org.finos.legend.engine.repl.relational.server.DataCubeHelpers; +import org.finos.legend.engine.repl.relational.server.model.DataCubeExecutionResult; +import org.finos.legend.engine.shared.core.ObjectMapperFactory; +import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +import static org.finos.legend.engine.repl.core.Helpers.REPL_RUN_FUNCTION_SIGNATURE; +import static org.finos.legend.engine.repl.relational.server.DataCubeHelpers.executeQuery; + +public class TestDataCubeHelpers +{ + private static final ObjectMapper objectMapper = ObjectMapperFactory.getNewStandardObjectMapperWithPureProtocolExtensionSupports(); + private static final PlanExecutor planExecutor = PlanExecutor.newPlanExecutorBuilder().withAvailableStoreExecutors().build(); + private final LegendInterface legendInterface = new LocalLegendInterface(); + private final PureModelContextData pureModelContextData = legendInterface.parse( + "###Relational\n" + + "Database test::TestDatabase\n" + + "(\n" + + " Table TEST0\n" + + " (\n" + + " FIRSTNAME VARCHAR(200),\n" + + " LASTNAME VARCHAR(200)\n" + + " )\n" + + ")\n" + + "\n" + + "###Pure\n" + + "function " + REPL_RUN_FUNCTION_SIGNATURE + "\n" + + "{\n" + + " #>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->select(~FIRSTNAME)->from(test::test)\n" + + "}\n" + + "\n" + + "###Runtime\n" + + "Runtime test::test\n" + + "{\n" + + " mappings: [];\n" + + " connections:\n" + + " [\n" + + " test::TestDatabase:\n" + + " [ \n" + + " connection: test::TestConnection\n" + + " ]\n" + + "\n" + + " ];\n" + + "}\n" + + "\n" + + "\n" + + "###Connection\n" + + "RelationalDatabaseConnection test::TestConnection\n" + + "{\n" + + " store: test::TestDatabase;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " '\\nDrop table if exists TEST0;\\nCreate Table TEST0(FIRSTNAME VARCHAR(200), LASTNAME VARCHAR(200));\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'John\\', \\'Doe\\');\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'Tim\\', \\'Smith\\');\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'Nicole\\', \\'Doe\\');\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}" + ); + + @Test + public void testExecuteSort() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->sort([~FIRSTNAME->ascending()])"; + String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", \\\"test0_0\\\".LASTNAME as \\\"LASTNAME\\\" from TEST0 as \\\"test0_0\\\" where (\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) order by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"LASTNAME\"],\"rows\":[{\"values\":[\"John\",\"Doe\"]},{\"values\":[\"Nicole\",\"Doe\"]},{\"values\":[\"Tim\",\"Smith\"]}]}}"; + testExecuteQuery(expectedResult, lambda); + } + + @Test + public void testExecuteFilter() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | ($c.FIRSTNAME != 'Doe' && $c.LASTNAME != 'Doe'))->from(test::test)->sort([~FIRSTNAME->ascending()])"; + String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", \\\"test0_0\\\".LASTNAME as \\\"LASTNAME\\\" from TEST0 as \\\"test0_0\\\" where ((\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) and (\\\"test0_0\\\".LASTNAME <> 'Doe' OR \\\"test0_0\\\".LASTNAME is null)) order by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"LASTNAME\"],\"rows\":[{\"values\":[\"Tim\",\"Smith\"]}]}}"; + testExecuteQuery(expectedResult, lambda); + } + + @Test + public void testExecuteGroupBy() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"count\",\"type\":\"Integer\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", count(\\\"test0_0\\\".FIRSTNAME) as \\\"count\\\" from TEST0 as \\\"test0_0\\\" where (\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) group by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"count\"],\"rows\":[{\"values\":[\"John\",1]},{\"values\":[\"Nicole\",1]},{\"values\":[\"Tim\",1]}]}}"; + testExecuteQuery(expectedResult, lambda); + } + + private void testExecuteQuery(String expectedResult, String code) + { + try + { + Lambda lambda = (Lambda) DataCubeHelpers.parseQuery(code, false); + PureModelContextData data = DataCubeHelpers.injectNewFunction(pureModelContextData, lambda).getOne(); + DataCubeExecutionResult result = executeQuery(legendInterface, planExecutor, data); + Assert.assertEquals(expectedResult, RelationalResultToJsonDefaultSerializer.removeComment(result.result)); + } + catch (IOException e) + { + throw new RuntimeException(e.getMessage()); + } + } + + @Test + public void testParseQuerySimple() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedQuery = "{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"groupBy\",\"parameters\":[{\"_type\":\"func\",\"function\":\"from\",\"parameters\":[{\"_type\":\"func\",\"function\":\"filter\",\"parameters\":[{\"_type\":\"classInstance\",\"type\":\">\",\"value\":{\"path\":[\"test::TestDatabase\",\"TEST0\"]}},{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"not\",\"parameters\":[{\"_type\":\"func\",\"function\":\"equal\",\"parameters\":[{\"_type\":\"property\",\"parameters\":[{\"_type\":\"var\",\"name\":\"c\"}],\"property\":\"FIRSTNAME\"},{\"_type\":\"string\",\"value\":\"Doe\"}]}]}],\"parameters\":[{\"_type\":\"var\",\"name\":\"c\"}]}]},{\"_type\":\"packageableElementPtr\",\"fullPath\":\"test::test\"}]},{\"_type\":\"classInstance\",\"type\":\"colSpecArray\",\"value\":{\"colSpecs\":[{\"name\":\"FIRSTNAME\"}]}},{\"_type\":\"classInstance\",\"type\":\"colSpecArray\",\"value\":{\"colSpecs\":[{\"function1\":{\"_type\":\"lambda\",\"body\":[{\"_type\":\"property\",\"parameters\":[{\"_type\":\"var\",\"name\":\"x\"}],\"property\":\"FIRSTNAME\"}],\"parameters\":[{\"_type\":\"var\",\"name\":\"x\"}]},\"function2\":{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"count\",\"parameters\":[{\"_type\":\"var\",\"name\":\"y\"}]}],\"parameters\":[{\"_type\":\"var\",\"name\":\"y\"}]},\"name\":\"count\"}]}}]}],\"parameters\":[]}"; + testParseQuery(expectedQuery, lambda, false); + } + + @Test + public void testParseQuerySimpleWithSourceInformationReturned() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedQuery = "{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"groupBy\",\"parameters\":[{\"_type\":\"func\",\"function\":\"from\",\"parameters\":[{\"_type\":\"func\",\"function\":\"filter\",\"parameters\":[{\"_type\":\"classInstance\",\"sourceInformation\":{\"endColumn\":30,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":2,\"startLine\":1},\"type\":\">\",\"value\":{\"path\":[\"test::TestDatabase\",\"TEST0\"],\"sourceInformation\":{\"endColumn\":30,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":2,\"startLine\":1}}},{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"not\",\"parameters\":[{\"_type\":\"func\",\"function\":\"equal\",\"parameters\":[{\"_type\":\"property\",\"parameters\":[{\"_type\":\"var\",\"name\":\"c\",\"sourceInformation\":{\"endColumn\":45,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":44,\"startLine\":1}}],\"property\":\"FIRSTNAME\",\"sourceInformation\":{\"endColumn\":55,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":47,\"startLine\":1}},{\"_type\":\"string\",\"sourceInformation\":{\"endColumn\":64,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":60,\"startLine\":1},\"value\":\"Doe\"}]}],\"sourceInformation\":{\"endColumn\":58,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":57,\"startLine\":1}}],\"parameters\":[{\"_type\":\"var\",\"name\":\"c\"}],\"sourceInformation\":{\"endColumn\":64,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":42,\"startLine\":1}}],\"sourceInformation\":{\"endColumn\":38,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":33,\"startLine\":1}},{\"_type\":\"packageableElementPtr\",\"fullPath\":\"test::test\",\"sourceInformation\":{\"endColumn\":82,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":73,\"startLine\":1}}],\"sourceInformation\":{\"endColumn\":71,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":68,\"startLine\":1}},{\"_type\":\"classInstance\",\"sourceInformation\":{\"endColumn\":105,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":95,\"startLine\":1},\"type\":\"colSpecArray\",\"value\":{\"colSpecs\":[{\"name\":\"FIRSTNAME\",\"sourceInformation\":{\"endColumn\":104,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":96,\"startLine\":1}}]}},{\"_type\":\"classInstance\",\"sourceInformation\":{\"endColumn\":151,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":109,\"startLine\":1},\"type\":\"colSpecArray\",\"value\":{\"colSpecs\":[{\"function1\":{\"_type\":\"lambda\",\"body\":[{\"_type\":\"property\",\"parameters\":[{\"_type\":\"var\",\"name\":\"x\",\"sourceInformation\":{\"endColumn\":122,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":121,\"startLine\":1}}],\"property\":\"FIRSTNAME\",\"sourceInformation\":{\"endColumn\":132,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":124,\"startLine\":1}}],\"parameters\":[{\"_type\":\"var\",\"name\":\"x\"}],\"sourceInformation\":{\"endColumn\":132,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":119,\"startLine\":1}},\"function2\":{\"_type\":\"lambda\",\"body\":[{\"_type\":\"func\",\"function\":\"count\",\"parameters\":[{\"_type\":\"var\",\"name\":\"y\",\"sourceInformation\":{\"endColumn\":141,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":140,\"startLine\":1}}],\"sourceInformation\":{\"endColumn\":148,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":144,\"startLine\":1}}],\"parameters\":[{\"_type\":\"var\",\"name\":\"y\"}],\"sourceInformation\":{\"endColumn\":150,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":138,\"startLine\":1}},\"name\":\"count\",\"sourceInformation\":{\"endColumn\":150,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":110,\"startLine\":1}}]}}],\"sourceInformation\":{\"endColumn\":92,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":86,\"startLine\":1}}],\"parameters\":[],\"sourceInformation\":{\"endColumn\":153,\"endLine\":1,\"sourceId\":\"\",\"startColumn\":1,\"startLine\":1}}"; + testParseQuery(expectedQuery, lambda, true); + } + + private void testParseQuery(String expectedQuery, String code, boolean returnSourceInformation) + { + try + { + Assert.assertEquals(expectedQuery, objectMapper.writeValueAsString(DataCubeHelpers.parseQuery(code, returnSourceInformation))); + } + catch (IOException e) + { + throw new RuntimeException(e.getMessage()); + } + } + + @Test + public void testGetQueryCodeStandard() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedQuery = "|#>{test::TestDatabase.TEST0}#->filter(c|!($c.FIRSTNAME == 'Doe'))->from(test::test)->groupBy(~[FIRSTNAME], ~[count:x|$x.FIRSTNAME:y|$y->count()])"; + testGetQueryCode(expectedQuery, lambda, false); + } + + @Test + public void testGetQueryCodePretty() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedQuery = "|#>{test::TestDatabase.TEST0}#->filter(\n" + + " c|!($c.FIRSTNAME == 'Doe')\n" + + ")->from(\n" + + " test::test\n" + + ")->groupBy(\n" + + " ~[\n" + + " FIRSTNAME\n" + + " ],\n" + + " ~[\n" + + " count: x|$x.FIRSTNAME:y|$y->count()\n" + + " ]\n" + + ")"; + testGetQueryCode(expectedQuery, lambda, true); + } + + private void testGetQueryCode(String expectedQuery, String code, boolean pretty) + { + ValueSpecification query = DataCubeHelpers.parseQuery(code, false); + Assert.assertEquals(expectedQuery, DataCubeHelpers.getQueryCode(query, pretty)); + } + + @Test + public void testTypeaheadPartial() + { + String code = "->extend(~[newCol:c|'ok', colX: c|$c."; + String expectedResult = "{\"completion\":[{\"completion\":\"FIRSTNAME\",\"display\":\"FIRSTNAME\"}]}"; + testTypeahead(expectedResult, code, true); + } + + @Test + public void testTypeaheadFull() + { + String code = "#>{test::TestDatabase.TEST0}#->extend(~[newCol:c|'ok', colX: c|$c."; + String expectedResult = "{\"completion\":[{\"completion\":\"FIRSTNAME\",\"display\":\"FIRSTNAME\"},{\"completion\":\"LASTNAME\",\"display\":\"LASTNAME\"}]}"; + testTypeahead(expectedResult, code, false); + } + + @Test + public void testTypeaheadFullWithError() + { + String code = "#>{test::TestDatabase.TEST0}#-->extend(~[newCol:c|'ok', colX: c|$c."; + String expectedResult = "{\"completion\":[]}"; + testTypeahead(expectedResult, code, false); + } + + private void testTypeahead(String expectedResult, String code, boolean isPartial) + { + try + { + Assert.assertEquals(expectedResult, objectMapper.writeValueAsString(DataCubeHelpers.getCodeTypeahead(code, isPartial, pureModelContextData))); + } + catch (IOException e) + { + throw new RuntimeException(e.getMessage()); + } + } + + @Test + public void testExtractRelationReturnTypeGroupBy() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; + String expectedResult = "{\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"count\",\"type\":\"Integer\"}]}"; + testExtractRelationReturnType(expectedResult, lambda); + } + + @Test + public void testExtractRelationReturnTypeSimpleExtend() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->extend(~newCol:c|'ok')"; + String expectedResult = "{\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"},{\"name\":\"newCol\",\"type\":\"String\"}]}"; + testExtractRelationReturnType(expectedResult, lambda); + } + + @Test + public void testExtractRelationReturnTypeMultipleExtend() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->extend(~[newCol:c|'ok', colX: c|$c.FIRSTNAME])"; + String expectedResult = "{\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"},{\"name\":\"newCol\",\"type\":\"String\"},{\"name\":\"colX\",\"type\":\"String\"}]}"; + testExtractRelationReturnType(expectedResult, lambda); + } + + private void testExtractRelationReturnType(String expectedResult, String code) + { + try + { + Lambda lambda = (Lambda) DataCubeHelpers.parseQuery(code, false); + PureModelContextData data = DataCubeHelpers.injectNewFunction(pureModelContextData, lambda).getOne(); + Assert.assertEquals(expectedResult, objectMapper.writeValueAsString(DataCubeHelpers.getRelationReturnType(legendInterface, data))); + } + catch (IOException e) + { + throw new RuntimeException(e.getMessage()); + } + } + + @Test + public void testExtractRelationReturnTypeWithParserError() + { + String lambda = "|#>{test::TestDatabase.TEST0}#-->extend(~[newCol:c|'ok', colX: c|$c.FIRSTNAME])"; + testExtractRelationReturnTypeFailure("PARSER error at [1:31]: Unexpected token '-'. Valid alternatives: [';']", lambda); + } + + @Test + public void testExtractRelationReturnTypeWithCompilationError() + { + String lambda = "|#>{test::TestDatabase.TEST0}#->extend(~[newCol:c|'ok', colX: c|$c.FIRSTNAME2])"; + testExtractRelationReturnTypeFailure("COMPILATION error at [1:68-77]: The column 'FIRSTNAME2' can't be found in the relation (FIRSTNAME:String, LASTNAME:String)", lambda); + } + + private void testExtractRelationReturnTypeFailure(String errorMessage, String code) + { + EngineException e = Assert.assertThrows(EngineException.class, () -> + { + Lambda lambda = (Lambda) DataCubeHelpers.parseQuery(code, true); + PureModelContextData data = DataCubeHelpers.injectNewFunction(pureModelContextData, lambda).getOne(); + DataCubeHelpers.getRelationReturnType(legendInterface, data); + }); + Assert.assertEquals(errorMessage, EngineException.buildPrettyErrorMessage(e.getMessage(), e.getSourceInformation(), e.getErrorType())); + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestGridServer.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestGridServer.java deleted file mode 100644 index f017cb8025c..00000000000 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestGridServer.java +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2024 Goldman Sachs -// -// 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 org.finos.legend.engine.repl; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.collections.impl.factory.Lists; -import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; -import org.finos.legend.engine.plan.execution.stores.relational.serialization.RelationalResultToJsonDefaultSerializer; -import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.ValueSpecification; -import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.application.AppliedFunction; -import org.finos.legend.engine.repl.core.legend.LegendInterface; -import org.finos.legend.engine.repl.core.legend.LocalLegendInterface; -import org.finos.legend.engine.repl.relational.httpServer.ReplGridServer; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -public class TestGridServer -{ - private String pmcd = "###Relational\n" + - "Database test::TestDatabase\n" + - "(\n" + - " Table TEST0\n" + - " (\n" + - " FIRSTNAME VARCHAR(200),\n" + - " LASTNAME VARCHAR(200)\n" + - " )\n" + - ")\n" + - "\n" + - "###Pure\n" + - "function a::b::c::d(): Any[*]\n" + - "{\n" + - " #>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(^meta::pure::mapping::Mapping(), test::test)\n" + - "}\n" + - "\n" + - "###Runtime\n" + - "Runtime test::test\n" + - "{\n" + - " mappings: [];\n" + - " connections:\n" + - " [\n" + - " test::TestDatabase:\n" + - " [ \n" + - " connection: test::TestConnection\n" + - " ]\n" + - "\n" + - " ];\n" + - "}\n" + - "\n" + - "\n" + - "###Connection\n" + - "RelationalDatabaseConnection test::TestConnection\n" + - "{\n" + - " store: test::TestDatabase;\n" + - " type: H2;\n" + - " specification: LocalH2\n" + - " {\n" + - " testDataSetupSqls: [\n" + - " '\\nDrop table if exists TEST0;\\nCreate Table TEST0(FIRSTNAME VARCHAR(200), LASTNAME VARCHAR(200));\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'John\\', \\'Doe\\');\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'Tim\\', \\'Smith\\');\\nInsert into TEST0 (FIRSTNAME, LASTNAME) values (\\'Nicole\\', \\'Doe\\');\\n\\n'\n" + - " ];\n" + - " };\n" + - " auth: DefaultH2;\n" + - "}"; - private LegendInterface legendInterface = new LocalLegendInterface(); - private ObjectMapper objectMapper = new ObjectMapper(); - private PureModelContextData pureModelContextData = legendInterface.parse(pmcd); - - @Test - public void testSort() - { - String lambda = "#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(^meta::pure::mapping::Mapping(), test::test)->sort([~FIRSTNAME->ascending()])"; - String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", \\\"test0_0\\\".LASTNAME as \\\"LASTNAME\\\" from TEST0 as \\\"test0_0\\\" where (\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) order by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"LASTNAME\"],\"rows\":[{\"values\":[\"John\",\"Doe\"]},{\"values\":[\"Nicole\",\"Doe\"]},{\"values\":[\"Tim\",\"Smith\"]}]}}"; - test(expectedResult, lambda); - } - - @Test - public void testFilter() - { - String lambda = "#>{test::TestDatabase.TEST0}#->filter(c | ($c.FIRSTNAME != 'Doe' && $c.LASTNAME != 'Doe'))->from(^meta::pure::mapping::Mapping(), test::test)->sort([~FIRSTNAME->ascending()])"; - String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", \\\"test0_0\\\".LASTNAME as \\\"LASTNAME\\\" from TEST0 as \\\"test0_0\\\" where ((\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) and (\\\"test0_0\\\".LASTNAME <> 'Doe' OR \\\"test0_0\\\".LASTNAME is null)) order by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"LASTNAME\"],\"rows\":[{\"values\":[\"Tim\",\"Smith\"]}]}}"; - test(expectedResult, lambda); - } - - @Test - public void testGroupBy() - { - String lambda = "#>{test::TestDatabase.TEST0}#->filter(c | $c.FIRSTNAME != 'Doe')->from(^meta::pure::mapping::Mapping(), test::test)->groupBy(~[FIRSTNAME], ~[count: x | $x.FIRSTNAME : y | $y->count()])"; - String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"count\",\"type\":\"Integer\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", count(\\\"test0_0\\\".FIRSTNAME) as \\\"count\\\" from TEST0 as \\\"test0_0\\\" where (\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) group by \\\"FIRSTNAME\\\"\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"count\"],\"rows\":[{\"values\":[\"John\",1]},{\"values\":[\"Nicole\",1]},{\"values\":[\"Tim\",1]}]}}"; - test(expectedResult, lambda); - } - - @Test - public void testSlice() - { - String lambda = "#>{test::TestDatabase.TEST0}#->filter(c | ($c.FIRSTNAME != 'Doe' && $c.LASTNAME != 'Doe'))->from(^meta::pure::mapping::Mapping(), test::test)"; - String expectedResult = "{\"builder\":{\"_type\":\"tdsBuilder\",\"columns\":[{\"name\":\"FIRSTNAME\",\"type\":\"String\"},{\"name\":\"LASTNAME\",\"type\":\"String\"}]},\"activities\":[{\"_type\":\"relational\",\"sql\":\"select top 100 \\\"test0_0\\\".FIRSTNAME as \\\"FIRSTNAME\\\", \\\"test0_0\\\".LASTNAME as \\\"LASTNAME\\\" from TEST0 as \\\"test0_0\\\" where ((\\\"test0_0\\\".FIRSTNAME <> 'Doe' OR \\\"test0_0\\\".FIRSTNAME is null) and (\\\"test0_0\\\".LASTNAME <> 'Doe' OR \\\"test0_0\\\".LASTNAME is null))\"}],\"result\":{\"columns\":[\"FIRSTNAME\",\"LASTNAME\"],\"rows\":[{\"values\":[\"Tim\",\"Smith\"]}]}}"; - test(expectedResult, lambda, true); - } - - private void test(String expectedResult, String function) - { - test(expectedResult, function, false); - } - - private void test(String expectedResult, String function, boolean isPaginationEnabled) - { - try - { - Function originalFunction = (Function) pureModelContextData.getElements().stream().filter(e -> e.getPath().equals("a::b::c::d__Any_MANY_")).collect(Collectors.toList()).get(0); - ValueSpecification originalFunctionBody = originalFunction.body.get(0); - AppliedFunction currentFunction = (AppliedFunction) PureGrammarParser.newInstance().parseValueSpecification(function, null, 0, 0, true); - List newBody = Lists.mutable.of(currentFunction); - if (isPaginationEnabled) - { - ReplGridServer.applySliceFunction(newBody); - } - originalFunction.body = newBody; - String response = ReplGridServer.executeLambda(legendInterface, pureModelContextData, originalFunction, originalFunctionBody); - ReplGridServer.GridServerResult result = objectMapper.readValue(response, ReplGridServer.GridServerResult.class); - Assert.assertEquals(expectedResult, RelationalResultToJsonDefaultSerializer.removeComment(result.getResult())); - } - catch (IOException e) - { - throw new RuntimeException(e.getMessage()); - } - } -} diff --git a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 index 1d830c5acd6..d15f55b25eb 100644 --- a/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 +++ b/legend-engine-core/legend-engine-core-base/legend-engine-core-language-pure/legend-engine-language-pure-grammar/src/main/antlr4/org/finos/legend/engine/language/pure/grammar/from/antlr4/core/M3ParserGrammar.g4 @@ -111,7 +111,7 @@ columnBuilders: TILDE (oneColSpec | colSpecArray) ; oneColSpec: identifier ((COLON (type | lambdaParam lambdaPipe) extraFunction? ))? ; -colSpecArray: (BRACKET_OPEN oneColSpec(COMMA oneColSpec)* BRACKET_CLOSE) +colSpecArray: (BRACKET_OPEN (oneColSpec(COMMA oneColSpec)*)? BRACKET_CLOSE) ; extraFunction: (COLON lambdaParam lambdaPipe) ; diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java index 064f3881e6c..0e9a5900df2 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-PCT/src/test/java/org/finos/legend/engine/plan/execution/stores/relational/test/duckdb/pct/Test_Relational_DuckDB_RelationFunctions_PCT.java @@ -21,7 +21,6 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.DatabaseType; import org.finos.legend.engine.pure.runtime.testConnection.CoreExternalTestConnectionCodeRepositoryProvider; import org.finos.legend.engine.test.shared.framework.TestServerResource; -import org.finos.legend.pure.code.core.CoreRelationalduckdbCodeRepositoryProvider; import org.finos.legend.pure.code.core.RelationCodeRepositoryProvider; import org.finos.legend.pure.m3.pct.reports.config.PCTReportConfiguration; import org.finos.legend.pure.m3.pct.reports.config.exclusion.ExclusionSpecification; @@ -36,10 +35,7 @@ public class Test_Relational_DuckDB_RelationFunctions_PCT extends PCTReportConfi private static final ReportScope reportScope = RelationCodeRepositoryProvider.relationFunctions; private static final Adapter adapter = CoreExternalTestConnectionCodeRepositoryProvider.duckDBAdapter; private static final String platform = "compiled"; - private static final MutableList expectedFailures = Lists.mutable.with( - // BUG: Column name with special characters is not properly escaped - one("meta::pure::functions::relation::tests::select::testSingleSelectWithQuotedColumn_Function_1__Boolean_1_", "\"Unexpected error executing function with params [Anonymous_Lambda]\"") - ); + private static final MutableList expectedFailures = Lists.mutable.empty(); public static Test suite() { diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-execution/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/vendors/duckdb/DuckDBCommands.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-execution/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/vendors/duckdb/DuckDBCommands.java index 4d754c800f3..064b3407771 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-execution/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/vendors/duckdb/DuckDBCommands.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-execution/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/vendors/duckdb/DuckDBCommands.java @@ -47,6 +47,12 @@ public String load(String tableName, String location) return "CREATE TABLE " + tableName + " AS SELECT * FROM read_csv('" + location + "', header=true);"; } + @Override + public String dropTable(String tableName, String location) + { + return "DROP TABLE " + tableName + ";"; + } + @Override public T accept(RelationalDatabaseCommandsVisitor visitor) { diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-pure/src/main/resources/core_relational_duckdb/relational/sqlQueryToString/duckdbExtension.pure b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-pure/src/main/resources/core_relational_duckdb/relational/sqlQueryToString/duckdbExtension.pure index 0be5f50aa7a..3af137b73a4 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-pure/src/main/resources/core_relational_duckdb/relational/sqlQueryToString/duckdbExtension.pure +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-dbExtension/legend-engine-xt-relationalStore-duckdb/legend-engine-xt-relationalStore-duckdb-pure/src/main/resources/core_relational_duckdb/relational/sqlQueryToString/duckdbExtension.pure @@ -27,6 +27,7 @@ function <> meta::relational::functions::sqlQueryToString::duckD joinStringsProcessor = processJoinStringsOperationWithConcatCall_JoinStrings_1__SqlGenerationContext_1__String_1_, literalProcessor = $literalProcessor, selectSQLQueryProcessor = processSelectSQLQueryForDuckDB_SelectSQLQuery_1__SqlGenerationContext_1__Boolean_1__String_1_, + columnNameToIdentifier = columnNameToIdentifierForDuckDB_String_1__DbConfig_1__String_1_, identifierProcessor = processIdentifierWithDoubleQuotes_String_1__DbConfig_1__String_1_, dynaFuncDispatch = $dynaFuncDispatch, ddlCommandsTranslator = getDDLCommandsTranslator() @@ -220,6 +221,22 @@ function <> meta::relational::functions::sqlQueryToString::duckD '%s offset %s'->format([$format.separator, $s.fromRow->toOne()->getValueForTake($format, $dbConfig, $extensions)]) + if ($size == -1, | '', | ' limit %s'->format($size)); } +function <> meta::relational::functions::sqlQueryToString::duckDB::columnNameToIdentifierForDuckDB(columnName: String[1], dbConfig: DbConfig[1]): String[1] +{ + if( + $dbConfig.isDbReservedIdentifier($columnName->toLower()) || + // TODO: the way we handle quoting right now is not systematic, i.e. we allow so many way to interject custom logic + // as such, for duckdb, to keep it simple, we will not use the default implementation of converting column name to identifier + (!$columnName->startsWith('"') && ( + $columnName->contains(' ') || + $columnName->contains('/') || + $columnName->contains('|') + )), + |'"' + $columnName->toLower() + '"', + |$columnName + ); +} + function <> meta::relational::functions::sqlQueryToString::duckDB::duckDBReservedWords():String[*] { //https://github.com/duckdb/duckdb/blob/main/third_party/libpg_query/grammar/keywords/reserved_keywords.list diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan-connection/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/commands/RelationalDatabaseCommands.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan-connection/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/commands/RelationalDatabaseCommands.java index 2b8b8f66b65..4bdc40f8aae 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan-connection/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/commands/RelationalDatabaseCommands.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-execution/legend-engine-xt-relationalStore-executionPlan-connection/src/main/java/org/finos/legend/engine/plan/execution/stores/relational/connection/driver/commands/RelationalDatabaseCommands.java @@ -34,6 +34,11 @@ public String load(String tableName, String location) throw new RuntimeException("Load not implemented for " + this.getClass().getSimpleName()); } + public String dropTable(String tableName, String location) + { + throw new RuntimeException("Drop table not implemented for " + this.getClass().getSimpleName()); + } + public abstract IngestionMethod getDefaultIngestionMethod(); // public void buildTempTableFromResult(RelationalExecutionConfiguration config, Connection connection, StreamingResult result, String tableName) diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/RelationalCompilerExtension.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/RelationalCompilerExtension.java index 8756451cc58..7e3b4b5ac1f 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/RelationalCompilerExtension.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/main/java/org/finos/legend/engine/language/pure/compiler/toPureGraph/RelationalCompilerExtension.java @@ -843,6 +843,18 @@ else if (c instanceof Double) { primitiveType = "Float"; } + else if (c instanceof Decimal) + { + primitiveType = "Decimal"; + } + else if (c instanceof Date) + { + primitiveType = "Date"; + } + else if (c instanceof Timestamp) + { + primitiveType = "DateTime"; + } else { throw new RuntimeException("Implement support for '" + c.getClass().getName() + "'");