Fix the Suggestions Database Updates Handling (#1161)

Misc updates to the Suggestions database updates handling
algorithm
This commit is contained in:
Dmitry Bushev 2020-10-05 17:22:13 +03:00 committed by GitHub
parent 8e07e0347f
commit 3d65ffd3cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 439 additions and 415 deletions

View File

@ -90,11 +90,7 @@ final class SuggestionsHandler(
context.system.eventStream context.system.eventStream
.subscribe(self, classOf[Api.ExpressionValuesComputed]) .subscribe(self, classOf[Api.ExpressionValuesComputed])
context.system.eventStream context.system.eventStream
.subscribe(self, classOf[Api.SuggestionsDatabaseUpdateNotification]) .subscribe(self, classOf[Api.SuggestionsDatabaseModuleUpdateNotification])
context.system.eventStream
.subscribe(self, classOf[Api.SuggestionsDatabaseReIndexNotification])
context.system.eventStream
.subscribe(self, classOf[Api.SuggestionsDatabaseIndexUpdateNotification])
context.system.eventStream.subscribe(self, classOf[ProjectNameChangedEvent]) context.system.eventStream.subscribe(self, classOf[ProjectNameChangedEvent])
context.system.eventStream.subscribe(self, classOf[FileDeletedEvent]) context.system.eventStream.subscribe(self, classOf[FileDeletedEvent])
context.system.eventStream context.system.eventStream
@ -143,24 +139,7 @@ final class SuggestionsHandler(
sender() ! CapabilityReleased sender() ! CapabilityReleased
context.become(initialized(projectName, clients - client.clientId)) context.become(initialized(projectName, clients - client.clientId))
case msg: Api.SuggestionsDatabaseIndexUpdateNotification => case msg: Api.SuggestionsDatabaseModuleUpdateNotification =>
applyIndexedModuleUpdates(msg.updates.toSeq)
.onComplete {
case Success(notification) =>
if (notification.updates.nonEmpty) {
clients.foreach { clientId =>
sessionRouter ! DeliverToJsonController(clientId, notification)
}
}
case Failure(ex) =>
log.error(
ex,
"Error applying suggestion database updates: {}",
msg.updates.map(_.file)
)
}
case msg: Api.SuggestionsDatabaseUpdateNotification =>
applyDatabaseUpdates(msg) applyDatabaseUpdates(msg)
.onComplete { .onComplete {
case Success(notification) => case Success(notification) =>
@ -173,25 +152,7 @@ final class SuggestionsHandler(
log.error( log.error(
ex, ex,
"Error applying suggestion database updates: {}", "Error applying suggestion database updates: {}",
msg.updates msg.file
)
}
case msg: Api.SuggestionsDatabaseReIndexNotification =>
log.debug(s"ReIndex ${msg.moduleName} ${msg.updates.map(_.suggestion)}")
applyReIndexUpdates(msg.updates)
.onComplete {
case Success(notification) =>
if (notification.updates.nonEmpty) {
clients.foreach { clientId =>
sessionRouter ! DeliverToJsonController(clientId, notification)
}
}
case Failure(ex) =>
log.error(
ex,
"Error applying suggestion re-index updates: {}",
msg.updates
) )
} }
@ -211,6 +172,20 @@ final class SuggestionsHandler(
} }
SuggestionsDatabaseUpdateNotification(version, updates) SuggestionsDatabaseUpdateNotification(version, updates)
} }
.onComplete {
case Success(notification) =>
if (notification.updates.nonEmpty) {
clients.foreach { clientId =>
sessionRouter ! DeliverToJsonController(clientId, notification)
}
}
case Failure(ex) =>
log.error(
ex,
"Error applying changes from computed values: {}",
updates
)
}
case GetSuggestionsDatabaseVersion => case GetSuggestionsDatabaseVersion =>
suggestionsRepo.currentVersion suggestionsRepo.currentVersion
@ -314,68 +289,6 @@ final class SuggestionsHandler(
} }
} }
private def applyIndexedModuleUpdates(
updates: Seq[Api.IndexedModule]
): Future[SuggestionsDatabaseUpdateNotification] = {
def createIndexedModuleUpdatesBatch(
contents: String,
file: java.io.File,
updates: Seq[Api.SuggestionsDatabaseUpdate.Add]
): Future[Seq[Api.SuggestionsDatabaseUpdate.Add]] =
fileVersionsRepo
.updateVersion(file, versionCalculator.evalDigest(contents))
.map(versionChanged => if (versionChanged) updates else Seq())
def getBatches =
Future
.traverse(updates) { indexed =>
createIndexedModuleUpdatesBatch(
indexed.contents,
indexed.file,
indexed.updates
)
}
.map(_.flatten)
for {
batch <- getBatches
update <- applyReIndexUpdates(batch)
} yield update
}
/**
* Handle the suggestions database re-index update.
*
* Function clears existing module suggestions from the database, inserts new
* suggestions and builds the notification containing combined removed and
* added suggestions.
*
* @param updates the list of updates after the full module re-index
* @return the API suggestions database update notification
*/
private def applyReIndexUpdates(
updates: Seq[Api.SuggestionsDatabaseUpdate.Add]
): Future[SuggestionsDatabaseUpdateNotification] = {
val added = updates.map(_.suggestion)
val modules = updates.map(_.suggestion.module).distinct
log.debug(s"Applying re-index updates; modules=$modules")
for {
(_, removedIds) <- suggestionsRepo.removeAllByModule(modules)
(version, addedIds) <- suggestionsRepo.insertAll(added)
} yield {
val updatesRemoved = removedIds.map(SuggestionsDatabaseUpdate.Remove)
val updatesAdded = (addedIds zip added).flatMap {
case (Some(id), suggestion) =>
Some(SuggestionsDatabaseUpdate.Add(id, suggestion))
case (None, suggestion) =>
log.error("Failed to insert re-index suggestion: {}", suggestion)
None
}
SuggestionsDatabaseUpdateNotification(
version,
updatesRemoved :++ updatesAdded
)
}
}
/** /**
* Handle the suggestions database update. * Handle the suggestions database update.
* *
@ -386,28 +299,42 @@ final class SuggestionsHandler(
* @return the API suggestions database update notification * @return the API suggestions database update notification
*/ */
private def applyDatabaseUpdates( private def applyDatabaseUpdates(
msg: Api.SuggestionsDatabaseUpdateNotification msg: Api.SuggestionsDatabaseModuleUpdateNotification
): Future[SuggestionsDatabaseUpdateNotification] = { ): Future[SuggestionsDatabaseUpdateNotification] = {
val (added, removed) = msg.updates val (addCmds, removeCmds, cleanCmds) = msg.updates
.foldLeft((Seq[Suggestion](), Seq[Suggestion]())) { .foldLeft(
case ((add, remove), msg: Api.SuggestionsDatabaseUpdate.Add) => (Vector[Suggestion](), Vector[Suggestion](), Vector[String]())
(add :+ msg.suggestion, remove) ) {
case ((add, remove), msg: Api.SuggestionsDatabaseUpdate.Remove) => case ((add, remove, clean), m: Api.SuggestionsDatabaseUpdate.Add) =>
(add, remove :+ msg.suggestion) (add :+ m.suggestion, remove, clean)
case ((add, remove, clean), m: Api.SuggestionsDatabaseUpdate.Remove) =>
(add, remove :+ m.suggestion, clean)
case ((add, remove, clean), m: Api.SuggestionsDatabaseUpdate.Clean) =>
(add, remove, clean :+ m.module)
} }
val fileVersion = versionCalculator.evalDigest(msg.contents)
log.debug( log.debug(
s"Applying suggestion updates; added=${added s"Applying suggestion updates: Add(${addCmds.map(_.name).mkString(",")}); Remove(${removeCmds
.map(_.name)}; removed=${removed.map(_.name)}" .map(_.name)
.mkString(",")}); Clean(${cleanCmds.mkString(",")})"
) )
for { for {
(_, removedIds) <- suggestionsRepo.removeAll(removed) (_, cleanedIds) <- suggestionsRepo.removeAllByModule(cleanCmds)
(version, addedIds) <- suggestionsRepo.insertAll(added) (_, removedIds) <- suggestionsRepo.removeAll(removeCmds)
(version, addedIds) <- suggestionsRepo.insertAll(addCmds)
_ <- fileVersionsRepo.setVersion(msg.file, fileVersion)
} yield { } yield {
val updatesRemoved = removedIds.collect { val updatesCleaned = cleanedIds.map(SuggestionsDatabaseUpdate.Remove)
case Some(id) => SuggestionsDatabaseUpdate.Remove(id) val updatesRemoved =
(removedIds zip removeCmds).flatMap {
case (Some(id), _) =>
Some(SuggestionsDatabaseUpdate.Remove(id))
case (None, suggestion) =>
log.error("Failed to remove suggestion: {}", suggestion)
None
} }
val updatesAdded = val updatesAdded =
(addedIds zip added).flatMap { (addedIds zip addCmds).flatMap {
case (Some(id), suggestion) => case (Some(id), suggestion) =>
Some(SuggestionsDatabaseUpdate.Add(id, suggestion)) Some(SuggestionsDatabaseUpdate.Add(id, suggestion))
case (None, suggestion) => case (None, suggestion) =>
@ -416,7 +343,7 @@ final class SuggestionsHandler(
} }
SuggestionsDatabaseUpdateNotification( SuggestionsDatabaseUpdateNotification(
version, version,
updatesRemoved :++ updatesAdded updatesCleaned :++ updatesRemoved :++ updatesAdded
) )
} }
} }

View File

@ -71,7 +71,9 @@ class SuggestionsHandlerSpec
expectMsg(CapabilityAcquired) expectMsg(CapabilityAcquired)
// receive updates // receive updates
handler ! Api.SuggestionsDatabaseUpdateNotification( handler ! Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Add) Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Add)
) )
@ -105,7 +107,9 @@ class SuggestionsHandlerSpec
expectMsg(CapabilityAcquired) expectMsg(CapabilityAcquired)
// receive updates // receive updates
handler ! Api.SuggestionsDatabaseUpdateNotification( handler ! Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Add) ++ Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Add) ++
Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Remove) Suggestions.all.map(Api.SuggestionsDatabaseUpdate.Remove)
) )
@ -178,7 +182,7 @@ class SuggestionsHandlerSpec
"search entries by empty search query" taggedAs Retry in withDb { "search entries by empty search query" taggedAs Retry in withDb {
(config, repo, _, _, handler) => (config, repo, _, _, handler) =>
Await.ready(repo.insertAll(Suggestions.all), Timeout) val (_, inserted) = Await.result(repo.insertAll(Suggestions.all), Timeout)
handler ! SearchProtocol.Completion( handler ! SearchProtocol.Completion(
file = mkModulePath(config, "Foo", "Main.enso"), file = mkModulePath(config, "Foo", "Main.enso"),
position = Position(0, 0), position = Position(0, 0),
@ -187,7 +191,12 @@ class SuggestionsHandlerSpec
tags = None tags = None
) )
expectMsg(SearchProtocol.CompletionResult(4L, Seq())) expectMsg(
SearchProtocol.CompletionResult(
4L,
Seq(inserted(0).get, inserted(1).get)
)
)
} }
"search entries by self type" taggedAs Retry in withDb { "search entries by self type" taggedAs Retry in withDb {

View File

@ -1,5 +1,7 @@
package org.enso.languageserver.websocket.json package org.enso.languageserver.websocket.json
import java.io.File
import io.circe.literal._ import io.circe.literal._
import org.enso.languageserver.refactoring.ProjectNameChangedEvent import org.enso.languageserver.refactoring.ProjectNameChangedEvent
import org.enso.languageserver.search.Suggestions import org.enso.languageserver.search.Suggestions
@ -20,7 +22,9 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
// add atom // add atom
system.eventStream.publish( system.eventStream.publish(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.atom)) Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.atom))
) )
) )
@ -56,7 +60,9 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
// add method // add method
system.eventStream.publish( system.eventStream.publish(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.method)) Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.method))
) )
) )
@ -102,7 +108,9 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
// add function // add function
system.eventStream.publish( system.eventStream.publish(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.function)) Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.function))
) )
) )
@ -142,7 +150,9 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
// add local // add local
system.eventStream.publish( system.eventStream.publish(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.local)) Seq(Api.SuggestionsDatabaseUpdate.Add(Suggestions.local))
) )
) )
@ -281,7 +291,9 @@ class SuggestionsHandlerEventsTest extends BaseServerTest with FlakySpec {
// remove items // remove items
system.eventStream.publish( system.eventStream.publish(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
new File("/tmp/foo"),
"",
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Remove(Suggestions.method), Api.SuggestionsDatabaseUpdate.Remove(Suggestions.method),
Api.SuggestionsDatabaseUpdate.Remove(Suggestions.function) Api.SuggestionsDatabaseUpdate.Remove(Suggestions.function)

View File

@ -28,8 +28,10 @@ import com.fasterxml.jackson.annotation.{JsonSubTypes, JsonTypeInfo}
) )
sealed trait Suggestion { sealed trait Suggestion {
def name: String def externalId: Option[Suggestion.ExternalId]
def module: String def module: String
def name: String
def returnType: String
} }
object Suggestion { object Suggestion {

View File

@ -165,12 +165,8 @@ object Runtime {
name = "runtimeServerShutDown" name = "runtimeServerShutDown"
), ),
new JsonSubTypes.Type( new JsonSubTypes.Type(
value = classOf[Api.SuggestionsDatabaseUpdateNotification], value = classOf[Api.SuggestionsDatabaseModuleUpdateNotification],
name = "suggestionsDatabaseUpdateNotification" name = "suggestionsDatabaseModuleUpdateNotification"
),
new JsonSubTypes.Type(
value = classOf[Api.SuggestionsDatabaseReIndexNotification],
name = "suggestionsDatabaseReindexNotification"
), ),
new JsonSubTypes.Type( new JsonSubTypes.Type(
value = classOf[Api.InvalidateModulesIndexRequest], value = classOf[Api.InvalidateModulesIndexRequest],
@ -179,10 +175,6 @@ object Runtime {
new JsonSubTypes.Type( new JsonSubTypes.Type(
value = classOf[Api.InvalidateModulesIndexResponse], value = classOf[Api.InvalidateModulesIndexResponse],
name = "invalidateModulesIndexResponse" name = "invalidateModulesIndexResponse"
),
new JsonSubTypes.Type(
value = classOf[Api.SuggestionsDatabaseIndexUpdateNotification],
name = "suggestionsDatabaseIndexUpdateNotification"
) )
) )
) )
@ -343,24 +335,37 @@ object Runtime {
new JsonSubTypes.Type( new JsonSubTypes.Type(
value = classOf[SuggestionsDatabaseUpdate.Remove], value = classOf[SuggestionsDatabaseUpdate.Remove],
name = "suggestionsDatabaseUpdateRemove" name = "suggestionsDatabaseUpdateRemove"
),
new JsonSubTypes.Type(
value = classOf[SuggestionsDatabaseUpdate.Clean],
name = "suggestionsDatabaseUpdateClean"
) )
) )
) )
sealed trait SuggestionsDatabaseUpdate sealed trait SuggestionsDatabaseUpdate
object SuggestionsDatabaseUpdate { object SuggestionsDatabaseUpdate {
/** Create or replace the database entry. /**
* Create or replace the database entry.
* *
* @param suggestion the new suggestion * @param suggestion the new suggestion
*/ */
case class Add(suggestion: Suggestion) extends SuggestionsDatabaseUpdate case class Add(suggestion: Suggestion) extends SuggestionsDatabaseUpdate
/** Remove the database entry. /**
* Remove the database entry.
* *
* @param suggestion the suggestion to remove * @param suggestion the suggestion to remove
*/ */
case class Remove(suggestion: Suggestion) case class Remove(suggestion: Suggestion)
extends SuggestionsDatabaseUpdate extends SuggestionsDatabaseUpdate
/**
* Remove all module entries from the database.
*
* @param module the module name
*/
case class Clean(module: String) extends SuggestionsDatabaseUpdate
} }
/** /**
@ -711,53 +716,24 @@ object Runtime {
case class ProjectRenamed(newName: String) extends ApiResponse case class ProjectRenamed(newName: String) extends ApiResponse
/** /**
* A notification about the change in the suggestions database. * A notification about the changes in the suggestions database.
* *
* @param updates the list of database updates * @param file the module file path
* @param contents the module source
* @param updates the list of suggestions extracted from module
*/ */
case class SuggestionsDatabaseUpdateNotification( case class SuggestionsDatabaseModuleUpdateNotification(
file: File,
contents: String,
updates: Seq[SuggestionsDatabaseUpdate] updates: Seq[SuggestionsDatabaseUpdate]
) extends ApiNotification ) extends ApiNotification
/**
* A notification about the re-indexed module updates.
*
* @param moduleName the name of re-indexed module
* @param updates the list of database updates
*/
case class SuggestionsDatabaseReIndexNotification(
moduleName: String,
updates: Seq[SuggestionsDatabaseUpdate.Add]
) extends ApiNotification
/** A request to invalidate the indexed flag of the modules. */ /** A request to invalidate the indexed flag of the modules. */
case class InvalidateModulesIndexRequest() extends ApiRequest case class InvalidateModulesIndexRequest() extends ApiRequest
/** Signals that the module indexes has been invalidated. */ /** Signals that the module indexes has been invalidated. */
case class InvalidateModulesIndexResponse() extends ApiResponse case class InvalidateModulesIndexResponse() extends ApiResponse
/**
* An indexed module.
*
* @param file the module file path
* @param contents the module source
* @param updates the list of suggestions extracted from module
*/
case class IndexedModule(
file: File,
contents: String,
updates: Seq[SuggestionsDatabaseUpdate.Add]
) extends ApiNotification
/**
* A notification about new indexed modules.
*
* @param updates the list of suggestions database updates
*/
case class SuggestionsDatabaseIndexUpdateNotification(
updates: Iterable[IndexedModule]
) extends ApiNotification
private lazy val mapper = { private lazy val mapper = {
val factory = new CBORFactory() val factory = new CBORFactory()
val mapper = new ObjectMapper(factory) with ScalaObjectMapper val mapper = new ObjectMapper(factory) with ScalaObjectMapper

View File

@ -92,33 +92,40 @@ class EnsureCompiledJob(protected val files: Iterable[File])
ctx.executionService.getContext.getTopScope.getModules.asScala ctx.executionService.getContext.getTopScope.getModules.asScala
ctx.executionService.getLogger ctx.executionService.getLogger
.finest(s"Modules in scope: ${modulesInScope.map(_.getName)}") .finest(s"Modules in scope: ${modulesInScope.map(_.getName)}")
val updates = modulesInScope.flatMap { module => modulesInScope.foreach { module =>
compile(module) compile(module)
analyzeModuleInScope(module) analyzeModuleInScope(module)
} }
sendIndexUpdateNotification(
Api.SuggestionsDatabaseIndexUpdateNotification(updates)
)
} }
private def analyzeImport( private def analyzeImport(
module: Module module: Module
)(implicit ctx: RuntimeContext): Unit = { )(implicit ctx: RuntimeContext): Unit = {
if (!module.isIndexed && module.getLiteralSource != null) { if (
!module.isIndexed &&
module.getLiteralSource != null &&
module.getPath != null
) {
ctx.executionService.getLogger ctx.executionService.getLogger
.finest(s"Analyzing imported ${module.getName}") .finest(s"Analyzing imported ${module.getName}")
val moduleName = module.getName.toString val moduleName = module.getName.toString
val addedSuggestions = SuggestionBuilder(module.getLiteralSource) val addedSuggestions = SuggestionBuilder(module.getLiteralSource)
.build(module.getName.toString, module.getIr) .build(module.getName.toString, module.getIr)
.filter(isSuggestionGlobal) .filter(isSuggestionGlobal)
sendReIndexNotification(moduleName, addedSuggestions) val update = Api.SuggestionsDatabaseModuleUpdateNotification(
new File(module.getPath),
module.getLiteralSource.toString,
Api.SuggestionsDatabaseUpdate.Clean(moduleName) +:
addedSuggestions.map(Api.SuggestionsDatabaseUpdate.Add)
)
sendModuleUpdate(update)
module.setIndexed(true) module.setIndexed(true)
} }
} }
private def analyzeModuleInScope(module: Module)(implicit private def analyzeModuleInScope(module: Module)(implicit
ctx: RuntimeContext ctx: RuntimeContext
): Option[Api.IndexedModule] = { ): Unit = {
try module.getSource try module.getSource
catch { catch {
case e: IOException => case e: IOException =>
@ -139,16 +146,14 @@ class EnsureCompiledJob(protected val files: Iterable[File])
val addedSuggestions = SuggestionBuilder(module.getLiteralSource) val addedSuggestions = SuggestionBuilder(module.getLiteralSource)
.build(moduleName, module.getIr) .build(moduleName, module.getIr)
.filter(isSuggestionGlobal) .filter(isSuggestionGlobal)
module.setIndexed(true) val update = Api.SuggestionsDatabaseModuleUpdateNotification(
Some(
Api.IndexedModule(
new File(module.getPath), new File(module.getPath),
module.getSource.getCharacters.toString, module.getLiteralSource.toString,
Api.SuggestionsDatabaseUpdate.Clean(moduleName) +:
addedSuggestions.map(Api.SuggestionsDatabaseUpdate.Add) addedSuggestions.map(Api.SuggestionsDatabaseUpdate.Add)
) )
) sendModuleUpdate(update)
} else { module.setIndexed(true)
None
} }
} }
@ -165,17 +170,30 @@ class EnsureCompiledJob(protected val files: Iterable[File])
val addedSuggestions = val addedSuggestions =
SuggestionBuilder(module.getLiteralSource) SuggestionBuilder(module.getLiteralSource)
.build(moduleName, module.getIr) .build(moduleName, module.getIr)
sendSuggestionsUpdateNotification( val update = Api.SuggestionsDatabaseModuleUpdateNotification(
removedSuggestions diff addedSuggestions, new File(module.getPath),
addedSuggestions diff removedSuggestions module.getLiteralSource.toString,
removedSuggestions
.diff(addedSuggestions)
.map(Api.SuggestionsDatabaseUpdate.Remove) :++
addedSuggestions
.diff(removedSuggestions)
.map(Api.SuggestionsDatabaseUpdate.Add)
) )
sendModuleUpdate(update)
} else { } else {
ctx.executionService.getLogger ctx.executionService.getLogger
.finest(s"Analyzing not-indexed module ${module.getName}") .finest(s"Analyzing not-indexed module ${module.getName}")
val addedSuggestions = val addedSuggestions =
SuggestionBuilder(module.getLiteralSource) SuggestionBuilder(module.getLiteralSource)
.build(moduleName, module.getIr) .build(moduleName, module.getIr)
sendReIndexNotification(moduleName, addedSuggestions) val update = Api.SuggestionsDatabaseModuleUpdateNotification(
new File(module.getPath),
module.getLiteralSource.toString,
Api.SuggestionsDatabaseUpdate.Clean(moduleName) +:
addedSuggestions.map(Api.SuggestionsDatabaseUpdate.Add)
)
sendModuleUpdate(update)
module.setIndexed(true) module.setIndexed(true)
} }
} }
@ -209,7 +227,7 @@ class EnsureCompiledJob(protected val files: Iterable[File])
val prevStage = module.getCompilationStage val prevStage = module.getCompilationStage
module.compileScope(ctx.executionService.getContext).getModule module.compileScope(ctx.executionService.getContext).getModule
if (prevStage != module.getCompilationStage) { if (prevStage != module.getCompilationStage) {
ctx.executionService.getLogger.finer( ctx.executionService.getLogger.finest(
s"Compiled ${module.getName} $prevStage->${module.getCompilationStage}" s"Compiled ${module.getName} $prevStage->${module.getCompilationStage}"
) )
} }
@ -289,56 +307,16 @@ class EnsureCompiledJob(protected val files: Iterable[File])
} }
/** /**
* Send notification about the suggestions database updates. * Send notification about module updates.
* *
* @param removed the list of suggestions to remove * @param payload the module update
* @param added the list of suggestions to add
* @param ctx the runtime context * @param ctx the runtime context
*/ */
private def sendSuggestionsUpdateNotification( private def sendModuleUpdate(
removed: Seq[Suggestion], payload: Api.SuggestionsDatabaseModuleUpdateNotification
added: Seq[Suggestion]
)(implicit ctx: RuntimeContext): Unit = )(implicit ctx: RuntimeContext): Unit =
if (added.nonEmpty || removed.nonEmpty) { if (payload.updates.nonEmpty) {
ctx.endpoint.sendToClient( ctx.endpoint.sendToClient(Api.Response(payload))
Api.Response(
Api.SuggestionsDatabaseUpdateNotification(
removed.map(Api.SuggestionsDatabaseUpdate.Remove) :++
added.map(Api.SuggestionsDatabaseUpdate.Add)
)
)
)
}
/**
* Send notification about the re-indexed module updates.
*
* @param moduleName the name of re-indexed module
* @param added the list of suggestions to add
* @param ctx the runtime context
*/
private def sendReIndexNotification(
moduleName: String,
added: Seq[Suggestion]
)(implicit ctx: RuntimeContext): Unit = {
ctx.endpoint.sendToClient(
Api.Response(
Api.SuggestionsDatabaseReIndexNotification(
moduleName,
added.map(Api.SuggestionsDatabaseUpdate.Add)
)
)
)
}
private def sendIndexUpdateNotification(
msg: Api.SuggestionsDatabaseIndexUpdateNotification
)(implicit
ctx: RuntimeContext
): Unit = {
if (msg.updates.nonEmpty) {
ctx.endpoint.sendToClient(Api.Response(msg))
}
} }
private def isSuggestionGlobal(suggestion: Suggestion): Boolean = private def isSuggestionGlobal(suggestion: Suggestion): Boolean =

View File

@ -454,9 +454,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -557,9 +559,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -660,9 +664,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -761,9 +767,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -862,9 +870,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -946,9 +956,11 @@ class RuntimeServerTest
context.Main.Update.mainZ(contextId), context.Main.Update.mainZ(contextId),
idMainUpdate, idMainUpdate,
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -1136,9 +1148,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -1270,9 +1284,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -1621,9 +1637,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -1877,6 +1895,7 @@ class RuntimeServerTest
Api.Response(requestId, Api.CreateContextResponse(contextId)) Api.Response(requestId, Api.CreateContextResponse(contextId))
) )
val moduleName = "Test.Main"
val code = val code =
"""from Builtins import all """from Builtins import all
| |
@ -1899,7 +1918,7 @@ class RuntimeServerTest
contextId, contextId,
Api.StackItem Api.StackItem
.ExplicitCall( .ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -1909,9 +1928,11 @@ class RuntimeServerTest
context.receive(3) should contain theSameElementsAs Seq( context.receive(3) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)), Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
"Test.Main", mainFile,
code,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -1955,6 +1976,7 @@ class RuntimeServerTest
it should "support file modification operations with attached ids" in { it should "support file modification operations with attached ids" in {
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
val moduleName = "Test.Main"
val metadata = new Metadata val metadata = new Metadata
val idMain = metadata.addItem(7, 2) val idMain = metadata.addItem(7, 2)
val code = metadata.appendToCode("main = 84") val code = metadata.appendToCode("main = 84")
@ -1987,7 +2009,7 @@ class RuntimeServerTest
contextId, contextId,
Api.StackItem Api.StackItem
.ExplicitCall( .ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2005,9 +2027,11 @@ class RuntimeServerTest
) )
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
"Test.Main", mainFile,
code,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
@ -2045,6 +2069,7 @@ class RuntimeServerTest
it should "send suggestion notifications when file is executed" in { it should "send suggestion notifications when file is executed" in {
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
val moduleName = "Test.Main"
val idMain = context.Main.metadata.addItem(33, 47) val idMain = context.Main.metadata.addItem(33, 47)
val idMainUpdate = val idMainUpdate =
Api.Response( Api.Response(
@ -2076,7 +2101,7 @@ class RuntimeServerTest
// push main // push main
val item1 = Api.StackItem.ExplicitCall( val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2090,13 +2115,15 @@ class RuntimeServerTest
context.Main.Update.mainZ(contextId), context.Main.Update.mainZ(contextId),
idMainUpdate, idMainUpdate,
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
"Test.Main", mainFile,
context.Main.code,
List( List(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
Some(idMain), Some(idMain),
"Test.Main", moduleName,
"main", "main",
List(Suggestion.Argument("this", "Any", false, false, None)), List(Suggestion.Argument("this", "Any", false, false, None)),
"Main", "Main",
@ -2107,7 +2134,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
"Test.Main", moduleName,
"foo", "foo",
List( List(
Suggestion.Argument("this", "Any", false, false, None), Suggestion.Argument("this", "Any", false, false, None),
@ -2121,7 +2148,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Local( Suggestion.Local(
Some(context.Main.idMainX), Some(context.Main.idMainX),
"Test.Main", moduleName,
"x", "x",
"Any", "Any",
Suggestion Suggestion
@ -2131,7 +2158,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Local( Suggestion.Local(
Some(context.Main.idMainY), Some(context.Main.idMainY),
"Test.Main", moduleName,
"y", "y",
"Any", "Any",
Suggestion Suggestion
@ -2141,7 +2168,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Local( Suggestion.Local(
Some(context.Main.idMainZ), Some(context.Main.idMainZ),
"Test.Main", moduleName,
"z", "z",
"Any", "Any",
Suggestion Suggestion
@ -2151,7 +2178,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Local( Suggestion.Local(
Some(context.Main.idFooY), Some(context.Main.idFooY),
"Test.Main", moduleName,
"y", "y",
"Any", "Any",
Suggestion Suggestion
@ -2161,7 +2188,7 @@ class RuntimeServerTest
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Local( Suggestion.Local(
Some(context.Main.idFooZ), Some(context.Main.idFooZ),
"Test.Main", moduleName,
"z", "z",
"Any", "Any",
Suggestion Suggestion
@ -2221,6 +2248,8 @@ class RuntimeServerTest
it should "send suggestion notifications when file is modified" in { it should "send suggestion notifications when file is modified" in {
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
val moduleName = "Test.Main"
val newline = System.lineSeparator()
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some( context.receive shouldEqual Some(
@ -2237,15 +2266,7 @@ class RuntimeServerTest
val mainFile = context.writeMain(code) val mainFile = context.writeMain(code)
// Open the new file // Open the new file
context.send( context.send(Api.Request(Api.OpenFileNotification(mainFile, code, false)))
Api.Request(
Api.OpenFileNotification(
mainFile,
code,
false
)
)
)
context.receiveNone shouldEqual None context.receiveNone shouldEqual None
context.consumeOut shouldEqual List() context.consumeOut shouldEqual List()
@ -2257,7 +2278,7 @@ class RuntimeServerTest
contextId, contextId,
Api.StackItem Api.StackItem
.ExplicitCall( .ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2267,13 +2288,15 @@ class RuntimeServerTest
context.receive(3) should contain theSameElementsAs Seq( context.receive(3) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)), Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
"Test.Main", mainFile,
code,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
"Test.Main", moduleName,
"main", "main",
Seq(Suggestion.Argument("this", "Any", false, false, None)), Seq(Suggestion.Argument("this", "Any", false, false, None)),
"Main", "Main",
@ -2300,20 +2323,29 @@ class RuntimeServerTest
), ),
TextEdit( TextEdit(
model.Range(model.Position(2, 0), model.Position(2, 0)), model.Range(model.Position(2, 0), model.Position(2, 0)),
"Number.lucky = 42\n\n" s"Number.lucky = 42$newline$newline"
) )
) )
) )
) )
) )
val codeModified =
"""from Builtins import all
|
|Number.lucky = 42
|
|main = IO.println "I'm a modified!"
|""".stripMargin
context.receive(2) should contain theSameElementsAs Seq( context.receive(2) should contain theSameElementsAs Seq(
Api.Response( Api.Response(
Api.SuggestionsDatabaseUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
mainFile,
codeModified,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
"Test.Main", moduleName,
"lucky", "lucky",
Seq(Suggestion.Argument("this", "Any", false, false, None)), Seq(Suggestion.Argument("this", "Any", false, false, None)),
"Number", "Number",
@ -2337,6 +2369,7 @@ class RuntimeServerTest
it should "recompute expressions without invalidation" in { it should "recompute expressions without invalidation" in {
val contents = context.Main.code val contents = context.Main.code
val mainFile = context.writeMain(contents) val mainFile = context.writeMain(contents)
val moduleName = "Test.Main"
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
@ -2354,7 +2387,7 @@ class RuntimeServerTest
// push main // push main
val item1 = Api.StackItem.ExplicitCall( val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2382,6 +2415,7 @@ class RuntimeServerTest
it should "recompute expressions invalidating all" in { it should "recompute expressions invalidating all" in {
val contents = context.Main.code val contents = context.Main.code
val mainFile = context.writeMain(contents) val mainFile = context.writeMain(contents)
val moduleName = "Test.Main"
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
@ -2399,7 +2433,7 @@ class RuntimeServerTest
// push main // push main
val item1 = Api.StackItem.ExplicitCall( val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2433,6 +2467,7 @@ class RuntimeServerTest
it should "recompute expressions invalidating some" in { it should "recompute expressions invalidating some" in {
val contents = context.Main.code val contents = context.Main.code
val mainFile = context.writeMain(contents) val mainFile = context.writeMain(contents)
val moduleName = "Test.Main"
val contextId = UUID.randomUUID() val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID() val requestId = UUID.randomUUID()
@ -2450,7 +2485,7 @@ class RuntimeServerTest
context.receiveNone shouldEqual None context.receiveNone shouldEqual None
// push main // push main
val item1 = Api.StackItem.ExplicitCall( val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2851,6 +2886,7 @@ class RuntimeServerTest
it should "emit visualisation update when expression is evaluated" in { it should "emit visualisation update when expression is evaluated" in {
val contents = context.Main.code val contents = context.Main.code
val mainFile = context.writeMain(context.Main.code) val mainFile = context.writeMain(context.Main.code)
val moduleName = "Test.Main"
val visualisationFile = val visualisationFile =
context.writeInSrcDir("Visualisation", context.Visualisation.code) context.writeInSrcDir("Visualisation", context.Visualisation.code)
@ -2882,7 +2918,7 @@ class RuntimeServerTest
// push main // push main
val item1 = Api.StackItem.ExplicitCall( val item1 = Api.StackItem.ExplicitCall(
Api.MethodPointer("Test.Main", "Main", "main"), Api.MethodPointer(moduleName, "Main", "main"),
None, None,
Vector() Vector()
) )
@ -2895,12 +2931,11 @@ class RuntimeServerTest
context.Main.Update.mainY(contextId), context.Main.Update.mainY(contextId),
context.Main.Update.mainZ(contextId), context.Main.Update.mainZ(contextId),
Api.Response( Api.Response(
Api.SuggestionsDatabaseIndexUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
Seq(
Api.IndexedModule(
visualisationFile, visualisationFile,
context.Visualisation.code, context.Visualisation.code,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean("Test.Visualisation"),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -2931,8 +2966,6 @@ class RuntimeServerTest
) )
) )
) )
)
)
), ),
context.executionSuccessful(contextId) context.executionSuccessful(contextId)
) )
@ -3066,12 +3099,11 @@ class RuntimeServerTest
context.Main.Update.mainY(contextId), context.Main.Update.mainY(contextId),
context.Main.Update.mainZ(contextId), context.Main.Update.mainZ(contextId),
Api.Response( Api.Response(
Api.SuggestionsDatabaseIndexUpdateNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
Seq(
Api.IndexedModule(
visualisationFile, visualisationFile,
context.Visualisation.code, context.Visualisation.code,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean("Test.Visualisation"),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,
@ -3102,13 +3134,13 @@ class RuntimeServerTest
) )
) )
) )
)
)
), ),
Api.Response( Api.Response(
Api.SuggestionsDatabaseReIndexNotification( Api.SuggestionsDatabaseModuleUpdateNotification(
moduleName, mainFile,
contents,
Seq( Seq(
Api.SuggestionsDatabaseUpdate.Clean(moduleName),
Api.SuggestionsDatabaseUpdate.Add( Api.SuggestionsDatabaseUpdate.Add(
Suggestion.Method( Suggestion.Method(
None, None,

View File

@ -112,6 +112,17 @@ class StdlibRuntimeServerTest
Iterator.continually(receive(timeout)).take(n).flatten.toList Iterator.continually(receive(timeout)).take(n).flatten.toList
} }
def receiveAllUntil(
msg: Api.Response,
timeout: Long
): List[Api.Response] = {
Iterator
.continually(receive(timeout))
.takeWhile(received => received.isDefined && received != Some(msg))
.flatten
.toList
}
def consumeOut: List[String] = { def consumeOut: List[String] = {
val result = out.toString val result = out.toString
out.reset() out.reset()
@ -169,22 +180,22 @@ class StdlibRuntimeServerTest
) )
) )
) )
val response = context.receiveN(3, timeout = 30) val response =
response.length shouldEqual 3 context.receiveAllUntil(
response should contain allOf ( context.executionSuccessful(contextId),
Api.Response(requestId, Api.PushContextResponse(contextId)), timeout = 30
context.executionSuccessful(contextId)
) )
response.collect { response should contain (
Api.Response(requestId, Api.PushContextResponse(contextId))
)
val collected = response.collect {
case Api.Response( case Api.Response(
None, None,
Api.SuggestionsDatabaseIndexUpdateNotification(xs) Api.SuggestionsDatabaseModuleUpdateNotification(_, _, xs)
) => ) =>
xs.nonEmpty shouldBe true xs.nonEmpty shouldBe true
xs.flatMap( }
_.updates.headOption.map(_.suggestion.module) collected.nonEmpty shouldBe true
) should not contain "Test.Main"
} should have length 1
context.consumeOut shouldEqual List("Hello World!") context.consumeOut shouldEqual List("Hello World!")
} }

View File

@ -28,6 +28,12 @@ trait FileVersionsRepo[F[_]] {
*/ */
def updateVersion(file: File, digest: Array[Byte]): F[Boolean] def updateVersion(file: File, digest: Array[Byte]): F[Boolean]
/** Update the versions in batch.
*
* @param versions files with corresponding digests
*/
def updateVersions(versions: Seq[(File, Array[Byte])]): F[Unit]
/** Remove the version record. /** Remove the version record.
* *
* @param file the file path * @param file the file path

View File

@ -269,6 +269,7 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
private def removeQuery(suggestion: Suggestion): DBIO[Option[Long]] = { private def removeQuery(suggestion: Suggestion): DBIO[Option[Long]] = {
val (raw, _) = toSuggestionRow(suggestion) val (raw, _) = toSuggestionRow(suggestion)
val selectQuery = Suggestions val selectQuery = Suggestions
.filter(_.module === raw.module)
.filter(_.kind === raw.kind) .filter(_.kind === raw.kind)
.filter(_.name === raw.name) .filter(_.name === raw.name)
.filter(_.scopeStartLine === raw.scopeStartLine) .filter(_.scopeStartLine === raw.scopeStartLine)
@ -306,13 +307,18 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
private def removeAllByModuleQuery( private def removeAllByModuleQuery(
modules: Seq[String] modules: Seq[String]
): DBIO[(Long, Seq[Long])] = { ): DBIO[(Long, Seq[Long])] = {
if (modules.nonEmpty) {
val selectQuery = Suggestions.filter(_.module inSet modules) val selectQuery = Suggestions.filter(_.module inSet modules)
val deleteQuery = for { for {
rows <- selectQuery.result rows <- selectQuery.result
n <- selectQuery.delete n <- selectQuery.delete
version <- if (n > 0) incrementVersionQuery else currentVersionQuery version <- if (n > 0) incrementVersionQuery else currentVersionQuery
} yield version -> rows.flatMap(_.id) } yield version -> rows.flatMap(_.id)
deleteQuery } else {
for {
version <- currentVersionQuery
} yield (version, Seq())
}
} }
/** The query to remove a list of suggestions. /** The query to remove a list of suggestions.
@ -418,6 +424,9 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
} }
/** Create a search query by the provided parameters. /** Create a search query by the provided parameters.
*
* Even if the module is specified, the response includes all available
* global symbols (atoms and method).
* *
* @param module the module name search parameter * @param module the module name search parameter
* @param selfType the selfType search parameter * @param selfType the selfType search parameter
@ -435,7 +444,8 @@ final class SqlSuggestionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
): Query[SuggestionsTable, SuggestionRow, Seq] = { ): Query[SuggestionsTable, SuggestionRow, Seq] = {
Suggestions Suggestions
.filterOpt(module) { .filterOpt(module) {
case (row, value) => row.module === value case (row, value) =>
row.scopeStartLine === ScopeColumn.EMPTY || row.module === value
} }
.filterOpt(selfType) { .filterOpt(selfType) {
case (row, value) => row.selfType === value case (row, value) => row.selfType === value

View File

@ -30,6 +30,12 @@ final class SqlVersionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
override def updateVersion(file: File, digest: Array[Byte]): Future[Boolean] = override def updateVersion(file: File, digest: Array[Byte]): Future[Boolean] =
db.run(updateVersionQuery(file, digest)) db.run(updateVersionQuery(file, digest))
/** @inheritdoc */
override def updateVersions(
versions: Seq[(File, Array[Byte])]
): Future[Unit] =
db.run(updateVersionsQuery(versions))
/** @inheritdoc */ /** @inheritdoc */
override def remove(file: File): Future[Unit] = override def remove(file: File): Future[Unit] =
db.run(removeQuery(file)) db.run(removeQuery(file))
@ -77,11 +83,10 @@ final class SqlVersionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
): DBIO[Option[Array[Byte]]] = { ): DBIO[Option[Array[Byte]]] = {
val upsertQuery = FileVersions val upsertQuery = FileVersions
.insertOrUpdate(FileVersionRow(file.toString, version)) .insertOrUpdate(FileVersionRow(file.toString, version))
val query = for { for {
version <- getVersionQuery(file) version <- getVersionQuery(file)
_ <- upsertQuery _ <- upsertQuery
} yield version } yield version
query.transactionally
} }
/** The query to update the version if it differs from the recorded version. /** The query to update the version if it differs from the recorded version.
@ -102,6 +107,23 @@ final class SqlVersionsRepo(db: SqlDatabase)(implicit ec: ExecutionContext)
else DBIO.successful(None) else DBIO.successful(None)
} yield !versionsEquals } yield !versionsEquals
/** The query to update the versions in batch.
*
* @param versions files with corresponding digests
*/
private def updateVersionsQuery(
versions: Seq[(File, Array[Byte])]
): DBIO[Unit] =
if (versions.nonEmpty) {
def upsertQuery(file: File, version: Array[Byte]) = FileVersions
.insertOrUpdate(FileVersionRow(file.toString, version))
DBIO.sequence(versions.map(Function.tupled(upsertQuery))) >>
DBIO.successful(())
} else {
DBIO.successful(())
}
/** The query to remove the version record. /** The query to remove the version record.
* *
* @param file the file path * @param file the file path

View File

@ -108,6 +108,28 @@ class FileVersionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
b4 shouldBe false b4 shouldBe false
} }
"batch update digest" taggedAs Retry in withRepo { repo =>
val file1 = new File("/foo/1")
val file2 = new File("/foo/2")
val digest0 = nextDigest()
val digest1 = nextDigest()
val digest2 = nextDigest()
val input = Seq(file1 -> digest1, file2 -> digest2)
val action =
for {
_ <- repo.setVersion(file1, digest0)
_ <- repo.updateVersions(input)
v1 <- repo.getVersion(file1)
v2 <- repo.getVersion(file2)
} yield (v1, v2)
val (v1, v2) = Await.result(action, Timeout)
v1 shouldBe a[Some[_]]
v2 shouldBe a[Some[_]]
util.Arrays.equals(v1.get, digest1) shouldBe true
util.Arrays.equals(v2.get, digest2) shouldBe true
}
"delete digest" taggedAs Retry in withRepo { repo => "delete digest" taggedAs Retry in withRepo { repo =>
val file = new File("/foo/bar") val file = new File("/foo/bar")
val digest = nextDigest() val digest = nextDigest()

View File

@ -204,6 +204,23 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
inserted should contain theSameElementsAs removed inserted should contain theSameElementsAs removed
} }
"remove all suggestions by empty module" taggedAs Retry in withRepo { repo =>
val action = for {
(_, idsIns) <- repo.insertAll(
Seq(
suggestion.atom,
suggestion.method,
suggestion.function,
suggestion.local
)
)
(_, idsRem) <- repo.removeAllByModule(Seq())
} yield (idsIns.flatten, idsRem)
val (_, removed) = Await.result(action, Timeout)
removed.isEmpty shouldBe true
}
"remove all suggestions" taggedAs Retry in withRepo { repo => "remove all suggestions" taggedAs Retry in withRepo { repo =>
val action = for { val action = for {
(_, Seq(id1, _, _, id4)) <- repo.insertAll( (_, Seq(id1, _, _, id4)) <- repo.insertAll(
@ -492,15 +509,15 @@ class SuggestionsRepoTest extends AnyWordSpec with Matchers with RetrySpec {
"search suggestion by empty module" taggedAs Retry in withRepo { repo => "search suggestion by empty module" taggedAs Retry in withRepo { repo =>
val action = for { val action = for {
_ <- repo.insert(suggestion.atom) id1 <- repo.insert(suggestion.atom)
_ <- repo.insert(suggestion.method) id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.function) _ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local) _ <- repo.insert(suggestion.local)
res <- repo.search(Some(""), None, None, None, None) res <- repo.search(Some(""), None, None, None, None)
} yield res._2 } yield (res._2, Seq(id1, id2))
val res = Await.result(action, Timeout) val (res, globals) = Await.result(action, Timeout)
res.isEmpty shouldEqual true res should contain theSameElementsAs globals.flatten
} }
"search suggestion by self type" taggedAs Retry in withRepo { repo => "search suggestion by self type" taggedAs Retry in withRepo { repo =>