diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/pom.xml b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/pom.xml index b8243a79b2c..e4ac07de680 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/pom.xml +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/pom.xml @@ -28,42 +28,32 @@ - - org.jline - jline - - - - - - org.finos.legend.pure - legend-pure-m4 - org.finos.legend.pure legend-pure-m3-core + + org.finos.legend.engine - legend-engine-protocol + legend-engine-language-pure-grammar org.finos.legend.engine - legend-engine-protocol-pure + legend-engine-protocol org.finos.legend.engine - legend-engine-language-pure-grammar + legend-engine-protocol-pure + + + org.finos.legend.engine legend-engine-language-pure-compiler - - org.finos.legend.engine - legend-engine-shared-extensions - - + @@ -96,25 +86,12 @@ - - - org.finos.legend.pure - legend-pure-m2-dsl-store-pure - - - org.finos.legend.pure - legend-pure-m2-store-relational-pure - - - - - + org.finos.legend.engine - legend-engine-xt-relationalStore-grammar - Runtime + legend-engine-shared-extensions - + org.eclipse.collections @@ -125,6 +102,11 @@ eclipse-collections-api + + org.jline + jline + + com.fasterxml.jackson.core jackson-databind @@ -140,7 +122,6 @@ junit - org.finos.legend.engine diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/Completer.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/Completer.java index 94c866f5be0..2a7b3796bbc 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/Completer.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/Completer.java @@ -35,18 +35,15 @@ import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.*; 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.repl.autocomplete.handlers.*; import org.finos.legend.engine.repl.autocomplete.parser.ParserFixer; import org.finos.legend.engine.shared.core.operational.errorManagement.EngineException; -import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.PackageableElement; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.function.FunctionAccessor; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.multiplicity.Multiplicity; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.relation.RelationType; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.Enumeration; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.Type; import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.type.generics.GenericType; -import org.finos.legend.pure.m3.coreinstance.meta.pure.store.Store; import org.finos.legend.pure.m3.navigation.M3Paths; import org.finos.legend.pure.m4.coreinstance.CoreInstance; @@ -60,8 +57,16 @@ public class Completer private final int lineOffset; private final MutableMap handlers; + private MutableList extensions; + public Completer(String buildCodeContext) { + this(buildCodeContext, Lists.mutable.empty()); + } + + public Completer(String buildCodeContext, MutableList extensions) + { + this.extensions = extensions; this.buildCodeContext = buildCodeContext; this.header = buildCodeContext + @@ -180,30 +185,10 @@ else if (topExpression instanceof AppliedProperty) private CompletionResult processClassInstance(ClassInstance topExpression, PureModel pureModel) { Object islandExpr = topExpression.value; - if (islandExpr instanceof RelationStoreAccessor) + CompletionResult result = this.extensions.collect(x -> x.extraClassInstanceProcessor(islandExpr, pureModel)).getFirst(); + if (result != null) { - MutableList path = Lists.mutable.withAll(((RelationStoreAccessor) islandExpr).path); - String writtenPath = path.makeString("::").replace(ParserFixer.magicToken, ""); - MutableList elements = pureModel.getAllStores().select(c -> nameMatch(c, writtenPath)).toList(); - if (elements.size() == 1 && - writtenPath.startsWith(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(elements.get(0))) && - !writtenPath.equals(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(elements.get(0))) - ) - { - org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.Database db = (org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.Database) elements.get(0); - String writtenTableName = writtenPath.replace(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(db), "").replace("::", ""); - MutableList tables = db._schemas().isEmpty() ? Lists.mutable.empty() : db._schemas().getFirst()._tables().toList(); - MutableList foundTables = tables.select(c -> c._name().startsWith(writtenTableName)); - if ((foundTables.size() == 1 && foundTables.get(0)._name().equals(path.getLast()))) - { - return new CompletionResult(Lists.mutable.empty()); - } - else - { - return new CompletionResult(foundTables.collect(c -> new CompletionItem(c._name(), c._name() + "}"))); - } - } - return new CompletionResult(ListIterate.collect(elements, c -> new CompletionItem(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c), ">{" + org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c))).toList()); + return result; } return new CompletionResult(Lists.mutable.empty()); } @@ -239,19 +224,6 @@ public static MutableList proposeColumnNamesForEditColSpec(ColSp //-------------------------------------------------------------------- - private static boolean nameMatch(PackageableElement c, String writtenPath) - { - String path = org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c); - if (path.length() > writtenPath.length()) - { - return path.startsWith(writtenPath); - } - else - { - return writtenPath.startsWith(path); - } - } - private ValueSpecification parseValueSpecification(String value) { String code = header + value + "\n" + "\n}"; diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/CompleterExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/CompleterExtension.java new file mode 100644 index 00000000000..d872528ae08 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/autocomplete/CompleterExtension.java @@ -0,0 +1,22 @@ +// Copyright 2024 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.repl.autocomplete; + +import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; + +public interface CompleterExtension +{ + CompletionResult extraClassInstanceProcessor(Object islandExpr, PureModel pureModel); +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java index 55080188bc2..2d504eb5692 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/client/Client.java @@ -17,6 +17,7 @@ import org.eclipse.collections.api.factory.Lists; import org.eclipse.collections.api.list.MutableList; import org.eclipse.collections.impl.block.predicate.checked.CheckedPredicate; +import org.finos.legend.engine.repl.autocomplete.CompleterExtension; import org.finos.legend.engine.repl.core.Command; import org.finos.legend.engine.repl.core.ReplExtension; import org.finos.legend.engine.repl.core.commands.*; @@ -39,18 +40,22 @@ public class Client private final Terminal terminal; private final LineReader reader; private boolean debug = false; - private MutableList replExtensions = Lists.mutable.empty(); + private MutableList completerExtensions = Lists.mutable.empty(); public static void main(String[] args) throws Exception { - new Client().loop(); + new Client(Lists.mutable.empty(), Lists.mutable.empty()).loop(); } public MutableList commands; - public Client() throws Exception + public Client(MutableList replExtensions, MutableList completerExtensions) throws Exception { + replExtensions.forEach(c -> c.setClient(this)); + this.replExtensions = replExtensions; + this.completerExtensions = completerExtensions; + this.terminal = TerminalBuilder.terminal(); this.terminal.writer().println("\n" + Logos.logos.get((int) (Logos.logos.size() * Math.random())) + "\n"); @@ -157,7 +162,12 @@ public void setDebug(boolean debug) public MutableList getReplExtensions() { - return replExtensions; + return this.replExtensions; + } + + public MutableList getCompleterExtensions() + { + return this.completerExtensions; } public MutableList buildState() diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java index d12af5adc4e..ad10e8e67ae 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/ReplExtension.java @@ -17,6 +17,7 @@ import org.eclipse.collections.api.list.MutableList; import org.eclipse.collections.impl.factory.Lists; import org.finos.legend.engine.plan.execution.result.Result; +import org.finos.legend.engine.repl.client.Client; import org.finos.legend.engine.shared.core.extension.LegendExtension; public interface ReplExtension extends LegendExtension @@ -46,4 +47,6 @@ default MutableList typeGroup() boolean supports(Result res); String print(Result res); + + void setClient(Client client); } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java index cae5db4d256..5f4a2e270de 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/main/java/org/finos/legend/engine/repl/core/commands/Execute.java @@ -71,7 +71,7 @@ public MutableList complete(String inScope, LineReader lineReader, Pa try { MutableList list = Lists.mutable.empty(); - CompletionResult result = new org.finos.legend.engine.repl.autocomplete.Completer(this.client.buildState().makeString("\n")).complete(inScope); + CompletionResult result = new org.finos.legend.engine.repl.autocomplete.Completer(this.client.buildState().makeString("\n"), this.client.getCompleterExtensions()).complete(inScope); if (result.getEngineException() == null) { list.addAll(result.getCompletion().collect(this::buildCandidate)); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java index 4df74c08325..2f0ca46a6f6 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-client/src/test/java/org/finos/legend/engine/repl/TestCompleter.java @@ -29,41 +29,6 @@ public void testPrimitives() Assert.assertEquals("[sum , sum], [mean , mean], [average , average], [min , min], [max , max], [count , count], [percentile , percentile], [variancePopulation , variancePopulation], [varianceSample , varianceSample], [stdDevPopulation , stdDevPopulation], [stdDevSample , stdDevSample]", checkResultNoException(new Completer("").complete("[1,2]->"))); } - @Test - public void testRelationAccessor() - { - Assert.assertEquals("[a::A , >{a::A]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>"))); - Assert.assertEquals("[a::other , >{a::other], [a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))").complete("#>{a"))); - Assert.assertEquals("[a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))").complete("#>{a::A"))); - Assert.assertEquals("[a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))").complete("#>{a::ABC"))); - Assert.assertEquals("[t , t}]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))").complete("#>{a::ABC."))); - Assert.assertEquals("[tab , tab}]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table co(val INTEGER) Table tab(col VARCHAR(200)))").complete("#>{a::A.t"))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table tab(col VARCHAR(200)))").complete("#>{a::A.x"))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table co(val INTEGER) Table tab(col VARCHAR(200)))").complete("#>{a::A.tab}#"))); - } - - @Test - public void testAutocompleteFunctionParameter() - { - Assert.assertEquals("[test::test , test::test)]", checkResultNoException(new Completer(db + connection + runtime).complete("#>{test::TestDatabase.tb}#->from("))); - Assert.assertEquals("[test::test , test::test)]", checkResultNoException(new Completer(db + connection + runtime).complete("#>{test::TestDatabase.tb}#->from(te"))); - Assert.assertEquals("", checkResultNoException(new Completer(db + connection + runtime).complete("#>{test::TestDatabase.tb}#->from(zte"))); - } - - @Test - public void testArrowOnFunction() - { - Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|$x.col == 'oo')->"))); - Assert.assertEquals("PARSER error at [6:1-23]: parsing error", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->limit(10)-").getEngineException().toPretty()); - } - - @Test - public void testArrowRelation() - { - Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->"))); - Assert.assertEquals("[select , select], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->s"))); - } - @Test public void testArrowOnType() { @@ -71,40 +36,6 @@ public void testArrowOnType() Assert.assertEquals("", checkResultNoException(new Completer("Class x::A{name:String[1];other:Integer[1];}").complete("x::A.all()->fu"))); } - @Test - public void testArrowDeep() - { - Assert.assertEquals("[contains , contains], [startsWith , startsWith], [endsWith , endsWith], [toLower , toLower], [toUpper , toUpper], [lpad , lpad], [rpad , rpad], [parseInteger , parseInteger], [parseFloat , parseFloat]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(f|$f.col->"))); - } - - @Test - public void testErrors() - { - Assert.assertEquals("PARSER error at [6:21]: Unexpected token", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->select(*").getEngineException().toPretty()); - Assert.assertEquals("PARSER error at [6:1-21]: parsing error", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->select(!").getEngineException().toPretty()); - Assert.assertEquals("COMPILATION error at [6:1-12]: The store 'a::As' can't be found.", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::As.t}#->select(~").getEngineException().toPretty()); - } - - @Test - public void testDeepWithCompilationError() - { - Assert.assertEquals("COMPILATION error at [6:26-49]: Can't find a match for function 'plus(Any[2])'", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|'p'+$x.col->startsWith('x'))->fr").getEngineException().toPretty()); - Assert.assertEquals("COMPILATION error at [6:22-24]: Can't find type 'x'", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->extend(~x:x.").getEngineException().toPretty()); - } - - @Test - public void testArrowPostCol() - { - Assert.assertEquals("[ascending , ascending], [descending , descending]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->sort(~col->"))); - } - - @Test - public void testMultiLevelFunction() - { - Assert.assertEquals("[select , select]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->selec"))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->select(~"))); - } - //-------- // Filter //-------- @@ -120,107 +51,6 @@ public void testDotInFilterDeepClass() Assert.assertEquals("[name , name], [other , other]", checkResultNoException(new Completer("Class x::A{name:String[1];other:Integer[1];}").complete("x::A.all()->filter(x|'x'+[1,2]->map(z|$z+$x."))); } - @Test - public void testDotInFilterDeepRelation() - { - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x."))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x.co"))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x.z"))); - Assert.assertEquals("['na col' , 'na col']", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"na col\" VARCHAR(200)))").complete("#>{a::A.t}#->filter(x|$x.'na"))); - } - - - //-------- - // Rename - //-------- - @Test - public void testRenameFirstParam() - { - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~"))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~co"))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~x"))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~col,~"))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~"))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->rename(~"))); - } - - - //-------- - // Extend - //-------- - @Test - public void testDotInExtend() - { - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->extend(~x:y|$y."))); - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))").complete("#>{a::A.t}#->extend(~[x:y|$y."))); - } - - //--------- - // GroupBy - //--------- - @Test - public void testGroupBy() - { - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~"))); - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~["))); - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~[col,"))); - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~col, ~z:x|$x."))); - Assert.assertEquals("[val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.v"))); - Assert.assertEquals("[sum , sum], [mean , mean], [average , average], [min , min], [max , max], [count , count], [percentile , percentile], [variancePopulation , variancePopulation], [varianceSample , varianceSample], [stdDevPopulation , stdDevPopulation], [stdDevSample , stdDevSample]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.val:y|$y->"))); - } - - - //------ - // Join - //------ - @Test - public void testJoin() - { - Assert.assertEquals("[a::A , >{a::A]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>"))); - Assert.assertEquals("[t , t}]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A."))); - Assert.assertEquals("[JoinKind , JoinKind.]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t}#, "))); - Assert.assertEquals("[LEFT , LEFT], [INNER , INNER]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind."))); - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER, {a,b|$a."))); - Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER,"))); - Assert.assertEquals("[k , k], [o , o]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t2(k VARCHAR(200), o INT) Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t2}#, JoinKind.INNER, {a,b|$a.col == $b."))); - Assert.assertEquals("COMPILATION error at [6:14-17]: \"The relation contains duplicates: [val, col]\"", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER, {a,b|$a.val == $b.val})->").getEngineException().toPretty()); - } - - //-------- - // Select - //-------- - @Test - public void testSelect() - { - Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->select(~c"))); - Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))").complete("#>{a::A.t}#->select(~[col,"))); - Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))").complete("#>{a::A.t}#->select(~'col space')->fro"))); - Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))").complete("#>{a::A.t}#->select(~['col space'])->fro"))); - } - - - private static String db = "###Relational\n" + - "Database test::TestDatabase(Table tb(col VARCHAR(200)))\n"; - - private static String connection = "###Connection\n" + - "RelationalDatabaseConnection test::testConnection\n" + - "{\n" + - " store: test::TestDatabase;" + - " specification: LocalH2{};" + - " type: H2;" + - " auth: DefaultH2;\n" + - "}\n"; - - private static String runtime = "###Runtime\n" + - "Runtime test::test\n" + - "{\n" + - " mappings : [];\n" + - " connections:\n" + - " [\n" + - " test::TestDatabase : [connection: test::testConnection]\n" + - " ];\n" + - "}\n"; - private String checkResultNoException(CompletionResult completion) { Assert.assertNull(completion.getEngineException()); diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml index aebc9b96263..5f5657922e8 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/pom.xml @@ -38,6 +38,16 @@ legend-engine-repl-client + + + + org.finos.legend.pure + legend-pure-m4 + + + org.finos.legend.pure + legend-pure-m3-core + org.finos.legend.engine legend-engine-protocol-pure @@ -46,6 +56,13 @@ org.finos.legend.engine legend-engine-language-pure-grammar + + org.finos.legend.engine + legend-engine-language-pure-compiler + + + + org.finos.legend.engine @@ -56,6 +73,26 @@ legend-engine-xt-relationalStore-executionPlan + + + org.finos.legend.pure + legend-pure-m2-dsl-store-pure + + + org.finos.legend.pure + legend-pure-m2-store-relational-pure + + + org.finos.legend.engine + legend-engine-xt-relationalStore-executionPlan + + + org.finos.legend.engine + legend-engine-xt-relationalStore-grammar + Runtime + + + org.eclipse.collections eclipse-collections @@ -64,5 +101,16 @@ org.eclipse.collections eclipse-collections-api + + + junit + junit + + + + + org.finos.legend.engine + legend-engine-shared-core + diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java index a7c5cc98689..d44aa2c90d2 100644 --- a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/RelationalReplExtension.java @@ -34,7 +34,7 @@ public class RelationalReplExtension implements ReplExtension { - private final Client client; + private Client client; static { @@ -47,7 +47,7 @@ public String type() return "relational"; } - public RelationalReplExtension(Client client) + public void setClient(Client client) { this.client = client; } diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java new file mode 100644 index 00000000000..188d580528b --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/autocomplete/RelationalCompleterExtension.java @@ -0,0 +1,74 @@ +// Copyright 2024 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.repl.relational.autocomplete; + +import org.eclipse.collections.api.list.MutableList; +import org.eclipse.collections.impl.factory.Lists; +import org.eclipse.collections.impl.utility.ListIterate; +import org.finos.legend.engine.language.pure.compiler.toPureGraph.PureModel; +import org.finos.legend.engine.protocol.pure.v1.model.valueSpecification.raw.classInstance.relation.RelationStoreAccessor; +import org.finos.legend.engine.repl.autocomplete.CompleterExtension; +import org.finos.legend.engine.repl.autocomplete.CompletionItem; +import org.finos.legend.engine.repl.autocomplete.CompletionResult; +import org.finos.legend.engine.repl.autocomplete.parser.ParserFixer; +import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.PackageableElement; +import org.finos.legend.pure.m3.coreinstance.meta.pure.store.Store; + +public class RelationalCompleterExtension implements CompleterExtension +{ + @Override + public CompletionResult extraClassInstanceProcessor(Object islandExpr, PureModel pureModel) + { + if (islandExpr instanceof RelationStoreAccessor) + { + MutableList path = Lists.mutable.withAll(((RelationStoreAccessor) islandExpr).path); + String writtenPath = path.makeString("::").replace(ParserFixer.magicToken, ""); + MutableList elements = pureModel.getAllStores().select(c -> nameMatch(c, writtenPath)).toList(); + if (elements.size() == 1 && + writtenPath.startsWith(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(elements.get(0))) && + !writtenPath.equals(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(elements.get(0))) + ) + { + org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.Database db = (org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.Database) elements.get(0); + String writtenTableName = writtenPath.replace(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(db), "").replace("::", ""); + MutableList tables = db._schemas().isEmpty() ? Lists.mutable.empty() : db._schemas().getFirst()._tables().toList(); + MutableList foundTables = tables.select(c -> c._name().startsWith(writtenTableName)); + if ((foundTables.size() == 1 && foundTables.get(0)._name().equals(path.getLast()))) + { + return new CompletionResult(Lists.mutable.empty()); + } + else + { + return new CompletionResult(foundTables.collect(c -> new CompletionItem(c._name(), c._name() + "}"))); + } + } + return new CompletionResult(ListIterate.collect(elements, c -> new CompletionItem(org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c), ">{" + org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c))).toList()); + } + return null; + } + + private static boolean nameMatch(PackageableElement c, String writtenPath) + { + String path = org.finos.legend.pure.m3.navigation.PackageableElement.PackageableElement.getUserPathForPackageableElement(c); + if (path.length() > writtenPath.length()) + { + return path.startsWith(writtenPath); + } + else + { + return writtenPath.startsWith(path); + } + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/client/RClient.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/client/RClient.java new file mode 100644 index 00000000000..55e132c2377 --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/main/java/org/finos/legend/engine/repl/relational/client/RClient.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.repl.relational.client; + +import org.eclipse.collections.impl.factory.Lists; +import org.finos.legend.engine.repl.client.Client; +import org.finos.legend.engine.repl.relational.RelationalReplExtension; +import org.finos.legend.engine.repl.relational.autocomplete.RelationalCompleterExtension; + +public class RClient +{ + public static void main(String[] args) throws Exception + { + new Client(Lists.mutable.with(new RelationalReplExtension()), Lists.mutable.with(new RelationalCompleterExtension())).loop(); + } +} diff --git a/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java new file mode 100644 index 00000000000..a6d4c822ffb --- /dev/null +++ b/legend-engine-config/legend-engine-repl/legend-engine-repl-relational/src/test/java/org/finos/legend/engine/repl/TestCompleter.java @@ -0,0 +1,204 @@ +// Copyright 2024 Goldman Sachs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.finos.legend.engine.repl; + +import org.eclipse.collections.impl.factory.Lists; +import org.finos.legend.engine.repl.autocomplete.Completer; +import org.finos.legend.engine.repl.autocomplete.CompletionResult; +import org.finos.legend.engine.repl.relational.autocomplete.RelationalCompleterExtension; +import org.junit.Assert; +import org.junit.Test; + +public class TestCompleter +{ + @Test + public void testRelationAccessor() + { + Assert.assertEquals("[a::A , >{a::A]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>"))); + Assert.assertEquals("[a::other , >{a::other], [a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a"))); + Assert.assertEquals("[a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A"))); + Assert.assertEquals("[a::ABC , >{a::ABC]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::ABC"))); + Assert.assertEquals("[t , t}]", checkResultNoException(new Completer("###Relational\nDatabase a::ABC(Table t(col VARCHAR(200)))\nDatabase a::other(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::ABC."))); + Assert.assertEquals("[tab , tab}]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table co(val INTEGER) Table tab(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t"))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table tab(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.x"))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table co(val INTEGER) Table tab(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.tab}#"))); + } + + @Test + public void testAutocompleteFunctionParameter() + { + Assert.assertEquals("[test::test , test::test)]", checkResultNoException(new Completer(db + connection + runtime, Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{test::TestDatabase.tb}#->from("))); + Assert.assertEquals("[test::test , test::test)]", checkResultNoException(new Completer(db + connection + runtime, Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{test::TestDatabase.tb}#->from(te"))); + Assert.assertEquals("", checkResultNoException(new Completer(db + connection + runtime, Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{test::TestDatabase.tb}#->from(zte"))); + } + + @Test + public void testArrowOnFunction() + { + Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|$x.col == 'oo')->"))); + Assert.assertEquals("PARSER error at [6:1-23]: parsing error", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->limit(10)-").getEngineException().toPretty()); + } + + @Test + public void testArrowRelation() + { + Assert.assertEquals("[distinct , distinct], [drop , drop], [select , select], [extend , extend], [filter , filter], [from , from], [groupBy , groupBy], [join , join], [limit , limit], [rename , rename], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->"))); + Assert.assertEquals("[select , select], [size , size], [slice , slice], [sort , sort]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->s"))); + } + + @Test + public void testArrowDeep() + { + Assert.assertEquals("[contains , contains], [startsWith , startsWith], [endsWith , endsWith], [toLower , toLower], [toUpper , toUpper], [lpad , lpad], [rpad , rpad], [parseInteger , parseInteger], [parseFloat , parseFloat]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(f|$f.col->"))); + } + + @Test + public void testErrors() + { + Assert.assertEquals("PARSER error at [6:21]: Unexpected token", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(*").getEngineException().toPretty()); + Assert.assertEquals("PARSER error at [6:1-21]: parsing error", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(!").getEngineException().toPretty()); + Assert.assertEquals("COMPILATION error at [6:1-12]: The store 'a::As' can't be found.", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::As.t}#->select(~").getEngineException().toPretty()); + } + + @Test + public void testDeepWithCompilationError() + { + Assert.assertEquals("COMPILATION error at [6:26-49]: Can't find a match for function 'plus(Any[2])'", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|'p'+$x.col->startsWith('x'))->fr").getEngineException().toPretty()); + Assert.assertEquals("COMPILATION error at [6:22-24]: Can't find type 'x'", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->extend(~x:x.").getEngineException().toPretty()); + } + + @Test + public void testArrowPostCol() + { + Assert.assertEquals("[ascending , ascending], [descending , descending]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->sort(~col->"))); + } + + @Test + public void testMultiLevelFunction() + { + Assert.assertEquals("[select , select]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->selec"))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col])->join(#>{a::A.t}#->select(~"))); + } + + //-------- + // Filter + //-------- + @Test + public void testDotInFilterDeepRelation() + { + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x."))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x.co"))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|'x'+[1,2]->map(z|$z+$x.z"))); + Assert.assertEquals("['na col' , 'na col']", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"na col\" VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->filter(x|$x.'na"))); + } + + + //-------- + // Rename + //-------- + @Test + public void testRenameFirstParam() + { + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~"))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~co"))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~x"))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~col,~"))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~"))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->rename(~"))); + } + + + //-------- + // Extend + //-------- + @Test + public void testDotInExtend() + { + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->extend(~x:y|$y."))); + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200)))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->extend(~[x:y|$y."))); + } + + //--------- + // GroupBy + //--------- + @Test + public void testGroupBy() + { + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~"))); + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~["))); + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~[col,"))); + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~z:x|$x."))); + Assert.assertEquals("[val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.v"))); + Assert.assertEquals("[sum , sum], [mean , mean], [average , average], [min , min], [max , max], [count , count], [percentile , percentile], [variancePopulation , variancePopulation], [varianceSample , varianceSample], [stdDevPopulation , stdDevPopulation], [stdDevSample , stdDevSample]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->groupBy(~col, ~[z:x|$x.val:y|$y->"))); + } + + + //------ + // Join + //------ + @Test + public void testJoin() + { + Assert.assertEquals("[a::A , >{a::A]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>"))); + Assert.assertEquals("[t , t}]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A."))); + Assert.assertEquals("[JoinKind , JoinKind.]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t}#, "))); + Assert.assertEquals("[LEFT , LEFT], [INNER , INNER]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind."))); + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER, {a,b|$a."))); + Assert.assertEquals("", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER,"))); + Assert.assertEquals("[k , k], [o , o]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t2(k VARCHAR(200), o INT) Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t2}#, JoinKind.INNER, {a,b|$a.col == $b."))); + Assert.assertEquals("COMPILATION error at [6:14-17]: \"The relation contains duplicates: [val, col]\"", new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->join(#>{a::A.t}#, JoinKind.INNER, {a,b|$a.val == $b.val})->").getEngineException().toPretty()); + } + + //-------- + // Select + //-------- + @Test + public void testSelect() + { + Assert.assertEquals("[col , col]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~c"))); + Assert.assertEquals("[col , col], [val , val]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(col VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~[col,"))); + Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~'col space')->fro"))); + Assert.assertEquals("[from , from]", checkResultNoException(new Completer("###Relational\nDatabase a::A(Table t(\"col space\" VARCHAR(200), val INT))", Lists.mutable.with(new RelationalCompleterExtension())).complete("#>{a::A.t}#->select(~['col space'])->fro"))); + } + + + private static String db = "###Relational\n" + + "Database test::TestDatabase(Table tb(col VARCHAR(200)))\n"; + + private static String connection = "###Connection\n" + + "RelationalDatabaseConnection test::testConnection\n" + + "{\n" + + " store: test::TestDatabase;" + + " specification: LocalH2{};" + + " type: H2;" + + " auth: DefaultH2;\n" + + "}\n"; + + private static String runtime = "###Runtime\n" + + "Runtime test::test\n" + + "{\n" + + " mappings : [];\n" + + " connections:\n" + + " [\n" + + " test::TestDatabase : [connection: test::testConnection]\n" + + " ];\n" + + "}\n"; + + private String checkResultNoException(CompletionResult completion) + { + Assert.assertNull(completion.getEngineException()); + return completion.getCompletion().makeString(", "); + } +}