Update Runtime Notifications API (#1055)

Co-authored-by: Ara Adkins <iamrecursion@users.noreply.github.com>
This commit is contained in:
Dmitry Bushev 2020-08-04 13:31:56 +03:00 committed by GitHub
parent d72de5f306
commit 467d13a9e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 217 additions and 158 deletions

View File

@ -28,6 +28,8 @@ transport formats, please look [here](./protocol-architecture).
- [`SuggestionEntryArgument`](#suggestionentryargument)
- [`SuggestionEntry`](#suggestionentry)
- [`SuggestionEntryType`](#suggestionentrytype)
- [`SuggestionId`](#suggestionid)
- [`SuggestionsDatabaseEntry`](#suggestionsdatabaseentry)
- [`SuggestionsDatabaseUpdate`](#suggestionsdatabaseupdate)
- [`File`](#file)
- [`DirectoryTree`](#directorytree)
@ -203,8 +205,13 @@ Points to a method definition.
```typescript
interface MethodPointer {
file: Path;
/** The fully qualified module name. */
module: String;
/** The type on which the method is defined. */
definedOnType: String;
/** The method name. */
name: String;
}
```
@ -216,8 +223,11 @@ interface ExpressionValueUpdate {
/** The id of updated expression */
expressionId: ExpressionId;
/** The updated suggestion id */
suggestionId: number;
/** The updated type of the expression */
type?: String;
/** The updated pointer to the method call */
methodPointer?: SuggestionId;
}
```
@ -335,6 +345,16 @@ The suggestion entry type that is used as a filter in search requests.
type SuggestionEntryType = Atom | Method | Function | Local;
```
### `SuggestionId`
The suggestion entry id of the suggestions database.
#### Format
```typescript
type SuggestionId = number;
```
### `SuggestionsDatabaseEntry`
#### Format
@ -344,7 +364,7 @@ The entry in the suggestions database.
```typescript
interface SuggestionsDatabaseEntry {
// suggestion entry id
id: number;
id: SuggestionId;
// suggestion entry
suggestion: SuggestionEntry;
}
@ -362,19 +382,19 @@ type SuggestionsDatabaseUpdate = Add | Remove | Modify;
interface Add {
// suggestion entry id
id: number;
id: SuggestionId;
// suggestion entry
suggestion: SuggestionEntry;
}
interface Remove {
// suggestion entry id
id: number;
id: SuggestionId;
}
interface Modify {
// suggestion entry id
id: number;
id: SuggestionId;
// new return type
returnType: String;
}

View File

@ -89,16 +89,41 @@ final class ContextEventsListener(
sessionRouter ! DeliverToBinaryController(rpcSession.clientId, payload)
case RunExpressionUpdates if expressionUpdates.nonEmpty =>
val updatedExpressionIds = expressionUpdates.map(_.expressionId)
def toMethodPointer(call: Api.MethodPointer): (String, String, String) =
(call.module, call.definedOnType, call.name)
val methodPointerToExpression = expressionUpdates
.flatMap(update =>
update.methodCall.map(call =>
toMethodPointer(call) -> update.expressionId
)
)
.toMap
val methodPointers = methodPointerToExpression.keys.toSeq
repo
.getAllByExternalIds(updatedExpressionIds)
.getAllMethods(methodPointers)
.map { suggestionIds =>
val valueUpdates = updatedExpressionIds.zip(suggestionIds).flatMap {
case (expressionId, Some(suggestionId)) =>
Some(ExpressionValueUpdate(expressionId, suggestionId))
case (id, None) =>
log.error("Unable to find suggestion with expression id: {}", id)
None
val methodPointerToSuggestion =
methodPointers
.zip(suggestionIds)
.collect {
case (ptr, Some(suggestionId)) =>
ptr -> suggestionId
}
.toMap
val valueUpdates = expressionUpdates.map { update =>
ExpressionValueUpdate(
update.expressionId,
update.expressionType,
update.methodCall.flatMap { call =>
val pointer = toMethodPointer(call)
methodPointerToSuggestion.get(pointer) match {
case suggestionId @ Some(_) => suggestionId
case None =>
log.error(s"Unable to find suggestion for $pointer")
None
}
}
)
}
val payload =
ContextRegistryProtocol.ExpressionValuesComputedNotification(

View File

@ -8,7 +8,6 @@ import org.enso.languageserver.event.{
ExecutionContextCreated,
ExecutionContextDestroyed
}
import org.enso.languageserver.filemanager.FileSystemFailure
import org.enso.languageserver.monitoring.MonitoringProtocol.{Ping, Pong}
import org.enso.languageserver.runtime.handler._
import org.enso.languageserver.util.UnhandledLogging
@ -134,14 +133,11 @@ final class ContextRegistry(
case PushContextRequest(client, contextId, stackItem) =>
if (store.hasContext(client.clientId, contextId)) {
getRuntimeStackItem(stackItem) match {
case Right(stackItem) =>
val handler =
context.actorOf(PushContextHandler.props(timeout, runtime))
handler.forward(Api.PushContextRequest(contextId, stackItem))
case Left(error) =>
sender() ! FileSystemError(error)
}
val item = getRuntimeStackItem(stackItem)
val handler =
context.actorOf(PushContextHandler.props(timeout, runtime))
handler.forward(Api.PushContextRequest(contextId, item))
} else {
sender() ! AccessDenied
}
@ -224,27 +220,24 @@ final class ContextRegistry(
private def getRuntimeStackItem(
stackItem: StackItem
): Either[FileSystemFailure, Api.StackItem] =
): Api.StackItem =
stackItem match {
case StackItem.ExplicitCall(pointer, argument, arguments) =>
getRuntimeMethodPointer(pointer).map { methodPointer =>
Api.StackItem.ExplicitCall(methodPointer, argument, arguments)
}
val methodPointer = getRuntimeMethodPointer(pointer)
Api.StackItem.ExplicitCall(methodPointer, argument, arguments)
case StackItem.LocalCall(expressionId) =>
Right(Api.StackItem.LocalCall(expressionId))
Api.StackItem.LocalCall(expressionId)
}
private def getRuntimeMethodPointer(
pointer: MethodPointer
): Either[FileSystemFailure, Api.MethodPointer] =
config.findContentRoot(pointer.file.rootId).map { rootPath =>
Api.MethodPointer(
file = pointer.file.toFile(rootPath),
definedOnType = pointer.definedOnType,
name = pointer.name
)
}
): Api.MethodPointer =
Api.MethodPointer(
module = pointer.module,
definedOnType = pointer.definedOnType,
name = pointer.name
)
private def toRuntimeInvalidatedExpressions(
expressions: InvalidatedExpressions

View File

@ -6,6 +6,11 @@ import org.enso.languageserver.runtime.ExecutionApi.ExpressionId
* An update containing information about expression.
*
* @param expressionId the id of updated expression
* @param suggestionId the updated suggestion id
* @param `type` the updated type of expression
* @param methodPointer the suggestion id of the updated method pointer
*/
case class ExpressionValueUpdate(expressionId: ExpressionId, suggestionId: Long)
case class ExpressionValueUpdate(
expressionId: ExpressionId,
`type`: Option[String],
methodPointer: Option[Long]
)

View File

@ -1,12 +1,10 @@
package org.enso.languageserver.runtime
import org.enso.languageserver.filemanager.Path
/**
* An object pointing to a method definition.
*
* @param file path to the method file
* @param module the module of the method file
* @param definedOnType method type
* @param name method name
*/
case class MethodPointer(file: Path, definedOnType: String, name: String)
case class MethodPointer(module: String, definedOnType: String, name: String)

View File

@ -74,8 +74,14 @@ class ContextEventsListenerSpec
Vector(
Api.ExpressionValueUpdate(
Suggestions.method.externalId.get,
None,
None
Some(Suggestions.method.returnType),
Some(
Api.MethodPointer(
Suggestions.method.module,
Suggestions.method.selfType,
Suggestions.method.name
)
)
)
)
)
@ -88,7 +94,8 @@ class ContextEventsListenerSpec
Vector(
ExpressionValueUpdate(
Suggestions.method.externalId.get,
suggestionIds(1).get
Some(Suggestions.method.returnType),
Some(suggestionIds(1).get)
)
)
)
@ -98,7 +105,7 @@ class ContextEventsListenerSpec
"send expression updates grouped" taggedAs Retry in withDb(0.seconds) {
(clientId, contextId, repo, router, listener) =>
val (_, suggestionIds) = Await.result(
Await.result(
repo.insertAll(
Seq(
Suggestions.atom,
@ -142,11 +149,13 @@ class ContextEventsListenerSpec
Vector(
ExpressionValueUpdate(
Suggestions.method.externalId.get,
suggestionIds(1).get
None,
None
),
ExpressionValueUpdate(
Suggestions.local.externalId.get,
suggestionIds(3).get
None,
None
)
)
)

View File

@ -198,7 +198,11 @@ object Runtime {
/**
* A representation of a pointer to a method definition.
*/
case class MethodPointer(file: File, definedOnType: String, name: String)
case class MethodPointer(
module: String,
definedOnType: String,
name: String
)
/**
* A representation of an executable position in code.

View File

@ -119,7 +119,7 @@ public class ExecutionService {
* Executes a method described by its name, constructor it's defined on and the module it's
* defined in.
*
* @param modulePath the path to the module where the method is defined.
* @param moduleName the module where the method is defined.
* @param consName the name of the constructor the method is defined on.
* @param methodName the method name.
* @param cache the precomputed expression values.
@ -129,7 +129,7 @@ public class ExecutionService {
* @param funCallCallback the consumer for function call events.
*/
public void execute(
File modulePath,
String moduleName,
String consName,
String methodName,
RuntimeCache cache,
@ -140,7 +140,7 @@ public class ExecutionService {
throws UnsupportedMessageException, ArityException, UnsupportedTypeException {
Optional<FunctionCallInstrumentationNode.FunctionCall> callMay =
context
.getModuleForFile(modulePath)
.findModule(moduleName)
.flatMap(module -> prepareFunctionCall(module, consName, methodName));
if (!callMay.isPresent()) {
return;

View File

@ -21,7 +21,7 @@ import scala.jdk.OptionConverters._
*
* @param files a files to compile
*/
class EnsureCompiledJob(protected val files: List[File])
class EnsureCompiledJob(protected val files: Iterable[File])
extends Job[Unit](List.empty, true, false) {
/**

View File

@ -14,8 +14,9 @@ import scala.jdk.OptionConverters._
*
* @param stack a call stack
*/
class EnsureCompiledStackJob(stack: Iterable[InstrumentFrame])
extends EnsureCompiledJob(EnsureCompiledStackJob.extractFiles(stack)) {
class EnsureCompiledStackJob(stack: Iterable[InstrumentFrame])(implicit
ctx: RuntimeContext
) extends EnsureCompiledJob(EnsureCompiledStackJob.extractFiles(stack)) {
/** @inheritdoc */
override def ensureCompiled(
@ -38,7 +39,7 @@ class EnsureCompiledStackJob(stack: Iterable[InstrumentFrame])
)(implicit ctx: RuntimeContext): Option[CachePreferenceAnalysis.Metadata] =
stack.lastOption flatMap {
case InstrumentFrame(Api.StackItem.ExplicitCall(ptr, _, _), _) =>
ctx.executionService.getContext.getModuleForFile(ptr.file).toScala.map {
ctx.executionService.getContext.findModule(ptr.module).toScala.map {
module =>
module.getIr
.unsafeGetMetadata(
@ -58,12 +59,19 @@ object EnsureCompiledStackJob {
* @param stack a call stack
* @return a list of files to compile
*/
private def extractFiles(stack: Iterable[InstrumentFrame]): List[File] =
private def extractFiles(stack: Iterable[InstrumentFrame])(implicit
ctx: RuntimeContext
): Iterable[File] =
stack
.map(_.item)
.collect {
.flatMap {
case Api.StackItem.ExplicitCall(methodPointer, _, _) =>
methodPointer.file
ctx.executionService.getContext
.findModule(methodPointer.module)
.flatMap(module => java.util.Optional.ofNullable(module.getPath))
.map(path => new File(path))
.toScala
case _ =>
None
}
.toList
}

View File

@ -1,6 +1,5 @@
package org.enso.interpreter.instrument.job
import java.io.File
import java.util.{Objects, UUID}
import java.util.function.Consumer
import java.util.logging.Level
@ -25,8 +24,6 @@ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.Functi
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.ContextId
import scala.jdk.javaapi.OptionConverters
/**
* Provides support for executing Enso code. Adds convenient methods to
* run Enso programs in a Truffle context.
@ -56,9 +53,12 @@ trait ProgramExecutionSupport {
enterables += fun.getExpressionId -> fun.getCall
}
executionFrame match {
case ExecutionFrame(ExecutionItem.Method(file, cons, function), cache) =>
case ExecutionFrame(
ExecutionItem.Method(module, cons, function),
cache
) =>
ctx.executionService.execute(
file,
module,
cons,
function,
cache,
@ -250,21 +250,15 @@ trait ProgramExecutionSupport {
private def toMethodPointer(
value: ExpressionValue
)(implicit ctx: RuntimeContext): Option[Api.MethodPointer] =
): Option[Api.MethodPointer] =
for {
call <- Option(value.getCallInfo)
moduleName <- Option(call.getModuleName)
functionName = call.getFunctionName
typeName <- Option(call.getTypeName).map(_.item)
module <- OptionConverters.toScala(
ctx.executionService.getContext.getTopScope
.getModule(moduleName.toString)
)
modulePath <- Option(module.getPath)
typeName <- Option(call.getTypeName).map(_.item)
} yield Api.MethodPointer(
new File(modulePath),
moduleName.toString,
typeName,
functionName
call.getFunctionName
)
}
@ -296,11 +290,11 @@ object ProgramExecutionSupport {
/** The explicit method call.
*
* @param file the file containing the method
* @param module the module containing the method
* @param constructor the type on which the method is defined
* @param function the method name
*/
case class Method(file: File, constructor: String, function: String)
case class Method(module: String, constructor: String, function: String)
extends ExecutionItem
object Method {
@ -312,7 +306,7 @@ object ProgramExecutionSupport {
*/
def apply(call: Api.StackItem.ExplicitCall): Method =
Method(
call.methodPointer.file,
call.methodPointer.module,
call.methodPointer.definedOnType,
call.methodPointer.name
)

View File

@ -144,7 +144,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
Main.idMainY,
Some("Number"),
Some(Api.MethodPointer(pkg.mainFile, "Number", "foo"))
Some(Api.MethodPointer("Test.Main", "Number", "foo"))
)
)
)
@ -228,7 +228,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainY,
Some("Number"),
Some(Api.MethodPointer(pkg.mainFile, "Main", "foo"))
Some(Api.MethodPointer("Test.Main", "Main", "foo"))
)
)
)
@ -242,7 +242,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainZ,
Some("Number"),
Some(Api.MethodPointer(pkg.mainFile, "Main", "bar"))
Some(Api.MethodPointer("Test.Main", "Main", "bar"))
)
)
)
@ -284,7 +284,7 @@ class RuntimeServerTest
}
"RuntimeServer" should "push and pop functions on the stack" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -306,7 +306,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -333,7 +333,7 @@ class RuntimeServerTest
// push method pointer on top of the non-empty stack
val invalidExplicitCall = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -402,7 +402,7 @@ class RuntimeServerTest
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer(moduleName, "Main", "main"),
None,
Vector()
)
@ -521,7 +521,7 @@ class RuntimeServerTest
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer(moduleName, "Main", "main"),
None,
Vector()
)
@ -689,7 +689,7 @@ class RuntimeServerTest
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer(moduleName, "Main", "main"),
None,
Vector()
)
@ -818,7 +818,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainA,
Some("Number"),
Some(Api.MethodPointer(mainFile, "Number", "x"))
Some(Api.MethodPointer(moduleName, "Number", "x"))
)
)
)
@ -865,7 +865,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainA,
Some("Number"),
Some(Api.MethodPointer(mainFile, "Main", "pie"))
Some(Api.MethodPointer(moduleName, "Main", "pie"))
)
)
)
@ -895,7 +895,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainA,
Some("Number"),
Some(Api.MethodPointer(mainFile, "Main", "uwu"))
Some(Api.MethodPointer(moduleName, "Main", "uwu"))
)
)
)
@ -925,7 +925,7 @@ class RuntimeServerTest
Api.ExpressionValueUpdate(
idMainA,
Some("Text"),
Some(Api.MethodPointer(mainFile, "Main", "hie"))
Some(Api.MethodPointer(moduleName, "Main", "hie"))
)
)
)
@ -992,7 +992,7 @@ class RuntimeServerTest
contextId,
Api.StackItem
.ExplicitCall(
Api.MethodPointer(fooFile, "Foo", "main"),
Api.MethodPointer("Test.Foo", "Foo", "main"),
None,
Vector()
)
@ -1080,7 +1080,7 @@ class RuntimeServerTest
contextId,
Api.StackItem
.ExplicitCall(
Api.MethodPointer(fooFile, "Foo", "main"),
Api.MethodPointer("Test.Foo", "Foo", "main"),
None,
Vector()
)
@ -1168,7 +1168,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1329,7 +1329,7 @@ class RuntimeServerTest
contextId,
Api.StackItem
.ExplicitCall(
Api.MethodPointer(fooFile, "Foo", "main"),
Api.MethodPointer("Test.Foo", "Foo", "main"),
None,
Vector()
)
@ -1405,7 +1405,7 @@ class RuntimeServerTest
}
it should "recompute expressions without invalidation" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -1417,7 +1417,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1441,7 +1441,7 @@ class RuntimeServerTest
}
it should "recompute expressions invalidating all" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -1453,7 +1453,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1483,7 +1483,7 @@ class RuntimeServerTest
}
it should "recompute expressions invalidating some" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -1495,7 +1495,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1527,7 +1527,7 @@ class RuntimeServerTest
}
it should "return error when computing erroneous code" in {
val mainFile = context.writeMain(context.MainWithError.code)
context.writeMain(context.MainWithError.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -1539,7 +1539,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1562,7 +1562,7 @@ class RuntimeServerTest
}
it should "skip side effects when evaluating cached expression" in {
val file = context.writeMain(context.Main2.code)
context.writeMain(context.Main2.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -1574,7 +1574,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(file, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1601,7 +1601,7 @@ class RuntimeServerTest
}
it should "emit visualisation update when expression is evaluated" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val visualisationFile =
context.writeInSrcDir("Visualisation", context.Visualisation.code)
@ -1627,7 +1627,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -1752,7 +1752,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer(moduleName, "Main", "main"),
None,
Vector()
)
@ -1918,7 +1918,7 @@ class RuntimeServerTest
}
it should "be able to modify visualisations" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val visualisationFile =
context.writeInSrcDir("Visualisation", context.Visualisation.code)
@ -1944,7 +1944,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -2031,7 +2031,7 @@ class RuntimeServerTest
}
it should "not emit visualisation updates when visualisation is detached" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val visualisationFile =
context.writeInSrcDir("Visualisation", context.Visualisation.code)
@ -2075,7 +2075,7 @@ class RuntimeServerTest
)
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)
@ -2130,7 +2130,7 @@ class RuntimeServerTest
}
it should "rename a project" in {
val mainFile = context.writeMain(context.Main.code)
context.writeMain(context.Main.code)
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
@ -2142,7 +2142,7 @@ class RuntimeServerTest
// push main
val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer(mainFile, "Main", "main"),
Api.MethodPointer("Test.Main", "Main", "main"),
None,
Vector()
)

View File

@ -32,7 +32,8 @@ public class SuggestionsRepoBenchmark {
final Path dbfile = Path.of(System.getProperty("java.io.tmpdir"), "bench-suggestions.db");
final Seq<Suggestion.Kind> kinds = SuggestionRandom.nextKinds();
final Seq<scala.Tuple2<UUID, String>> updateInput = SuggestionRandom.nextUpdateAllInput();
final Seq<UUID> getAllByExternalIdsInput = SuggestionRandom.nextGetByExternalIdsInput();
final Seq<scala.Tuple3<String, String, String>> getAllMethodsInput =
SuggestionRandom.nextGetAllMethodsInput();
SqlSuggestionsRepo repo;
@ -104,8 +105,8 @@ public class SuggestionsRepoBenchmark {
}
@Benchmark
public Object searchByExternalIds() throws TimeoutException, InterruptedException {
return Await.result(repo.getAllByExternalIds(getAllByExternalIdsInput), TIMEOUT);
public Object getAllMethods() throws TimeoutException, InterruptedException {
return Await.result(repo.getAllMethods(getAllMethodsInput), TIMEOUT);
}
@Benchmark

View File

@ -11,8 +11,11 @@ object SuggestionRandom {
def nextUpdateAllInput(): Seq[(UUID, String)] =
Seq(UUID.randomUUID() -> nextString())
def nextGetByExternalIdsInput(): Seq[UUID] =
Seq(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())
def nextGetAllMethodsInput(): Seq[(String, String, String)] =
Seq(
(nextString(), nextString(), nextString()),
(nextString(), nextString(), nextString())
)
def nextKinds(): Seq[Suggestion.Kind] =
Set.fill(1)(nextKind()).toSeq

View File

@ -14,12 +14,12 @@ trait SuggestionsRepo[F[_]] {
*/
def getAll: F[(Long, Seq[SuggestionEntry])]
/** Get suggestions by external ids.
/** Get suggestions by the method call info.
*
* @param ids the list of external ids
* @param calls the list of triples: module, self type and method name
* @return the list of found suggestion ids
*/
def getAllByExternalIds(ids: Seq[Suggestion.ExternalId]): F[Seq[Option[Long]]]
def getAllMethods(calls: Seq[(String, String, String)]): F[Seq[Option[Long]]]
/** Search suggestion by various parameters.
*

View File

@ -40,10 +40,10 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
db.run(getAllQuery)
/** @inheritdoc */
override def getAllByExternalIds(
ids: Seq[Suggestion.ExternalId]
override def getAllMethods(
calls: Seq[(String, String, String)]
): Future[Seq[Option[Long]]] =
db.run(getAllByExternalIdsQuery(ids))
db.run(getAllMethodsQuery(calls))
/** @inheritdoc */
override def search(
@ -140,34 +140,32 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
query.transactionally
}
/** The query to get suggestions by external ids.
/** The query to get the suggestions by the method call info.
*
* @param ids the list of external ids
* @param calls the triples containing module name, self type, method name
* @return the list of found suggestion ids
*/
private def getAllByExternalIdsQuery(
ids: Seq[Suggestion.ExternalId]
def getAllMethodsQuery(
calls: Seq[(String, String, String)]
): DBIO[Seq[Option[Long]]] =
if (ids.isEmpty) {
if (calls.isEmpty) {
DBIO.successful(Seq())
} else {
val bits =
ids.map(id => (id.getLeastSignificantBits, id.getMostSignificantBits))
val query = Suggestions
.filter { row =>
bits
calls
.map {
case (least, most) =>
row.externalIdLeast === least && row.externalIdMost === most
case (module, selfType, name) =>
row.module === module && row.selfType === selfType && row.name === name
}
.reduce(_ || _)
}
.map(row => (row.id, row.externalIdLeast, row.externalIdMost))
query.result.map { triples =>
val result = triples.flatMap {
case (id, least, most) => toUUID(least, most).map(_ -> id)
.map(row => (row.id, row.module, row.selfType, row.name))
query.result.map { tuples =>
val result = tuples.map {
case (id, module, selfType, name) => (module, selfType, name) -> id
}.toMap
ids.map(result.get)
calls.map(result.get)
}
}

View File

@ -63,7 +63,7 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
)
}
"get suggestions by external ids" taggedAs Retry in withRepo { repo =>
"get suggestions by method call info" taggedAs Retry in withRepo { repo =>
val action = for {
(_, ids) <- repo.insertAll(
Seq(
@ -73,30 +73,31 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
suggestion.local
)
)
results <- repo.getAllByExternalIds(
Seq(suggestion.method.externalId.get, suggestion.local.externalId.get)
results <- repo.getAllMethods(
Seq(("Test.Main", "Main", "main"), ("Test.Main", "Main", "foo"))
)
} yield (ids, results)
val (ids, results) = Await.result(action, Timeout)
results should contain theSameElementsAs Seq(ids(1), ids(3))
results should contain theSameElementsInOrderAs Seq(ids(1), None)
}
"get suggestions by empty external ids" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
"get suggestions by empty method call info" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insertAll(
Seq(
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
)
results <- repo.getAllByExternalIds(Seq())
} yield results
results <- repo.getAllMethods(Seq())
} yield results
val results = Await.result(action, Timeout)
results.isEmpty shouldEqual true
val results = Await.result(action, Timeout)
results.isEmpty shouldEqual true
}
"fail to insert duplicate suggestion" taggedAs Retry in withRepo { repo =>