From cddcc195cef9fe464897dabc59c5cca695ad192c Mon Sep 17 00:00:00 2001 From: An Phi Date: Sun, 22 Dec 2024 22:18:31 -0500 Subject: [PATCH] repl: minor fixes (#3312) * repl: fix handling of column name that needs quotes * datacube: prepare for query persistence * bump webapp@12.86.0 --- .../repl/core/legend/LegendInterface.java | 9 +- .../core/legend/LocalLegendInterface.java | 16 +- .../dataCube/server/REPLServerHelpers.java | 163 ++++++++++++++---- .../handler/DataCubeInfrastructure.java | 24 ++- .../server/handler/DataCubeQueryBuilder.java | 57 ++++-- .../server/handler/DataCubeQueryExecutor.java | 4 +- .../model/DataCubeGetBaseQueryResult.java | 3 +- .../model/DataCubeInfrastructureInfo.java | 3 + .../dataCube/server/model/DataCubeQuery.java | 23 --- .../from/RelationalParseTreeWalker.java | 2 +- .../to/HelperRelationalGrammarComposer.java | 69 ++------ .../test/TestRelationalGrammarComposer.java | 26 +++ .../test/TestRelationalGrammarRoundtrip.java | 6 + .../test/resources/columnNameWithQuotes.json | 60 +++++++ pom.xml | 2 +- 15 files changed, 333 insertions(+), 134 deletions(-) delete mode 100644 legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeQuery.java create mode 100644 legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/resources/columnNameWithQuotes.json diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LegendInterface.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LegendInterface.java index af06c5f5aad..d02cd09c75f 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LegendInterface.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/legend/LegendInterface.java @@ -22,7 +22,14 @@ public interface LegendInterface { - PureModelContextData parse(String txt); + default PureModelContextData parse(String txt) + { + return this.parse(txt, true); + } + + PureModelContextData parse(String txt, boolean returnSourceInformation); + + String render(PureModelContextData model); PureModel compile(PureModelContextData model); 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 6fcd5667ea8..fcf844e97ce 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 @@ -14,13 +14,14 @@ package org.finos.legend.engine.repl.core.legend; -import java.util.concurrent.ForkJoinPool; import org.eclipse.collections.api.RichIterable; import org.eclipse.collections.api.tuple.Pair; import org.finos.legend.engine.language.pure.compiler.Compiler; import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModelProcessParameter; import org.finos.legend.engine.language.pure.grammar.from.PureGrammarParser; +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.generation.PlanGenerator; import org.finos.legend.engine.plan.platform.PlanPlatform; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; @@ -31,6 +32,7 @@ import org.finos.legend.pure.generated.Root_meta_pure_extension_Extension; import java.net.URL; +import java.util.concurrent.ForkJoinPool; import static org.finos.legend.engine.repl.shared.ExecutionHelper.REPL_RUN_FUNCTION_QUALIFIED_PATH; @@ -39,7 +41,7 @@ public class LocalLegendInterface implements LegendInterface private final ForkJoinPool forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors()); @Override - public PureModelContextData parse(String txt) + public PureModelContextData parse(String txt, boolean returnSourceInformation) { // txt = "#>{a::DB.test}#->filter(t|$t.name->startsWith('Dr'))->meta::pure::mapping::from(^meta::core::runtime::Runtime\n" + // " (\n" + @@ -52,7 +54,7 @@ public PureModelContextData parse(String txt) // " )\n" + // " )\n" + // " )"; - return PureGrammarParser.newInstance().parseModel(txt); + return PureGrammarParser.newInstance().parseModel(txt, returnSourceInformation); // // "" + // "###Runtime\n" + @@ -78,6 +80,12 @@ public PureModelContextData parse(String txt) // "function a::b::c::d():Any[*]{"+txt+"}"); } + @Override + public String render(PureModelContextData model) + { + return PureGrammarComposer.newInstance(PureGrammarComposerContext.Builder.newInstance().build()).renderPureModelContextData(model); + } + @Override public PureModel compile(PureModelContextData pureModelContextData) { @@ -87,7 +95,7 @@ public PureModel compile(PureModelContextData pureModelContextData) @Override public Root_meta_pure_executionPlan_ExecutionPlan generatePlan(PureModel pureModel, boolean debug) { - RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); + RichIterable extensions = PureCoreExtensionLoader.extensions().flatCollect(e -> e.extraPureCoreExtensions(pureModel.getExecutionSupport())); Pair res = PlanGenerator.generateExecutionPlanAsPure(pureModel.getConcreteFunctionDefinition_safe(REPL_RUN_FUNCTION_QUALIFIED_PATH), null, pureModel, PlanPlatform.JAVA, "", debug, extensions); if (debug) { diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/REPLServerHelpers.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/REPLServerHelpers.java index 799b0fd25d8..db0e9a5e083 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/REPLServerHelpers.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/REPLServerHelpers.java @@ -30,22 +30,32 @@ 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.EngineErrorType; 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.connection.Connection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.ConnectionPointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.domain.Function; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.PackageableRuntime; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.section.SectionIndex; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.DatabaseType; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.RelationalDatabaseConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Database; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Schema; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Table; 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.packageableElement.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.protocol.pure.v1.model.valueSpecification.raw.classInstance.relation.RelationStoreAccessor; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.packageableElement.PackageableElementPtr; import org.finos.legend.engine.repl.client.Client; import org.finos.legend.engine.repl.core.legend.LegendInterface; -import org.finos.legend.engine.repl.dataCube.server.model.DataCubeQuery; import org.finos.legend.engine.repl.dataCube.server.model.DataCubeQueryColumn; import org.finos.legend.engine.repl.shared.ExecutionHelper; +import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; import org.finos.legend.pure.m3.navigation.M3Paths; import org.finos.legend.pure.m3.navigation.generictype.GenericType; @@ -62,12 +72,26 @@ public class REPLServerHelpers { - public static void handleResponse(HttpExchange exchange, int responseCode, String response, REPLServerState state) + public static void handleJSONResponse(HttpExchange exchange, int responseCode, String response, REPLServerState state) + { + handleResponse(exchange, responseCode, response, state, "application/json"); + } + + public static void handleTextResponse(HttpExchange exchange, int responseCode, String response, REPLServerState state) + { + handleResponse(exchange, responseCode, response, state, "text/plain"); + } + + private static void handleResponse(HttpExchange exchange, int responseCode, String response, REPLServerState state, String contentType) { try { OutputStream os = exchange.getResponseBody(); byte[] byteResponse = response != null ? response.getBytes(StandardCharsets.UTF_8) : new byte[0]; + if (contentType != null) + { + exchange.getResponseHeaders().add("Content-Type", contentType); + } exchange.sendResponseHeaders(responseCode, byteResponse.length); os.write(byteResponse); os.close(); @@ -87,8 +111,9 @@ public static class REPLServerState public Long startTime; private PureModelContextData currentPureModelContextData; - private DataCubeQuery query; - private Map source; + private String query; + private Map queryConfiguration; + private Map querySource; public REPLServerState(Client client, ObjectMapper objectMapper, PlanExecutor planExecutor, LegendInterface legendInterface) { @@ -108,8 +133,8 @@ private void initialize(PureModelContextData pureModelContextData, Listfrom(), such as when mapping is specified Function function = (Function) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(REPL_RUN_FUNCTION_QUALIFIED_PATH)).getFirst(); - String runtime = null; - String mapping = null; + String runtimePath = null; + String mappingPath = null; Deque fns = new LinkedList<>(); ValueSpecification currentExpression = function.body.get(0); while (currentExpression instanceof AppliedFunction) @@ -121,23 +146,23 @@ private void initialize(PureModelContextData pureModelContextData, Listfrom(), only one is expected"); } - runtime = newRuntime; + runtimePath = newRuntime; } else if (fn.parameters.size() == 3) { // TODO: verify the type of the element (i.e. Mapping & Runtime) String newMapping = ((PackageableElementPtr) fn.parameters.get(1)).fullPath; String newRuntime = ((PackageableElementPtr) fn.parameters.get(2)).fullPath; - if ((mapping != null && !mapping.equals(newMapping)) || (runtime != null && !runtime.equals(newRuntime))) + if ((mappingPath != null && !mappingPath.equals(newMapping)) || (runtimePath != null && !runtimePath.equals(newRuntime))) { throw new RuntimeException("Can't launch DataCube. Source query contains multiple different ->from(), only one is expected"); } - mapping = newMapping; - runtime = newRuntime; + mappingPath = newMapping; + runtimePath = newRuntime; } } else @@ -151,29 +176,105 @@ else if (fn.parameters.size() == 3) fn.parameters.set(0, currentExpression); currentExpression = fn; } - Connection connection = null; - if (runtime != null) + + // Build the minimal PMCD needed to persist to run the query + // NOTE: the ONLY use case we want to support right now is when user uses a single DB with relation store accessor + // with a single connection in a single runtime, no mapping, no join, etc. + // Those cases would be too complex to handle and result in too big of a PMCD to persist. + boolean isLocal = false; + boolean isPersistenceSupported = false; + PureModelContextData model = null; + PackageableRuntime runtime = null; + if (runtimePath != null) + { + String _runtimePath = runtimePath; + runtime = (PackageableRuntime) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(_runtimePath)).getOnly(); + } + Database database = null; + if (currentExpression instanceof ClassInstance && ((ClassInstance) currentExpression).value instanceof RelationStoreAccessor) + { + RelationStoreAccessor accessor = (RelationStoreAccessor) ((ClassInstance) currentExpression).value; + + if (accessor.path.size() <= 1) + { + throw new EngineException("Error in the accessor definition. Please provide a table.", accessor.sourceInformation, EngineErrorType.COMPILATION); + } + String schemaName = (accessor.path.size() == 3) ? accessor.path.get(1) : "default"; + String tableName = (accessor.path.size() == 3) ? accessor.path.get(2) : accessor.path.get(1); + + // clone the database, only extract the schema and table that we need + Database _database = (Database) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(accessor.path.get(0))).getOnly(); + Schema _schema = ListIterate.select(_database.schemas, s -> s.name.equals(schemaName)).getOnly(); + Table _table = ListIterate.select(_schema.tables, t -> t.name.equals(tableName)).getOnly(); + database = new Database(); + database.name = _database.name; + database._package = _database._package; + Schema schema = new Schema(); + schema.name = _schema.name; + Table table = new Table(); + table.name = _table.name; + table.columns = _table.columns; + schema.tables = Lists.mutable.with(table); + database.schemas = Lists.mutable.with(schema); + } + PackageableConnection connection = null; + if (runtimePath != null) { - String _runtime = runtime; - PackageableRuntime rt = (PackageableRuntime) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(_runtime)).getFirst(); + String _runtime = runtimePath; + PackageableRuntime rt = (PackageableRuntime) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(_runtime)).getOnly(); if (rt != null && rt.runtimeValue.connections.size() == 1 && rt.runtimeValue.connections.get(0).storeConnections.size() == 1) { - connection = rt.runtimeValue.connections.get(0).storeConnections.get(0).connection; + Connection conn = rt.runtimeValue.connections.get(0).storeConnections.get(0).connection; + if (conn instanceof ConnectionPointer) + { + PackageableConnection _connection = (PackageableConnection) ListIterate.select(pureModelContextData.getElements(), e -> e.getPath().equals(((ConnectionPointer) conn).connection)).getOnly(); + if (_connection.connectionValue instanceof RelationalDatabaseConnection) + { + connection = _connection; + isLocal = DatabaseType.DuckDB.equals(((RelationalDatabaseConnection) _connection.connectionValue).databaseType); + } + } + } + } + // The only case we want to support persisting the model is when we have a single DB and connection + if (database != null && connection != null && runtime != null && mappingPath == null) + { + model = PureModelContextData.newBuilder() + .withSerializer(pureModelContextData.serializer) + .withElements(Lists.mutable.with(database, connection, runtime)) + .build(); + try + { + this.legendInterface.compile(model); + model = this.legendInterface.parse(this.legendInterface.render(model), false); + model = PureModelContextData.newBuilder().withSerializer(model.serializer).withElements(ListIterate.reject(model.getElements(), el -> el instanceof SectionIndex)).build(); + isPersistenceSupported = true; + } + catch (Exception e) + { + this.client.printDebug("Error while compiling persistent model: " + e.getMessage()); + // something was wrong with the assembled model, reset it + model = null; } } + Map source = Maps.mutable.empty(); source.put("_type", "repl"); - source.put("timestamp", this.startTime); source.put("query", currentExpression.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build())); - source.put("runtime", runtime); - source.put("mapping", mapping); + source.put("runtime", runtimePath); + source.put("model", model); + // some extra analytics metadata which would not be part of the persistent query source.put("columns", columns); - source.put("connection", connection); - this.source = source; + source.put("mapping", mappingPath); + source.put("timestamp", this.startTime); + source.put("isLocal", isLocal); + source.put("isPersistenceSupported", isPersistenceSupported); + this.querySource = source; + + // -------------------- CONFIGURATION -------------------- + this.queryConfiguration = null; // initially, the config is not initialized // -------------------- QUERY -------------------- - DataCubeQuery query = new DataCubeQuery(); - query.configuration = null; // initially, the config is not initialized // NOTE: for this, the initial query is going to be a select all AppliedFunction partialFn = new AppliedFunction(); partialFn.function = "select"; @@ -185,8 +286,7 @@ else if (fn.parameters.size() == 3) return colSpec; }); partialFn.parameters = Lists.mutable.with(new ClassInstance("colSpecArray", colSpecArray, null)); - query.query = partialFn.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); - this.query = query; + this.query = partialFn.accept(DEPRECATED_PureGrammarComposerCore.Builder.newInstance().build()); } public void initializeFromTable(PureModelContextData pureModelContextData) @@ -253,14 +353,19 @@ public PureModelContextData getCurrentPureModelContextData() return data; } - public DataCubeQuery getQuery() + public String getQuery() { return this.query; } - public Map getSource() + public Map getQueryConfiguration() + { + return this.queryConfiguration; + } + + public Map getQuerySource() { - return this.source; + return this.querySource; } } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeInfrastructure.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeInfrastructure.java index 44915cda55d..ccdef4a7c32 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeInfrastructure.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeInfrastructure.java @@ -19,6 +19,8 @@ import org.finos.legend.engine.repl.dataCube.server.REPLServer; import org.finos.legend.engine.repl.dataCube.server.model.DataCubeInfrastructureInfo; import org.finos.legend.engine.repl.dataCube.shared.DataCubeSampleData; +import org.finos.legend.engine.shared.core.identity.Identity; +import org.finos.legend.engine.shared.core.kerberos.SubjectTools; import java.io.InputStream; import java.io.OutputStream; @@ -39,14 +41,28 @@ public HttpHandler getHandler(REPLServerState state) try { DataCubeInfrastructureInfo info = new DataCubeInfrastructureInfo(); - info.gridClientLicense = System.getProperty("legend.repl.dataCube.gridLicenseKey") == null ? "" : System.getProperty("legend.repl.dataCube.gridLicenseKey"); + try + { + Identity identity = Identity.makeIdentity(SubjectTools.getLocalSubject()); + if (identity != null) + { + info.currentUser = identity.getName(); + } + } + catch (Exception ignored) + { + // do nothing + } + info.gridClientLicense = System.getProperty("legend.repl.dataCube.gridLicenseKey"); + info.queryServerBaseUrl = System.getProperty("legend.repl.dataCube.queryServerBaseUrl"); + info.hostedApplicationBaseUrl = System.getProperty("legend.repl.dataCube.hostedApplicationBaseUrl"); info.simpleSampleDataTableName = DataCubeSampleData.TREE.tableName; info.complexSampleDataTableName = DataCubeSampleData.SPORT.tableName; - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(info), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(info), state); } catch (Exception e) { - handleResponse(exchange, 500, e.getMessage(), state); + handleTextResponse(exchange, 500, e.getMessage(), state); } } }; @@ -93,7 +109,7 @@ else if (resourcePath.endsWith(".css")) } catch (Exception e) { - handleResponse(exchange, 500, e.getMessage(), state); + handleTextResponse(exchange, 500, e.getMessage(), state); } } }; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryBuilder.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryBuilder.java index 9debbe9347a..8766944751d 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryBuilder.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryBuilder.java @@ -58,11 +58,18 @@ public HttpHandler getHandler(REPLServerState state) 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); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); } catch (Exception e) { - handleResponse(exchange, 400, e instanceof EngineException ? state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)) : e.getMessage(), state); + if (e instanceof EngineException) + { + handleJSONResponse(exchange, 400, state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)), state); + } + else + { + handleTextResponse(exchange, 500, e.getMessage(), state); + } } } }; @@ -84,11 +91,11 @@ public HttpHandler getHandler(REPLServerState state) BufferedReader bufferReader = new BufferedReader(inputStreamReader); String requestBody = bufferReader.lines().collect(Collectors.joining()); DataCubeGetValueSpecificationCodeInput input = state.objectMapper.readValue(requestBody, DataCubeGetValueSpecificationCodeInput.class); - handleResponse(exchange, 200, DataCubeHelpers.getQueryCode(input.value, input.pretty), state); + handleTextResponse(exchange, 200, DataCubeHelpers.getQueryCode(input.value, input.pretty), state); } catch (Exception e) { - handleResponse(exchange, 400, e.getMessage(), state); + handleTextResponse(exchange, 400, e.getMessage(), state); } } }; @@ -122,11 +129,11 @@ public HttpHandler getHandler(REPLServerState state) result.queries.put(key, null); } }); - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); } catch (Exception e) { - handleResponse(exchange, 400, e.getMessage(), state); + handleTextResponse(exchange, 400, e.getMessage(), state); } } }; @@ -149,11 +156,11 @@ public HttpHandler getHandler(REPLServerState state) String requestBody = bufferReader.lines().collect(Collectors.joining()); DataCubeQueryTypeaheadInput input = state.objectMapper.readValue(requestBody, DataCubeQueryTypeaheadInput.class); CompletionResult result = DataCubeHelpers.getCodeTypeahead(input.code, input.baseQuery, input.isolated ? null : state.getCurrentPureModelContextData(), state.client.getCompleterExtensions(), state.legendInterface); - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result.getCompletion()), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(result.getCompletion()), state); } catch (Exception e) { - handleResponse(exchange, 500, e.getMessage(), state); + handleTextResponse(exchange, 500, e.getMessage(), state); } } }; @@ -175,11 +182,18 @@ public HttpHandler getHandler(REPLServerState state) BufferedReader bufferReader = new BufferedReader(inputStreamReader); String requestBody = bufferReader.lines().collect(Collectors.joining()); DataCubeGetQueryRelationReturnTypeInput input = state.objectMapper.readValue(requestBody, DataCubeGetQueryRelationReturnTypeInput.class); - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(DataCubeHelpers.getRelationReturnType(state.legendInterface, input.query, input.isolated ? null : state.getCurrentPureModelContextData())), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(DataCubeHelpers.getRelationReturnType(state.legendInterface, input.query, input.isolated ? null : state.getCurrentPureModelContextData())), state); } catch (Exception e) { - handleResponse(exchange, 500, e instanceof EngineException ? state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)) : e.getMessage(), state); + if (e instanceof EngineException) + { + handleJSONResponse(exchange, 500, state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)), state); + } + else + { + handleTextResponse(exchange, 500, e.getMessage(), state); + } } } }; @@ -224,19 +238,26 @@ public HttpHandler getHandler(REPLServerState state) { PureModelContextData data = PureGrammarParser.newInstance().parseModel(graphCode); RelationType relationType = DataCubeHelpers.getRelationReturnType(state.legendInterface, data); - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(relationType), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(relationType), state); } catch (EngineException e) { SourceInformation sourceInformation = e.getSourceInformation(); sourceInformation.startLine -= lineOffset; sourceInformation.endLine -= lineOffset; - handleResponse(exchange, 400, state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError(new EngineException(e.getMessage(), sourceInformation, e.getErrorType()))), state); + handleJSONResponse(exchange, 400, state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError(new EngineException(e.getMessage(), sourceInformation, e.getErrorType()))), state); } } catch (Exception e) { - handleResponse(exchange, 500, e instanceof EngineException ? state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)) : e.getMessage(), state); + if (e instanceof EngineException) + { + handleJSONResponse(exchange, 500, state.objectMapper.writeValueAsString(new DataCubeQueryBuilderError((EngineException) e)), state); + } + else + { + handleTextResponse(exchange, 500, e.getMessage(), state); + } } } }; @@ -254,14 +275,16 @@ public HttpHandler getHandler(REPLServerState state) { try { - DataCubeQuery query = state.getQuery(); - Map source = state.getSource(); + String query = state.getQuery(); + Map configuration = state.getQueryConfiguration(); + Map source = state.getQuerySource(); if (query != null) { DataCubeGetBaseQueryResult result = new DataCubeGetBaseQueryResult(); result.query = query; + result.configuration = configuration; result.source = source; - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); } else { @@ -270,7 +293,7 @@ public HttpHandler getHandler(REPLServerState state) } catch (Exception e) { - handleResponse(exchange, 500, e.getMessage(), state); + handleTextResponse(exchange, 500, e.getMessage(), state); } } }; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryExecutor.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryExecutor.java index 7609dbad1dd..cc8e94b03f4 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryExecutor.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/handler/DataCubeQueryExecutor.java @@ -50,11 +50,11 @@ public HttpHandler getHandler(REPLServerState state) Lambda lambda = input.query; PureModelContextData data = DataCubeHelpers.injectNewFunction(state.getCurrentPureModelContextData(), lambda).getOne(); DataCubeExecutionResult result = executeQuery(state.client, state.legendInterface, state.planExecutor, data, debug); - handleResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); + handleJSONResponse(exchange, 200, state.objectMapper.writeValueAsString(result), state); } catch (Exception e) { - handleResponse(exchange, 500, e.getMessage(), state); + handleTextResponse(exchange, 500, e.getMessage(), state); } } }; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeGetBaseQueryResult.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeGetBaseQueryResult.java index b73ab865282..666ada393df 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeGetBaseQueryResult.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeGetBaseQueryResult.java @@ -18,6 +18,7 @@ public class DataCubeGetBaseQueryResult { - public DataCubeQuery query; + public String query; + public Map configuration; public Map source; } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeInfrastructureInfo.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeInfrastructureInfo.java index c61b90614d2..b90cc257973 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeInfrastructureInfo.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeInfrastructureInfo.java @@ -16,7 +16,10 @@ public class DataCubeInfrastructureInfo { + public String currentUser; public String gridClientLicense; + public String queryServerBaseUrl; + public String hostedApplicationBaseUrl; public String simpleSampleDataTableName; public String complexSampleDataTableName; } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeQuery.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeQuery.java deleted file mode 100644 index ec6ba099076..00000000000 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-data-cube/src/main/java/org/finos/legend/engine/repl/dataCube/server/model/DataCubeQuery.java +++ /dev/null @@ -1,23 +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.dataCube.server.model; - -import java.util.Map; - -public class DataCubeQuery -{ - public String query; - public Map configuration; -} 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/grammar/from/RelationalParseTreeWalker.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/grammar/from/RelationalParseTreeWalker.java index 0dc27eef52c..2bcd302482d 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/grammar/from/RelationalParseTreeWalker.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/grammar/from/RelationalParseTreeWalker.java @@ -203,7 +203,7 @@ private Column visitColumnDefinition(RelationalParserGrammar.ColumnDefinitionCon { Column column = new Column(); column.sourceInformation = this.walkerSourceInformation.getSourceInformation(ctx); - column.name = ctx.relationalIdentifier().getText(); + column.name = ctx.relationalIdentifier().getText(); boolean nullable = true; if (ctx.PRIMARY_KEY() != null) { 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/grammar/to/HelperRelationalGrammarComposer.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/grammar/to/HelperRelationalGrammarComposer.java index a5c0f6a5282..7b7b9c91a58 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/grammar/to/HelperRelationalGrammarComposer.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/grammar/to/HelperRelationalGrammarComposer.java @@ -18,15 +18,7 @@ import org.eclipse.collections.impl.utility.LazyIterate; import org.eclipse.collections.impl.utility.ListIterate; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.PropertyMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.ApiTokenAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.AuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.DefaultH2AuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.DelegatedKerberosAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.GCPApplicationDefaultCredentialsAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.GCPWorkloadIdentityFederationAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.MiddleTierUserNamePasswordAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.TestDatabaseAuthenticationStrategy; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.UserNamePasswordAuthenticationStrategy; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.authentication.*; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.postprocessor.Mapper; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.postprocessor.MapperPostProcessor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.postprocessor.SchemaNameMapper; @@ -35,51 +27,22 @@ import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.specification.EmbeddedH2DatasourceSpecification; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.specification.LocalH2DatasourceSpecification; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.specification.StaticDatasourceSpecification; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.EmbeddedRelationalPropertyMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.FilterMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.InlineEmbeddedPropertyMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.OtherwiseEmbeddedRelationalPropertyMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.RelationalPropertyMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Column; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.ColumnMapping; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Schema; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.Table; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.TabularFunction; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.View; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.BigInt; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Binary; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Bit; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Char; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Date; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Decimal; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Json; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Numeric; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Other; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Real; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.SemiStructured; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.SmallInt; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Timestamp; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.TinyInt; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.VarChar; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.Varbinary; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.*; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.*; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.datatype.*; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.milestoning.BusinessMilestoning; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.milestoning.BusinessSnapshotMilestoning; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.milestoning.Milestoning; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.milestoning.ProcessingMilestoning; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.DynaFunc; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.ElementWithJoins; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.JoinPointer; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.Literal; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.LiteralList; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.RelationalOperationElement; -import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.TableAliasColumn; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.model.operation.*; import org.finos.legend.engine.shared.core.api.grammar.RenderStyle; +import java.lang.Double; +import java.lang.Float; +import java.lang.Integer; import java.util.List; -import static org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerUtility.convertString; -import static org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerUtility.getTabString; -import static org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerUtility.unsupported; +import static org.finos.legend.engine.language.pure.grammar.to.PureGrammarComposerUtility.*; public class HelperRelationalGrammarComposer { @@ -112,7 +75,7 @@ else if (op instanceof Literal) public static String renderRelationalOperationElement(RelationalOperationElement op, RelationalGrammarComposerContext context) { - return renderRelationalOperationElement(op, context, false, 0); + return renderRelationalOperationElement(op, context, false, 0); } private static String renderDynaFunc(DynaFunc dynaFunc, RelationalGrammarComposerContext context, boolean nested, int numTabs) @@ -152,7 +115,7 @@ private static String renderDynaFunc(DynaFunc dynaFunc, RelationalGrammarCompose } else { - return LazyIterate.collect(dynaFunc.parameters, param -> renderRelationalOperationElement(param, context)).makeString(" " + PureGrammarComposerUtility.convertIdentifier(dynaFunc.funcName) + " "); + return LazyIterate.collect(dynaFunc.parameters, param -> renderRelationalOperationElement(param, context)).makeString(" " + PureGrammarComposerUtility.convertIdentifier(dynaFunc.funcName) + " "); } } case "isNull": @@ -263,7 +226,7 @@ private static String renderElementWithJoins(ElementWithJoins elementWithJoins, if (elementWithJoins.joins.size() > 1) { builder.append(" > "); - String joins = LazyIterate.collect((elementWithJoins.joins.subList(1,elementWithJoins.joins.size())), HelperRelationalGrammarComposer::renderJoinPointer).makeString(" > "); + String joins = LazyIterate.collect((elementWithJoins.joins.subList(1, elementWithJoins.joins.size())), HelperRelationalGrammarComposer::renderJoinPointer).makeString(" > "); builder.append(joins); } } @@ -372,7 +335,11 @@ public static String renderDatabaseTabularFunction(TabularFunction tabularFuncti private static String renderDatabaseTableColumn(Column column, List primaryKeys, int baseIndentation) { StringBuilder builder = new StringBuilder(); - builder.append(getTabString(baseIndentation)).append(column.name).append(" "); + builder.append(getTabString(baseIndentation)).append( + // NOTE: for backward compatibility, we have to keep the current behavior of storing quotes as part of column name if present + // so the composer need to compensate respectively + column.name.startsWith("\"") && column.name.endsWith("\"") ? column.name : PureGrammarComposerUtility.convertIdentifier(column.name, true) + ).append(" "); if (column.type instanceof Char) { builder.append("CHAR(").append(((Char) column.type).size).append(")"); @@ -723,7 +690,7 @@ else if (_auth instanceof DelegatedKerberosAuthenticationStrategy) } else if (_auth instanceof MiddleTierUserNamePasswordAuthenticationStrategy) { - MiddleTierUserNamePasswordAuthenticationStrategy auth = (MiddleTierUserNamePasswordAuthenticationStrategy)_auth; + MiddleTierUserNamePasswordAuthenticationStrategy auth = (MiddleTierUserNamePasswordAuthenticationStrategy) _auth; int baseIndentation = 1; return "MiddleTierUserNamePassword" + (auth.vaultReference != null diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarComposer.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarComposer.java index 227aa5ccda1..9d506a13846 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarComposer.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarComposer.java @@ -149,4 +149,30 @@ public void TestMappingWithLeftOuterJoin() throws Exception Assert.assertEquals(expected, formatted); } + + @Test + public void columnNameWithQuotes() throws Exception + { + PureModelContextData context = objectMapper.readValue(Objects.requireNonNull(getClass().getClassLoader().getResourceAsStream("columnNameWithQuotes.json")), PureModelContextData.class); + PureGrammarComposer grammarTransformer = PureGrammarComposer.newInstance(PureGrammarComposerContext.Builder.newInstance().build()); + String formatted = grammarTransformer.renderPureModelContextData(context); + + // Test checks that we do not output schema name before {target} - the grammar round trip tests do not exercise this code path as the graph + // produced for the roundtrip always sets the schema name in the table alias to default. + // This is testing out a graph as would be produced by tools such as Studio and checking that syntactically valid Pure is produced for self-joins + String expected = + "###Relational\n" + + "Database local::DuckDuckDatabase\n" + + "(\n" + + " Table sport\n" + + " (\n" + + " \"Athlete(s)\" VARCHAR(0),\n" + + " \"Age/Annee\" BIGINT,\n" + + " \"Country of Origin\" VARCHAR(0),\n" + + " \"Final Event\" VARCHAR(0)\n" + + " )\n" + + ")\n"; + + Assert.assertEquals(expected, formatted); + } } diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarRoundtrip.java b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarRoundtrip.java index 1d4a6d2681c..8ef14cff9ce 100644 --- a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarRoundtrip.java +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/java/org/finos/legend/engine/language/pure/grammar/test/TestRelationalGrammarRoundtrip.java @@ -343,6 +343,12 @@ public void testRelationalDatabase() " (\n" + " col1 CHAR(32)\n" + " )\n" + + " Table table3\n" + + " (\n" + + // handle quoted column name + " \"col1\" CHAR(32),\n" + + " \"this is a col\" VARCHAR(32)\n" + + " )\n" + "\n" + " View view1\n" + " (\n" + diff --git a/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/resources/columnNameWithQuotes.json b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/resources/columnNameWithQuotes.json new file mode 100644 index 00000000000..51659994419 --- /dev/null +++ b/legend-engine-xts-relationalStore/legend-engine-xt-relationalStore-generation/legend-engine-xt-relationalStore-grammar/src/test/resources/columnNameWithQuotes.json @@ -0,0 +1,60 @@ +{ + "_type": "data", + "elements": [ + { + "_type": "relational", + "filters": [], + "includedStores": [], + "joins": [], + "name": "DuckDuckDatabase", + "package": "local", + "schemas": [ + { + "name": "default", + "tables": [ + { + "columns": [ + { + "name": "Athlete(s)", + "nullable": true, + "type": { + "_type": "Varchar", + "size": 0 + } + }, + { + "name": "Age/Annee", + "nullable": true, + "type": { + "_type": "BigInt" + } + }, + { + "name": "Country of Origin", + "nullable": true, + "type": { + "_type": "Varchar", + "size": 0 + } + }, + { + "name": "\"Final Event\"", + "nullable": true, + "type": { + "_type": "Varchar", + "size": 0 + } + } + ], + "milestoning": [], + "name": "sport", + "primaryKey": [] + } + ], + "views": [] + } + ], + "stereotypes": [] + } + ] +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2a7ac37e7e6..14b918301e6 100644 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,7 @@ 5.28.0 0.25.7 - 12.72.0 + 12.86.0 legend-engine