From cee0208f7196d9469bba23f9b6de2423a3341e73 Mon Sep 17 00:00:00 2001 From: Jake Kim <60619826+jake-kim1@users.noreply.github.com> Date: Fri, 23 Feb 2024 16:16:47 -0500 Subject: [PATCH] Implement runtime, connection, and service navigations (#146) * Implement runtime, connection, and service navigations * Add required files --------- Co-authored-by: Bey-Hernandez, Rafael E [Engineering] --- .gitlab-ci.yml | 12 + .gs-project.yml | 1 + .../AbstractLSPGrammarExtension.java | 4 +- .../ConnectionLSPGrammarExtension.java | 69 +++ .../ConnectionLSPGrammarProvider.java | 28 + .../RelationalLSPGrammarExtension.java | 21 +- .../runtime/RuntimeLSPGrammarExtension.java | 111 ++++ .../service/ServiceLSPGrammarExtension.java | 68 +++ .../AbstractLSPGrammarExtensionTest.java | 12 +- .../TestConnectionLSPGrammarExtension.java | 526 ++++++++++++++++++ .../TestRelationalLSPGrammarExtension.java | 108 ++++ .../TestServiceLSPGrammarExtension.java | 155 ++++++ 12 files changed, 1101 insertions(+), 14 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 .gs-project.yml create mode 100644 legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarProvider.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..b6aa5417 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +stages: + - clmscan + + +clm_scan: + stage: clmscan + tags: [sdlc] + image: ${CI_REGISTRY}/dx/sdlc-tools/aws-gitlab-clm-plugin/aws-gitlab-clm-plugin:current + before_script: [] + script: + - /opt/clm/request-clmscan.sh "${CI_PROJECT_DIR}" + diff --git a/.gs-project.yml b/.gs-project.yml new file mode 100644 index 00000000..522eb8f2 --- /dev/null +++ b/.gs-project.yml @@ -0,0 +1 @@ +productGuid: "product::630429" diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtension.java index 79173186..99375a2f 100644 --- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtension.java @@ -691,7 +691,7 @@ public Optional getLegendReference(SectionState section, TextPo Optional elementAtPosition = this.getElementAtPosition(section, textPosition); return elementAtPosition.flatMap(pe -> - this.geReferenceResolversResult(section, pe) + this.getReferenceResolversResult(section, pe) .stream() .filter(ref -> ref.getLocation().getTextInterval().includes(textPosition)) .findAny() @@ -719,7 +719,7 @@ public Optional getLegendReference(SectionState section, TextPo })); } - private Collection geReferenceResolversResult(SectionState section, PackageableElement packageableElement) + private Collection getReferenceResolversResult(SectionState section, PackageableElement packageableElement) { return section.getProperty(REFERENCE_RESULT + ":" + packageableElement.getPath(), () -> getReferenceResolvers(section, packageableElement)); } diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarExtension.java index 596425ab..4a65daf1 100644 --- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarExtension.java @@ -17,17 +17,22 @@ package org.finos.legend.engine.ide.lsp.extension.connection; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.factory.Sets; import org.eclipse.collections.api.list.ListIterable; import org.eclipse.collections.api.set.MutableSet; import org.finos.legend.engine.ide.lsp.extension.AbstractLegacyParserLSPGrammarExtension; +import org.finos.legend.engine.ide.lsp.extension.LegendReferenceResolver; import org.finos.legend.engine.ide.lsp.extension.completion.LegendCompletion; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult.Type; +import org.finos.legend.engine.ide.lsp.extension.state.GlobalState; import org.finos.legend.engine.ide.lsp.extension.state.SectionState; import org.finos.legend.engine.ide.lsp.extension.text.TextPosition; import org.finos.legend.engine.language.pure.grammar.from.connection.ConnectionParser; @@ -35,7 +40,14 @@ import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensions; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement; +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.ConnectionVisitor; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.PackageableConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.connection.JsonModelConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.connection.ModelChainConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.connection.ModelConnection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.modelToModel.connection.XmlModelConnection; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.connection.RelationalDatabaseConnection; /** @@ -135,6 +147,63 @@ private static ListIterable findKeywords() return Lists.immutable.withAll(keywords); } + @Override + protected Collection getReferenceResolvers(SectionState section, PackageableElement packageableElement) + { + if (!(packageableElement instanceof PackageableConnection)) + { + return List.of(); + } + + return this.getConnectionReferences(((PackageableConnection) packageableElement).connectionValue, section.getDocumentState().getGlobalState()).collect(Collectors.toList()); + } + + public Stream getConnectionReferences(Connection connection, GlobalState state) + { + Stream connectionReferences = connection.accept(new ConnectionVisitor<>() + { + @Override + public Stream visit(Connection connection) + { + return state.findGrammarExtensionThatImplements(ConnectionLSPGrammarProvider.class) + .flatMap(x -> x.getConnectionReferences(connection, state)); + } + + @Override + public Stream visit(ConnectionPointer connectionPointer) + { + return Stream.of(LegendReferenceResolver.newReferenceResolver(connectionPointer.sourceInformation, c -> c.resolveConnection(connectionPointer.connection, connectionPointer.sourceInformation))); + } + + @Override + public Stream visit(ModelConnection modelConnection) + { + return Stream.empty(); + } + + @Override + public Stream visit(JsonModelConnection jsonModelConnection) + { + return Stream.of(LegendReferenceResolver.newReferenceResolver(jsonModelConnection.classSourceInformation, c -> c.resolveClass(jsonModelConnection._class, jsonModelConnection.classSourceInformation))); + } + + @Override + public Stream visit(XmlModelConnection xmlModelConnection) + { + return Stream.of(LegendReferenceResolver.newReferenceResolver(xmlModelConnection.classSourceInformation, c -> c.resolveClass(xmlModelConnection._class, xmlModelConnection.classSourceInformation))); + } + + @Override + public Stream visit(ModelChainConnection modelChainConnection) + { + // TODO: Refactor ModelChainConnection to contain List in order to reference the mappings + return Stream.empty(); + } + }); + + return connectionReferences; + } + static class DatabaseBuilderInput { public DatabaseBuilderConfig config; diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarProvider.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarProvider.java new file mode 100644 index 00000000..6e26095a --- /dev/null +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/connection/ConnectionLSPGrammarProvider.java @@ -0,0 +1,28 @@ +/* + * 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.ide.lsp.extension.connection; + +import java.util.stream.Stream; +import org.finos.legend.engine.ide.lsp.extension.LegendReferenceResolver; +import org.finos.legend.engine.ide.lsp.extension.state.GlobalState; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.Connection; + +public interface ConnectionLSPGrammarProvider +{ + Stream getConnectionReferences(Connection connection, GlobalState state); + +} diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/relational/RelationalLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/relational/RelationalLSPGrammarExtension.java index 0f39b76b..0c40f459 100644 --- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/relational/RelationalLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/relational/RelationalLSPGrammarExtension.java @@ -27,6 +27,7 @@ import org.finos.legend.engine.ide.lsp.extension.LegendReferenceResolver; import org.finos.legend.engine.ide.lsp.extension.SourceInformationUtil; import org.finos.legend.engine.ide.lsp.extension.completion.LegendCompletion; +import org.finos.legend.engine.ide.lsp.extension.connection.ConnectionLSPGrammarProvider; import org.finos.legend.engine.ide.lsp.extension.declaration.LegendDeclaration; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult; import org.finos.legend.engine.ide.lsp.extension.mapping.MappingLSPGrammarExtension; @@ -39,7 +40,9 @@ import org.finos.legend.engine.language.pure.grammar.from.RelationalGrammarParserExtension; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.Connection; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.mapping.ClassMapping; +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.mapping.RelationalPropertyMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.RootRelationalClassMapping; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.store.relational.mapping.TablePtr; @@ -64,7 +67,7 @@ /** * Extension for the Relational grammar. */ -public class RelationalLSPGrammarExtension extends AbstractSectionParserLSPGrammarExtension implements MappingLSPGrammarProvider +public class RelationalLSPGrammarExtension extends AbstractSectionParserLSPGrammarExtension implements MappingLSPGrammarProvider, ConnectionLSPGrammarProvider { private static final Logger LOGGER = LoggerFactory.getLogger(RelationalLSPGrammarExtension.class); @@ -449,4 +452,20 @@ else if (element instanceof DynaFunc) return Stream.empty(); } + + @Override + public Stream getConnectionReferences(Connection connection, GlobalState state) + { + if (connection instanceof RelationalDatabaseConnection) + { + return Stream.of(toReference((RelationalDatabaseConnection) connection)); + } + + return Stream.empty(); + } + + private LegendReferenceResolver toReference(RelationalDatabaseConnection relationalDatabaseConnection) + { + return LegendReferenceResolver.newReferenceResolver(relationalDatabaseConnection.elementSourceInformation, s -> s.resolveStore(relationalDatabaseConnection.element, relationalDatabaseConnection.elementSourceInformation)); + } } diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/runtime/RuntimeLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/runtime/RuntimeLSPGrammarExtension.java index f585cb46..2b66cf4f 100644 --- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/runtime/RuntimeLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/runtime/RuntimeLSPGrammarExtension.java @@ -16,17 +16,32 @@ package org.finos.legend.engine.ide.lsp.extension.runtime; +import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.list.ImmutableList; import org.eclipse.collections.impl.lazy.CompositeIterable; import org.finos.legend.engine.ide.lsp.extension.AbstractLegacyParserLSPGrammarExtension; +import org.finos.legend.engine.ide.lsp.extension.LegendReferenceResolver; import org.finos.legend.engine.ide.lsp.extension.completion.LegendCompletion; +import org.finos.legend.engine.ide.lsp.extension.connection.ConnectionLSPGrammarExtension; +import org.finos.legend.engine.ide.lsp.extension.state.GlobalState; import org.finos.legend.engine.ide.lsp.extension.state.SectionState; import org.finos.legend.engine.ide.lsp.extension.text.TextPosition; import org.finos.legend.engine.language.pure.grammar.from.extension.PureGrammarParserExtensions; import org.finos.legend.engine.language.pure.grammar.from.runtime.RuntimeParser; +import org.finos.legend.engine.protocol.pure.v1.model.context.PackageableElementPointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.connection.Connection; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.ConnectionStores; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.EngineRuntime; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.PackageableRuntime; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.Runtime; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.RuntimePointer; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.StoreConnections; /** * Extension for the Runtime grammar. @@ -77,4 +92,100 @@ public Iterable getCompletions(SectionState section, return CompositeIterable.with(legendCompletions, this.computeCompletionsForSupportedTypes(section, location, Set.of("Runtime"))); } + + @Override + protected Collection getReferenceResolvers(SectionState section, PackageableElement packageableElement) + { + if (!(packageableElement instanceof PackageableRuntime)) + { + return List.of(); + } + + return this.getRuntimeReferences(((PackageableRuntime) packageableElement).runtimeValue, section.getDocumentState().getGlobalState()).collect(Collectors.toList()); + } + + public Stream getRuntimeReferences(Runtime runtime, GlobalState state) + { + if (runtime instanceof EngineRuntime) + { + return this.toEngineRuntimeReferences((EngineRuntime) runtime, state); + } + + if (runtime instanceof RuntimePointer) + { + return Stream.of(this.toRuntimePointerReference((RuntimePointer) runtime)); + } + + return Stream.empty(); + } + + private Stream toEngineRuntimeReferences(EngineRuntime engineRuntime, GlobalState state) + { + return Stream.concat(this.toMappingReferences(engineRuntime.mappings), + Stream.concat(this.toStoreConnectionReferences(engineRuntime.connections, state), + this.toConnectionStoreReferences(engineRuntime.connectionStores, state))); + } + + private LegendReferenceResolver toRuntimePointerReference(RuntimePointer runtimePointer) + { + return LegendReferenceResolver.newReferenceResolver( + runtimePointer.sourceInformation, + x -> x.resolveRuntime(runtimePointer.runtime, runtimePointer.sourceInformation) + ); + } + + private Stream toMappingReferences(List mappings) + { + return mappings.stream() + .map(this::toMappingReference); + } + + private LegendReferenceResolver toMappingReference(PackageableElementPointer packageableElementPointer) + { + return LegendReferenceResolver.newReferenceResolver( + packageableElementPointer.sourceInformation, + x -> x.resolveMapping(packageableElementPointer.path, packageableElementPointer.sourceInformation) + ); + } + + private Stream toStoreConnectionReferences(List storeConnections, GlobalState state) + { + return storeConnections.stream() + .flatMap(storeConnection -> + { + LegendReferenceResolver storeReference = this.toStoreReference(storeConnection.store); + Stream connectionReferences = storeConnection.storeConnections + .stream() + .flatMap(identifiedConnection -> this.toConnectionReferences(identifiedConnection.connection, state)); + return Stream.concat(Stream.of(storeReference), connectionReferences); + }); + } + + private Stream toConnectionStoreReferences(List connectionStores, GlobalState state) + { + return connectionStores.stream() + .flatMap(connectionStore -> + { + Stream connectionReferences = this.toConnectionReferences(connectionStore.connectionPointer, state); + Stream storeReferences = connectionStore.storePointers + .stream() + .map(packageableElementPointer -> this.toStoreReference(packageableElementPointer)); + return Stream.concat(connectionReferences, storeReferences); + }); + + } + + private LegendReferenceResolver toStoreReference(PackageableElementPointer packageableElementPointer) + { + return LegendReferenceResolver.newReferenceResolver( + packageableElementPointer.sourceInformation, + x -> x.resolveStore(packageableElementPointer.path, packageableElementPointer.sourceInformation) + ); + } + + private Stream toConnectionReferences(Connection connection, GlobalState state) + { + return state.findGrammarExtensionThatImplements(ConnectionLSPGrammarExtension.class) + .flatMap(x -> x.getConnectionReferences(connection, state)); + } } diff --git a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/service/ServiceLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/service/ServiceLSPGrammarExtension.java index ec29faad..3c19cc34 100644 --- a/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/service/ServiceLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/main/java/org/finos/legend/engine/ide/lsp/extension/service/ServiceLSPGrammarExtension.java @@ -24,19 +24,25 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.ServiceLoader; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.list.ImmutableList; import org.eclipse.collections.api.list.MutableList; import org.eclipse.collections.impl.lazy.CompositeIterable; import org.eclipse.collections.impl.utility.Iterate; import org.finos.legend.engine.ide.lsp.extension.AbstractSectionParserLSPGrammarExtension; +import org.finos.legend.engine.ide.lsp.extension.LegendReferenceResolver; import org.finos.legend.engine.ide.lsp.extension.completion.LegendCompletion; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult.Type; +import org.finos.legend.engine.ide.lsp.extension.runtime.RuntimeLSPGrammarExtension; +import org.finos.legend.engine.ide.lsp.extension.state.GlobalState; import org.finos.legend.engine.ide.lsp.extension.state.SectionState; import org.finos.legend.engine.ide.lsp.extension.text.TextPosition; import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; @@ -53,6 +59,10 @@ import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextPointer; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.PackageableElement; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.runtime.Runtime; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.service.Execution; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.service.PureMultiExecution; +import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.service.PureSingleExecution; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.service.Service; import org.finos.legend.engine.protocol.pure.v1.model.packageableElement.service.ServiceTestSuite; import org.finos.legend.engine.protocol.pure.v1.model.test.TestSuite; @@ -314,4 +324,62 @@ public Iterable getCompletions(SectionState section, return CompositeIterable.with(legendCompletions, (Iterable) super.getCompletions(section, location)); } + + @Override + protected Collection getReferenceResolvers(SectionState section, PackageableElement packageableElement) + { + if (!(packageableElement instanceof Service)) + { + return List.of(); + } + + // TODO: Handle references in ServiceTest_Legacy + return this.getExecutionReferences(((Service) packageableElement).execution, section.getDocumentState().getGlobalState()).collect(Collectors.toList()); + } + + private Stream getExecutionReferences(Execution execution, GlobalState state) + { + // TODO: Handle references in Lambda expressions + if (execution instanceof PureSingleExecution) + { + return this.toPureSingleExecutionReferences((PureSingleExecution) execution, state); + } + + if (execution instanceof PureMultiExecution) + { + return this.toPureMultiExecutionReferences((PureMultiExecution) execution, state); + } + + return Stream.empty(); + } + + private Stream toPureSingleExecutionReferences(PureSingleExecution pureSingleExecution, GlobalState state) + { + LegendReferenceResolver mappingReference = LegendReferenceResolver.newReferenceResolver( + pureSingleExecution.mappingSourceInformation, + x -> x.resolveMapping(pureSingleExecution.mapping, pureSingleExecution.mappingSourceInformation)); + Stream runtimeReferences = this.toRuntimeReferences(pureSingleExecution.runtime, state); + return Stream.concat(Stream.of(mappingReference), runtimeReferences); + } + + private Stream toPureMultiExecutionReferences(PureMultiExecution pureMultiExecution, GlobalState state) + { + return pureMultiExecution.executionParameters + .stream() + .flatMap(executionParameter -> + { + LegendReferenceResolver mappingReference = LegendReferenceResolver.newReferenceResolver( + executionParameter.mappingSourceInformation, + x -> x.resolveMapping(executionParameter.mapping, executionParameter.mappingSourceInformation) + ); + Stream runtimeReferences = this.toRuntimeReferences(executionParameter.runtime, state); + return Stream.concat(Stream.of(mappingReference), runtimeReferences); + }); + } + + private Stream toRuntimeReferences(Runtime runtime, GlobalState state) + { + return state.findGrammarExtensionThatImplements(RuntimeLSPGrammarExtension.class) + .flatMap(x -> x.getRuntimeReferences(runtime, state)); + } } diff --git a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtensionTest.java b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtensionTest.java index 6a534a7b..dbd48ff0 100644 --- a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtensionTest.java +++ b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/AbstractLSPGrammarExtensionTest.java @@ -295,17 +295,7 @@ public void forEachDocumentState(Consumer consumer) @Override public Collection getAvailableGrammarExtensions() { - List extensionList = new ArrayList<>(); - - this.forEachDocumentState(documentState -> - { - documentState.forEachSectionState(sectionState -> - { - extensionList.add(sectionState.getExtension()); - }); - }); - - return extensionList; + return legendLSPGrammarExtensions; } @Override diff --git a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/connection/TestConnectionLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/connection/TestConnectionLSPGrammarExtension.java index b8c1d967..a24641c4 100644 --- a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/connection/TestConnectionLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/connection/TestConnectionLSPGrammarExtension.java @@ -22,14 +22,18 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import org.apache.http.HttpStatus; +import org.eclipse.collections.api.factory.Maps; import org.eclipse.collections.api.factory.Sets; +import org.eclipse.collections.api.map.MutableMap; import org.eclipse.collections.api.set.MutableSet; import org.eclipse.collections.impl.utility.Iterate; import org.finos.legend.engine.ide.lsp.extension.AbstractLSPGrammarExtensionTest; import org.finos.legend.engine.ide.lsp.extension.declaration.LegendDeclaration; import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic; import org.finos.legend.engine.ide.lsp.extension.execution.LegendExecutionResult; +import org.finos.legend.engine.ide.lsp.extension.reference.LegendReference; import org.finos.legend.engine.ide.lsp.extension.text.TextLocation; +import org.finos.legend.engine.ide.lsp.extension.text.TextPosition; import org.finos.legend.engine.protocol.pure.v1.model.context.PureModelContextData; import org.finos.legend.engine.shared.core.ObjectMapperFactory; import org.junit.jupiter.api.Assertions; @@ -149,4 +153,526 @@ void testGenerateDBFromConnectionCommand() throws IOException httpServer.stop(0); } } + + @Test + public void testGetReferenceResolvers() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_CONNECTION_DOC_ID = "vscodelsp::test::FirmConnection"; + final String TEST_CLASS_DOC_ID = "vscodelsp::test::Firm"; + codeFiles.put("vscodelsp::test::Person", + "###Pure\n" + + "Class vscodelsp::test::Person\n" + + "{\n" + + " firstName: String[1];\n" + + " lastName : String[1];\n" + + "}"); + + codeFiles.put(TEST_CLASS_DOC_ID, + "###Pure\n" + + "Class vscodelsp::test::Firm\n" + + "{\n" + + " employees: vscodelsp::test::Person[1..*];\n" + + " legalName : String[1];\n" + + "}"); + + codeFiles.put(TEST_CONNECTION_DOC_ID, + "###Connection\n" + + "JsonModelConnection vscodelsp::test::FirmConnection\n" + + "{\n" + + " class: vscodelsp::test::Firm;\n" + + " url: 'my_url';\n" + + "}"); + + LegendReference mappedClassReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_CONNECTION_DOC_ID, 3, 11, 3, 31)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CLASS_DOC_ID, 1, 0, 5, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(2, 2), null, "Outside of mappedClassReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(3, 10), null, "Outside of mappedClassReference-able element (before class name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(3, 11), mappedClassReference, "Start of class name has been mapped, referring to class definition"); + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(3, 25), mappedClassReference, "Within the class name has been mapped, referring to class definition"); + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(3, 31), mappedClassReference, "End of class name has been mapped, referring to class definition"); + testReferenceLookup(codeFiles, TEST_CONNECTION_DOC_ID, TextPosition.newPosition(4, 3), null, "Outside of mappedClassReference-able element should yield nothing"); + } + + @Test + public void testGetConnectionReferencesStoreConnectionsWithConnectionPointer() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + final String TEST_STORE_DOC_ID_1 = "vscodelsp::test::TestDB1"; + final String TEST_STORE_DOC_ID_2 = "vscodelsp::test::TestDB2"; + final String TEST_CONNECTION_DOC_ID_1 = "vscodelsp::test::LocalH2Connection1"; + final String TEST_CONNECTION_DOC_ID_2 = "vscodelsp::test::LocalH2Connection2"; + codeFiles.put("vscodelsp::test::Employee", + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put(TEST_STORE_DOC_ID_1, + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_CONNECTION_DOC_ID_1, + "###Connection\n" + + "RelationalDatabaseConnection vscodelsp::test::LocalH2Connection1\n" + + "{\n" + + " store: vscodelsp::test::TestDB1;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}"); + + codeFiles.put(TEST_STORE_DOC_ID_2, + "###Relational\n" + + "Database vscodelsp::test::TestDB2\n" + + "(\n" + + " Table FirmTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " Type VARCHAR(200),\n" + + " Legal_name VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_CONNECTION_DOC_ID_2, + "###Connection\n" + + "RelationalDatabaseConnection vscodelsp::test::LocalH2Connection2\n" + + "{\n" + + " store: vscodelsp::test::TestDB2;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connections:\n" + + " [\n" + + " vscodelsp::test::TestDB1:\n" + + " [\n" + + " connection_1: vscodelsp::test::LocalH2Connection1\n" + + " ],\n" + + " vscodelsp::test::TestDB2:\n" + + " [\n" + + " connection_2: vscodelsp::test::LocalH2Connection2\n" + + " ]\n" + + " ];\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 5, 7, 5, 38)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(4, 2), null, "Outside of mappedMappingReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 6), null, "Outside of mappedMappingReference-able element (before mapping name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 7), mappedMappingReference, "Start of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 25), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 38), mappedMappingReference, "End of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(6, 3), null, "Outside of mappedMappingReference-able element should yield nothing"); + + LegendReference mappedStoreReference1 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 9, 7, 9, 30)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID_1, 1, 0, 10, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(9, 20), mappedStoreReference1, "Within the store name has been mapped, referring to store definition"); + + LegendReference mappedConnectionReference1 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 11, 25, 11, 59)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CONNECTION_DOC_ID_1, 1, 0, 12, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(11, 30), mappedConnectionReference1, "Within the connection name has been mapped, referring to connection definition"); + + LegendReference mappedStoreReference2 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 13, 7, 13, 30)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID_2, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(13, 20), mappedStoreReference2, "Within the store name has been mapped, referring to store definition"); + + LegendReference mappedConnectionReference2 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 15, 25, 15, 59)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CONNECTION_DOC_ID_2, 1, 0, 12, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(15, 38), mappedConnectionReference2, "Within the connection name has been mapped, referring to connection definition"); + } + + @Test + public void testGetConnectionReferencesStoreConnectionsWithJsonModelConnection() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_CLASS_DOC_ID = "vscodelsp::test::Employee"; + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + codeFiles.put(TEST_CLASS_DOC_ID, + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put("vscodelsp::test::TestDB1", + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connections:\n" + + " [\n" + + " ModelStore:\n" + + " [\n" + + " connection_1:\n" + + " #{\n" + + " JsonModelConnection\n" + + " {\n" + + " class: vscodelsp::test::Employee;\n" + + " url: 'my_url';\n" + + " }\n" + + " }#\n" + + " ]\n" + + " ];\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 5, 7, 5, 38)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(4, 2), null, "Outside of mappedMappingReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 6), null, "Outside of mappedMappingReference-able element (before mapping name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 7), mappedMappingReference, "Start of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 25), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 38), mappedMappingReference, "End of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(6, 3), null, "Outside of mappedMappingReference-able element should yield nothing"); + + LegendReference mappedClassReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 15, 26, 15, 50)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CLASS_DOC_ID, 1, 0, 6, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(15, 46), mappedClassReference, "Within the class name has been mapped, referring to class definition"); + } + + @Test + public void testGetConnectionReferencesStoreConnectionsWithXmlModelConnection() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_CLASS_DOC_ID = "vscodelsp::test::Employee"; + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + codeFiles.put(TEST_CLASS_DOC_ID, + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put("vscodelsp::test::TestDB1", + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connections:\n" + + " [\n" + + " ModelStore:\n" + + " [\n" + + " connection_1:\n" + + " #{\n" + + " XmlModelConnection\n" + + " {\n" + + " class: vscodelsp::test::Employee;\n" + + " url: 'my_url';\n" + + " }\n" + + " }#\n" + + " ]\n" + + " ];\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 5, 7, 5, 38)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(4, 2), null, "Outside of mappedMappingReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 6), null, "Outside of mappedMappingReference-able element (before mapping name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 7), mappedMappingReference, "Start of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 25), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 38), mappedMappingReference, "End of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(6, 3), null, "Outside of mappedMappingReference-able element should yield nothing"); + + LegendReference mappedClassReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 15, 26, 15, 50)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CLASS_DOC_ID, 1, 0, 6, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(15, 46), mappedClassReference, "Within the class name has been mapped, referring to class definition"); + } + + @Test + public void testGetConnectionReferencesConnectionStores() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + final String TEST_STORE_DOC_ID_1 = "vscodelsp::test::TestDB1"; + final String TEST_STORE_DOC_ID_2 = "connections::TestDB2"; + final String TEST_CONNECTION_DOC_ID = "vscodelsp::test::LocalH2Connection1"; + codeFiles.put("vscodelsp::test::Employee", + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put(TEST_STORE_DOC_ID_1, + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " include connections::TestDB2\n" + + "\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + " Join FirmPerson(PersonTable.firm_id = FirmTable.id)\n" + + ")"); + + codeFiles.put(TEST_STORE_DOC_ID_2, + "###Relational\n" + + "Database connections::TestDB2\n" + + "(\n" + + " Table FirmTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " Type VARCHAR(200),\n" + + " Legal_name VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_CONNECTION_DOC_ID, + "###Connection\n" + + "RelationalDatabaseConnection vscodelsp::test::LocalH2Connection1\n" + + "{\n" + + " store: vscodelsp::test::TestDB1;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connectionStores:\n" + + " [\n" + + " vscodelsp::test::LocalH2Connection1:\n" + + " [\n" + + " vscodelsp::test::TestDB1,\n" + + " connections::TestDB2\n" + + " ]\n" + + " ];\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 5, 7, 5, 38)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(4, 2), null, "Outside of mappedMappingReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 6), null, "Outside of mappedMappingReference-able element (before mapping name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 7), mappedMappingReference, "Start of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 25), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 38), mappedMappingReference, "End of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(6, 3), null, "Outside of mappedMappingReference-able element should yield nothing"); + + LegendReference mappedConnectionReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 9, 7, 9, 41)) + .withReferencedLocation(TextLocation.newTextSource(TEST_CONNECTION_DOC_ID, 1, 0, 12, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(9, 30), mappedConnectionReference, "Within the connection name has been mapped, referring to connection definition"); + + LegendReference mappedStoreReference1 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 11, 11, 11, 34)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID_1, 1, 0, 13, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(11, 20), mappedStoreReference1, "Within the store name has been mapped, referring to store definition"); + + LegendReference mappedStoreReference2 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 12, 11, 12, 30)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID_2, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(12, 20), mappedStoreReference2, "Within the store name has been mapped, referring to store definition"); + } } diff --git a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/relational/TestRelationalLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/relational/TestRelationalLSPGrammarExtension.java index 4637a9e7..559de4db 100644 --- a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/relational/TestRelationalLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/relational/TestRelationalLSPGrammarExtension.java @@ -376,4 +376,112 @@ public void testMappingLegendReference() testReferenceLookup(codeFiles, "vscodelsp::test::EmployeeMapping", TextPosition.newPosition(6, 21), columnReference, "Property mapped reference"); } + + @Test + public void testLegendReferenceStoreConnectionsWithRelationalDatabaseConnection() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + final String TEST_STORE_DOC_ID = "vscodelsp::test::TestDB1"; + codeFiles.put("vscodelsp::test::Employee", + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put(TEST_STORE_DOC_ID, + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connections:\n" + + " [\n" + + " vscodelsp::test::TestDB1:\n" + + " [\n" + + " connection_1:\n" + + " #{\n" + + " RelationalDatabaseConnection\n" + + " {\n" + + " store: vscodelsp::test::TestDB1;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + " }\n" + + " }#\n" + + " ]\n" + + " ];\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 5, 7, 5, 38)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(4, 2), null, "Outside of mappedMappingReference-able element should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 6), null, "Outside of mappedMappingReference-able element (before mapping name) should yield nothing"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 7), mappedMappingReference, "Start of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 25), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(5, 38), mappedMappingReference, "End of mapping name has been mapped, referring to mapping definition"); + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(6, 3), null, "Outside of mappedMappingReference-able element should yield nothing"); + + LegendReference mappedStoreReference1 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 9, 7, 9, 30)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID, 1, 0, 10, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(9, 20), mappedStoreReference1, "Within the store name has been mapped, referring to store definition"); + + LegendReference mappedStoreReference2 = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 15, 26, 15, 49)) + .withReferencedLocation(TextLocation.newTextSource(TEST_STORE_DOC_ID, 1, 0, 10, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_RUNTIME_DOC_ID, TextPosition.newPosition(15, 31), mappedStoreReference2, "Within the store name has been mapped, referring to store definition"); + } } diff --git a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/service/TestServiceLSPGrammarExtension.java b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/service/TestServiceLSPGrammarExtension.java index 2fdbcc02..ca696067 100644 --- a/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/service/TestServiceLSPGrammarExtension.java +++ b/legend-engine-ide-lsp-default-extensions/src/test/java/org/finos/legend/engine/ide/lsp/extension/service/TestServiceLSPGrammarExtension.java @@ -17,12 +17,16 @@ package org.finos.legend.engine.ide.lsp.extension.service; import java.util.Set; + +import org.eclipse.collections.api.factory.Maps; +import org.eclipse.collections.api.map.MutableMap; import org.finos.legend.engine.ide.lsp.extension.AbstractLSPGrammarExtensionTest; import org.finos.legend.engine.ide.lsp.extension.completion.LegendCompletion; import org.finos.legend.engine.ide.lsp.extension.declaration.LegendDeclaration; import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic; import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic.Kind; import org.finos.legend.engine.ide.lsp.extension.diagnostic.LegendDiagnostic.Source; +import org.finos.legend.engine.ide.lsp.extension.reference.LegendReference; import org.finos.legend.engine.ide.lsp.extension.text.TextLocation; import org.finos.legend.engine.ide.lsp.extension.text.TextPosition; import org.junit.jupiter.api.Assertions; @@ -138,4 +142,155 @@ void testAntlrExpectedTokens() Set antlrExpectedTokens = this.extension.getAntlrExpectedTokens(); Assertions.assertEquals(Set.of("Service", "ExecutionEnvironment"), antlrExpectedTokens); } + + @Test + public void testGetReferenceResolvers() + { + MutableMap codeFiles = Maps.mutable.empty(); + final String TEST_RUNTIME_DOC_ID = "vscodelsp::test::H2Runtime"; + final String TEST_MAPPING_DOC_ID = "vscodelsp::test::EmployeeMapping"; + final String TEST_STORE_DOC_ID_1 = "vscodelsp::test::TestDB1"; + final String TEST_STORE_DOC_ID_2 = "vscodelsp::test::TestDB2"; + final String TEST_CONNECTION_DOC_ID_1 = "vscodelsp::test::LocalH2Connection1"; + final String TEST_CONNECTION_DOC_ID_2 = "vscodelsp::test::LocalH2Connection2"; + final String TEST_SERVICE_DOC_ID = "vscodelsp::test::TestService"; + codeFiles.put("vscodelsp::test::Employee", + "###Pure\n" + + "Class vscodelsp::test::Employee\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put("vscodelsp::test::EmployeeSrc", + "###Pure\n" + + "Class vscodelsp::test::EmployeeSrc\n" + + "{\n" + + " foobar: Float[1];\n" + + " hireDate : Date[1];\n" + + " hireType : String[1];\n" + + "}"); + + codeFiles.put(TEST_MAPPING_DOC_ID, + "###Mapping\n" + + "Mapping vscodelsp::test::EmployeeMapping\n" + + "(\n" + + " vscodelsp::test::Employee[emp] : Pure\n" + + " {\n" + + " ~src vscodelsp::test::EmployeeSrc\n" + + " hireDate : today(),\n" + + " hireType : 'FullTime'\n" + + " }\n" + + ")"); + + codeFiles.put(TEST_STORE_DOC_ID_1, + "###Relational\n" + + "Database vscodelsp::test::TestDB1\n" + + "(\n" + + " Table PersonTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " firm_id INTEGER,\n" + + " firstName VARCHAR(200),\n" + + " lastName VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_CONNECTION_DOC_ID_1, + "###Connection\n" + + "RelationalDatabaseConnection vscodelsp::test::LocalH2Connection1\n" + + "{\n" + + " store: vscodelsp::test::TestDB1;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}"); + + codeFiles.put(TEST_STORE_DOC_ID_2, + "###Relational\n" + + "Database vscodelsp::test::TestDB2\n" + + "(\n" + + " Table FirmTable\n" + + " (\n" + + " id INTEGER PRIMARY KEY,\n" + + " Type VARCHAR(200),\n" + + " Legal_name VARCHAR(200)\n" + + " )\n" + + ")"); + + codeFiles.put(TEST_CONNECTION_DOC_ID_2, + "###Connection\n" + + "RelationalDatabaseConnection vscodelsp::test::LocalH2Connection2\n" + + "{\n" + + " store: vscodelsp::test::TestDB2;\n" + + " type: H2;\n" + + " specification: LocalH2\n" + + " {\n" + + " testDataSetupSqls: [\n" + + " 'Drop table if exists FirmTable;\\nDrop table if exists PersonTable;\\nCreate Table FirmTable(id INT, Type VARCHAR(200), Legal_Name VARCHAR(200));\\nCreate Table PersonTable(id INT, firm_id INT, lastName VARCHAR(200), firstName VARCHAR(200));\\nInsert into FirmTable (id, Type, Legal_Name) values (1,\\'LLC\\',\\'FirmA\\');\\nInsert into FirmTable (id, Type, Legal_Name) values (2,\\'CORP\\',\\'Apple\\');\\nInsert into PersonTable (id, firm_id, lastName, firstName) values (1, 1, \\'John\\', \\'Doe\\');\\n\\n\\n'\n" + + " ];\n" + + " };\n" + + " auth: DefaultH2;\n" + + "}"); + + codeFiles.put(TEST_RUNTIME_DOC_ID, + "###Runtime\n" + + "Runtime vscodelsp::test::H2Runtime\n" + + "{\n" + + " mappings:\n" + + " [\n" + + " vscodelsp::test::EmployeeMapping\n" + + " ];\n" + + " connections:\n" + + " [\n" + + " vscodelsp::test::TestDB1:\n" + + " [\n" + + " connection_1: vscodelsp::test::LocalH2Connection1\n" + + " ],\n" + + " vscodelsp::test::TestDB2:\n" + + " [\n" + + " connection_2: vscodelsp::test::LocalH2Connection2\n" + + " ]\n" + + " ];\n" + + "}"); + + codeFiles.put(TEST_SERVICE_DOC_ID, + "###Service\n" + + "Service vscodelsp::test::TestService\n" + + "{\n" + + " pattern : 'test';\n" + + " documentation : 'service for testing';\n" + + " execution : Single\n" + + " {\n" + + " query : src:vscodelsp::test::Employee[1] | $src.hireType;\n" + + " mapping : vscodelsp::test::EmployeeMapping;\n" + + " runtime : vscodelsp::test::H2Runtime;\n" + + " }\n" + + " test : Single" + + " {\n" + + " data : '';\n" + + " asserts : [];\n" + + " }\n" + + "}"); + + LegendReference mappedMappingReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_SERVICE_DOC_ID, 8, 18, 8, 49)) + .withReferencedLocation(TextLocation.newTextSource(TEST_MAPPING_DOC_ID, 1, 0, 9, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_SERVICE_DOC_ID, TextPosition.newPosition(8, 21), mappedMappingReference, "Within the mapping name has been mapped, referring to mapping definition"); + + LegendReference mappedRuntimeReference = LegendReference.builder() + .withLocation(TextLocation.newTextSource(TEST_SERVICE_DOC_ID, 9, 18, 9, 43)) + .withReferencedLocation(TextLocation.newTextSource(TEST_RUNTIME_DOC_ID, 1, 0, 18, 0)) + .build(); + + testReferenceLookup(codeFiles, TEST_SERVICE_DOC_ID, TextPosition.newPosition(9, 41), mappedRuntimeReference, "Within the runtime name has been mapped, referring to runtime definition"); + } }