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 extends org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.relation.Table> tables = db._schemas().isEmpty() ? Lists.mutable.empty() : db._schemas().getFirst()._tables().toList();
- MutableList extends org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.relation.Table> 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 extends org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.relation.Table> tables = db._schemas().isEmpty() ? Lists.mutable.empty() : db._schemas().getFirst()._tables().toList();
+ MutableList extends org.finos.legend.pure.m3.coreinstance.meta.relational.metamodel.relation.Table> 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(", ");
+ }
+}