In-memory suggestions (#9751)

This change replaces an sqllite-backed suggestions' repo with a simple, in-memory, one.
As `completion` functionality has been implemented completely in GUI, there is no need to support it in backend, which simplifies a lot of functionality.

Closes #9650 and #9471.

# Important Notes
Loading suggestions and sending them to GUI on startup is almost instantaneous. Previously it would take ~10s just for `Standard.Base`.
This commit is contained in:
Hubert Plociniczak 2024-04-22 13:02:17 +02:00 committed by GitHub
parent 11dda5b9bc
commit 58009b7c04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 836 additions and 2867 deletions

View File

@ -460,6 +460,7 @@ val scalaCompiler = Seq(
"org.scala-lang" % "scala-reflect" % scalacVersion,
"org.scala-lang" % "scala-compiler" % scalacVersion
)
val scalaCollectionCompatVersion = "2.8.1"
// === std-lib ================================================================
@ -503,7 +504,6 @@ val scalameterVersion = "0.19"
val scalatestVersion = "3.3.0-SNAP4"
val shapelessVersion = "2.3.10"
val slf4jVersion = JPMSUtils.slf4jVersion
val slickVersion = "3.4.1"
val sqliteVersion = "3.42.0.0"
val tikaVersion = "2.4.1"
val typesafeConfigVersion = "1.4.2"
@ -1104,8 +1104,6 @@ lazy val searcher = project
.settings(
frgaalJavaCompilerSetting,
libraryDependencies ++= jmh ++ Seq(
"com.typesafe.slick" %% "slick" % slickVersion,
"org.xerial" % "sqlite-jdbc" % sqliteVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
) ++ logbackTest
)
@ -2176,7 +2174,8 @@ lazy val `engine-runner` = project
"org.jline" % "jline" % jlineVersion,
"org.typelevel" %% "cats-core" % catsVersion,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.scala-lang.modules" %% "scala-collection-compat" % scalaCollectionCompatVersion
),
run / connectInput := true
)

View File

@ -156,11 +156,6 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.scala-logging.scala-logging_2.13-3.9.4`.
'slick_2.13', licensed under the Two-clause BSD-style license, is distributed with the engine.
The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `com.typesafe.slick.slick_2.13-3.4.1`.
'ssl-config-core_2.13', licensed under the Apache-2.0, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `com.typesafe.ssl-config-core_2.13-0.4.3`.
@ -421,9 +416,9 @@ The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `org.netbeans.api.org-openide-util-lookup-RELEASE180`.
'reactive-streams', licensed under the MIT-0, is distributed with the engine.
The license file can be found at `licenses/MIT-0`.
Copyright notices related to this dependency can be found in the directory `org.reactivestreams.reactive-streams-1.0.4`.
'reactive-streams', licensed under the CC0, is distributed with the engine.
The license file can be found at `licenses/CC0`.
Copyright notices related to this dependency can be found in the directory `org.reactivestreams.reactive-streams-1.0.3`.
'scala-collection-compat_2.13', licensed under the Apache-2.0, is distributed with the engine.
@ -471,11 +466,6 @@ The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `org.typelevel.jawn-parser_2.13-1.4.0`.
'sqlite-jdbc', licensed under the The Apache Software License, Version 2.0, is distributed with the engine.
The license information can be found along with the copyright notices.
Copyright notices related to this dependency can be found in the directory `org.xerial.sqlite-jdbc-3.42.0.0`.
'snakeyaml', licensed under the Apache License, Version 2.0, is distributed with the engine.
The license file can be found at `licenses/APACHE2.0`.
Copyright notices related to this dependency can be found in the directory `org.yaml.snakeyaml-1.33`.

View File

@ -1,25 +0,0 @@
Copyright 2011-2021 Lightbend, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -1,16 +0,0 @@
MIT No Attribution
Copyright <YEAR> <COPYRIGHT HOLDER>
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,10 @@
/************************************************************************
* Licensed under Public Domain (CC0) *
* *
* To the extent possible under law, the person who associated CC0 with *
* this code has waived all copyright and related or neighboring *
* rights to this code. *
* *
* You should have received a copy of the CC0 legalcode along with this *
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.*
************************************************************************/

View File

@ -1,2 +0,0 @@
See https://github.com/reactive-streams/reactive-streams-jvm for more information.

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -1,24 +0,0 @@
Copyright (c) 2006, David Crawshaw. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -1,7 +0,0 @@
Copyright (c) 2007 David Crawshaw <david@zentus.com>
Copyright (c) 2021 Gauthier Roebroeck <gauthier.roebroeck@gmail.com>
Copyright 2007 Taro L. Saito
Copyright 2016 Magnus Reftel

View File

@ -158,7 +158,6 @@ transport formats, please look [here](./protocol-architecture).
- [`search/getSuggestionsDatabaseVersion`](#searchgetsuggestionsdatabaseversion)
- [`search/suggestionsDatabaseUpdate`](#searchsuggestionsdatabaseupdate)
- [`search/suggestionsOrderDatabaseUpdate`](#searchsuggestionsorderdatabaseupdate)
- [`search/completion`](#searchcompletion)
- [Input/Output Operations](#inputoutput-operations)
- [`io/redirectStandardOutput`](#ioredirectstandardoutput)
- [`io/suppressStandardOutput`](#iosuppressstandardoutput)

View File

@ -10,8 +10,7 @@ import org.apache.commons.io.FileUtils;
import org.enso.languageserver.data.ProjectDirectoriesConfig;
import org.enso.languageserver.event.InitializedEvent;
import org.enso.logger.masking.MaskedPath;
import org.enso.searcher.sql.SqlDatabase;
import org.enso.searcher.sql.SqlSuggestionsRepo;
import org.enso.searcher.memory.InMemorySuggestionsRepo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.jdk.javaapi.FutureConverters;
@ -26,8 +25,7 @@ public class RepoInitialization implements InitializationComponent {
private final ProjectDirectoriesConfig projectDirectoriesConfig;
private final EventStream eventStream;
private final SqlDatabase sqlDatabase;
private final SqlSuggestionsRepo sqlSuggestionsRepo;
private final InMemorySuggestionsRepo suggestionsRepo;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@ -41,20 +39,18 @@ public class RepoInitialization implements InitializationComponent {
* @param executor the executor that runs the initialization
* @param projectDirectoriesConfig configuration of language server directories
* @param eventStream the events stream
* @param sqlDatabase the sql database
* @param sqlSuggestionsRepo the suggestions repo
* @param suggestionsRepo the suggestions repo
*/
public RepoInitialization(
Executor executor,
ProjectDirectoriesConfig projectDirectoriesConfig,
EventStream eventStream,
SqlDatabase sqlDatabase,
SqlSuggestionsRepo sqlSuggestionsRepo) {
InMemorySuggestionsRepo
suggestionsRepo) { // Java won't allow Future type constructor in SuggestionsRepo[Future]
this.executor = executor;
this.projectDirectoriesConfig = projectDirectoriesConfig;
this.eventStream = eventStream;
this.sqlDatabase = sqlDatabase;
this.sqlSuggestionsRepo = sqlSuggestionsRepo;
this.suggestionsRepo = suggestionsRepo;
}
@Override
@ -64,8 +60,7 @@ public class RepoInitialization implements InitializationComponent {
@Override
public CompletableFuture<Void> init() {
return initSqlDatabase()
.thenComposeAsync(v -> initSuggestionsRepo(), executor)
return initSuggestionsRepo()
.whenCompleteAsync(
(res, err) -> {
if (err == null && res != null) {
@ -76,33 +71,9 @@ public class RepoInitialization implements InitializationComponent {
executor);
}
private CompletableFuture<Void> initSqlDatabase() {
return CompletableFuture.runAsync(
() -> {
try {
lock.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!isInitialized) {
logger.info("Initializing sql database [{}]...", sqlDatabase);
sqlDatabase.open();
logger.info("Initialized sql database [{}].", sqlDatabase);
}
},
executor)
.whenCompleteAsync(
(res, err) -> {
if (err != null) {
logger.error("Failed to initialize sql database [{}].", sqlDatabase, err);
}
},
executor);
}
private CompletableFuture<Void> initSuggestionsRepo() {
return CompletableFuture.runAsync(
() -> logger.info("Initializing suggestions repo [{}]...", sqlDatabase), executor)
() -> logger.info("Initializing suggestions repo [{}]...", suggestionsRepo), executor)
.thenComposeAsync(
v -> {
if (!isInitialized)
@ -112,11 +83,12 @@ public class RepoInitialization implements InitializationComponent {
},
executor)
.thenRunAsync(
() -> logger.info("Initialized Suggestions repo [{}].", sqlDatabase), executor)
() -> logger.info("Initialized Suggestions repo [{}].", suggestionsRepo), executor)
.whenCompleteAsync(
(res, err) -> {
if (err != null) {
logger.error("Failed to initialize SQL suggestions repo [{}].", sqlDatabase, err);
logger.error(
"Failed to initialize SQL suggestions repo [{}].", suggestionsRepo, err);
} else {
eventStream.publish(InitializedEvent.SuggestionsRepoInitialized$.MODULE$);
}
@ -127,12 +99,9 @@ public class RepoInitialization implements InitializationComponent {
return CompletableFuture.runAsync(
() ->
logger.warn(
"Failed to initialize the suggestions database [{}].", sqlDatabase, error),
"Failed to initialize the suggestions database [{}].", suggestionsRepo, error),
executor)
.thenRunAsync(sqlDatabase::close, executor)
.thenComposeAsync(v -> clearDatabaseFile(0), executor)
.thenRunAsync(sqlDatabase::open, executor)
.thenRunAsync(() -> logger.info("Retrying database initialization."), executor)
.thenRunAsync(() -> logger.info("Retrying suggestions repo initialization."), executor)
.thenComposeAsync(v -> doInitSuggestionsRepo(), executor);
}
@ -191,6 +160,6 @@ public class RepoInitialization implements InitializationComponent {
}
private CompletionStage<Void> doInitSuggestionsRepo() {
return FutureConverters.asJava(sqlSuggestionsRepo.init()).thenAcceptAsync(res -> {}, executor);
return FutureConverters.asJava(suggestionsRepo.init()).thenAcceptAsync(res -> {}, executor);
}
}

View File

@ -51,7 +51,7 @@ import org.enso.logger.JulHandler
import org.enso.logger.akka.AkkaConverter
import org.enso.polyglot.{HostAccessFactory, RuntimeOptions, RuntimeServerInfo}
import org.enso.profiling.events.NoopEventsMonitor
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo}
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Engine
import org.graalvm.polyglot.Context
@ -63,7 +63,6 @@ import java.io.{File, PrintStream}
import java.net.URI
import java.nio.charset.StandardCharsets
import java.time.Clock
import scala.concurrent.duration._
/** A main module containing all components of the server.
@ -136,9 +135,10 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: Level) {
Sha3_224VersionCalculator
log.trace("Created Version Calculator [{}].", versionCalculator)
val sqlDatabase = SqlDatabase.inmem("memdb")
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)(system.dispatcher)
val suggestionsRepo =
new InMemorySuggestionsRepo()(
system.dispatcher
);
log.trace("Created SQL suggestions repo: [{}].", suggestionsRepo)
val idlenessMonitor =
@ -427,7 +427,6 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: Level) {
system.eventStream,
directoriesConfig,
jsonRpcProtocolFactory,
sqlDatabase,
suggestionsRepo,
context,
zioRuntime

View File

@ -14,7 +14,7 @@ import org.enso.languageserver.boot.resource.{
}
import org.enso.languageserver.data.ProjectDirectoriesConfig
import org.enso.languageserver.effect
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo}
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.graalvm.polyglot.Context
import scala.concurrent.ExecutionContextExecutor
@ -30,7 +30,6 @@ object ResourcesInitialization {
* @param directoriesConfig configuration of directories that should be created
* @param protocolFactory the JSON-RPC protocol factory
* @param suggestionsRepo the suggestions repo
* @param sqlDatabase the sql database
* @param truffleContext the runtime context
* @param runtime the runtime to run effects
* @return the initialization component
@ -39,8 +38,7 @@ object ResourcesInitialization {
eventStream: EventStream,
directoriesConfig: ProjectDirectoriesConfig,
protocolFactory: ProtocolFactory,
sqlDatabase: SqlDatabase,
suggestionsRepo: SqlSuggestionsRepo,
suggestionsRepo: InMemorySuggestionsRepo,
truffleContext: Context,
runtime: effect.Runtime
)(implicit ec: ExecutionContextExecutor): InitializationComponent = {
@ -54,7 +52,6 @@ object ResourcesInitialization {
ec,
directoriesConfig,
eventStream,
sqlDatabase,
suggestionsRepo
),
new TruffleContextInitialization(ec, truffleContext, eventStream)

View File

@ -569,8 +569,6 @@ class JsonConnectionController(
AICompletion -> ai.AICompletionHandler.props(
languageServerConfig.aiCompletionConfig
),
Completion -> search.CompletionHandler
.props(requestTimeout, suggestionsHandler),
ExecuteExpression -> ExecuteExpressionHandler
.props(rpcSession.clientId, requestTimeout, contextRegistry),
AttachVisualization -> AttachVisualizationHandler

View File

@ -1,94 +0,0 @@
package org.enso.languageserver.requesthandler.search
import akka.actor.{Actor, ActorRef, Cancellable, Props, Status}
import com.typesafe.scalalogging.LazyLogging
import org.enso.jsonrpc._
import org.enso.languageserver.requesthandler.RequestTimeout
import org.enso.languageserver.search.SearchApi.{
Completion,
SuggestionsDatabaseError
}
import org.enso.languageserver.search.{SearchFailureMapper, SearchProtocol}
import org.enso.languageserver.util.UnhandledLogging
import scala.concurrent.duration.FiniteDuration
/** A request handler for `search/completion` command.
*
* @param timeout request timeout
* @param suggestionsHandler a reference to the suggestions handler
*/
class CompletionHandler(
timeout: FiniteDuration,
suggestionsHandler: ActorRef
) extends Actor
with LazyLogging
with UnhandledLogging {
import context.dispatcher
override def receive: Receive = requestStage
private def requestStage: Receive = {
case Request(
Completion,
id,
Completion.Params(file, pos, selfType, returnType, tags, isStatic)
) =>
suggestionsHandler ! SearchProtocol.Completion(
file,
pos,
selfType,
returnType,
tags,
isStatic
)
val cancellable =
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
context.become(responseStage(id, sender(), cancellable))
}
private def responseStage(
id: Id,
replyTo: ActorRef,
cancellable: Cancellable
): Receive = {
case Status.Failure(ex) =>
logger.error("Search completion error.", ex)
replyTo ! ResponseError(Some(id), SuggestionsDatabaseError)
cancellable.cancel()
context.stop(self)
case RequestTimeout =>
logger.error("Request [{}] timed out.", id)
replyTo ! ResponseError(Some(id), Errors.RequestTimeout)
context.stop(self)
case msg: SearchProtocol.SearchFailure =>
replyTo ! ResponseError(Some(id), SearchFailureMapper.mapFailure(msg))
case SearchProtocol.CompletionResult(version, results) =>
replyTo ! ResponseResult(
Completion,
id,
Completion.Result(results, version)
)
cancellable.cancel()
context.stop(self)
}
}
object CompletionHandler {
/** Creates configuration object used to create a [[CompletionHandler]].
*
* @param timeout request timeout
* @param suggestionsHandler a reference to the suggestions handler
*/
def props(
timeout: FiniteDuration,
suggestionsHandler: ActorRef
): Props =
Props(new CompletionHandler(timeout, suggestionsHandler))
}

View File

@ -8,7 +8,6 @@ import org.enso.languageserver.filemanager.{FileSystemFailure, Path}
import org.enso.pkg.QualifiedName
import org.enso.polyglot.Suggestion
import org.enso.searcher.SuggestionEntry
import org.enso.text.editing.model.Position
object SearchProtocol {
@ -538,31 +537,6 @@ object SearchProtocol {
*/
case class GetSuggestionsDatabaseVersionResult(version: Long)
/** The completion request.
*
* @param file the edited file
* @param position the cursor position
* @param selfType filter entries matching the self type
* @param returnType filter entries matching the return type
* @param tags filter entries by suggestion type
* @param isStatic filter entries by `static` field
*/
case class Completion(
file: Path,
position: Position,
selfType: Option[String],
returnType: Option[String],
tags: Option[Seq[SuggestionKind]],
isStatic: Option[Boolean]
)
/** The reply to the [[Completion]] request.
*
* @param currentVersion current version of the suggestions database
* @param results the list of suggestion ids matched the search query
*/
case class CompletionResult(currentVersion: Long, results: Seq[SuggestionId])
/** Base trait for export statements. */
sealed trait Export {
def module: String

View File

@ -34,7 +34,6 @@ import org.enso.polyglot.data.TypeGraph
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.searcher.data.QueryResult
import org.enso.searcher.SuggestionsRepo
import org.enso.text.editing.model.Position
import scala.collection.mutable
import scala.concurrent.Future
@ -198,7 +197,7 @@ final class SuggestionsHandler(
case msg: Api.SuggestionsDatabaseSuggestionsLoadedNotification =>
logger.debug(
"Starting loading suggestions for library [{}].",
"Starting loading suggestions for library [{0}].",
msg.libraryName
)
context.become(
@ -213,8 +212,9 @@ final class SuggestionsHandler(
.onComplete {
case Success(notification) =>
logger.debug(
"Complete loading suggestions for library [{}].",
msg.libraryName
"Complete loading suggestions for library [{0}]. Has updates: {1}",
msg.libraryName,
notification.updates.nonEmpty
)
if (notification.updates.nonEmpty) {
clients.foreach { clientId =>
@ -224,7 +224,7 @@ final class SuggestionsHandler(
self ! SuggestionsHandler.SuggestionLoadingCompleted
case Failure(ex) =>
logger.error(
"Error applying suggestion updates for loaded library [{}].",
"Error applying suggestion updates for loaded library [{0}].",
msg.libraryName,
ex
)
@ -373,38 +373,6 @@ final class SuggestionsHandler(
)
)
case Completion(path, pos, selfType, returnType, tags, isStatic) =>
val selfTypes = selfType.toList.flatMap(ty => ty :: graph.getParents(ty))
getModuleName(projectName, path)
.flatMap { either =>
either.fold(
Future.successful,
module =>
suggestionsRepo
.search(
Some(module),
selfTypes,
returnType,
tags.map(_.map(SuggestionKind.toSuggestion)),
Some(toPosition(pos)),
isStatic
)
.map(CompletionResult.tupled)
)
}
.pipeTo(sender())
if (state.shouldStartBackgroundProcessing) {
runtimeConnector ! Api.Request(Api.StartBackgroundProcessing())
context.become(
initialized(
projectName,
graph,
clients,
state.backgroundProcessingStarted()
)
)
}
case FileDeletedEvent(path) =>
getModuleName(projectName, path)
.flatMap { either =>
@ -683,13 +651,6 @@ final class SuggestionsHandler(
} yield module
}
/** Convert the internal position representation to the API position.
*
* @param pos the internal position
* @return the API position
*/
private def toPosition(pos: Position): Suggestion.Position =
Suggestion.Position(pos.line, pos.character)
}
object SuggestionsHandler {

View File

@ -8,15 +8,15 @@ import org.enso.languageserver.data._
import org.enso.languageserver.event.InitializedEvent
import org.enso.languageserver.filemanager.{ContentRoot, ContentRootWithFile}
import org.enso.logger.ReportLogsOnFailure
import org.enso.searcher.sql.{SchemaVersion, SqlDatabase, SqlSuggestionsRepo}
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.enso.testkit.{FlakySpec, ToScalaFutureConversions}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import org.sqlite.SQLiteException
import java.nio.file.{Files, StandardOpenOption}
import java.nio.file.Files
import java.util.UUID
import scala.annotation.unused
import scala.concurrent.Await
import scala.concurrent.duration._
@ -41,8 +41,7 @@ class RepoInitializationSpec
"RepoInitialization" should {
"initialize repositories" in withDb {
(config, sqlDatabase, suggestionsRepo) =>
"initialize repositories" in withDb { (config, suggestionsRepo) =>
system.eventStream.subscribe(self, classOf[InitializedEvent])
val component =
@ -50,151 +49,37 @@ class RepoInitializationSpec
system.dispatcher,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
val action =
for {
_ <- component.init()
schemaVersion <- suggestionsRepo.getSchemaVersion
} yield schemaVersion
val version = Await.result(action, Timeout)
version shouldEqual SchemaVersion.CurrentVersion
val resourceInitialization = component.init()
Await.result(resourceInitialization, Timeout)
expectMsg(InitializedEvent.SuggestionsRepoInitialized)
}
"recreate suggestion database when schema version is incorrect" in withDb {
(config, sqlDatabase, suggestionsRepo) =>
(config, suggestionsRepo) =>
system.eventStream.subscribe(self, classOf[InitializedEvent])
val testSchemaVersion = Long.MaxValue
val component =
new RepoInitialization(
system.dispatcher,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
sqlDatabase.open()
val action =
for {
_ <- suggestionsRepo.init
_ <- suggestionsRepo.setSchemaVersion(testSchemaVersion)
_ <- component.init()
version <- suggestionsRepo.getSchemaVersion
} yield version
val version = Await.result(action, Timeout)
version shouldEqual SchemaVersion.CurrentVersion
} yield ()
Await.result(action, Timeout)
expectMsg(InitializedEvent.SuggestionsRepoInitialized)
}
"recreate suggestion database when schema version is empty" in withDb {
(config, sqlDatabase, suggestionsRepo) =>
system.eventStream.subscribe(self, classOf[InitializedEvent])
val component =
new RepoInitialization(
system.dispatcher,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
// initialize
val init =
for {
_ <- component.init()
version <- suggestionsRepo.getSchemaVersion
} yield version
val version1 = Await.result(init, Timeout)
version1 shouldEqual SchemaVersion.CurrentVersion
expectMsg(InitializedEvent.SuggestionsRepoInitialized)
// remove schema and re-initialize
val action =
for {
_ <- suggestionsRepo.clearSchemaVersion
_ <- component.init()
version <- suggestionsRepo.getSchemaVersion
} yield version
val version2 = Await.result(action, Timeout)
version2 shouldEqual SchemaVersion.CurrentVersion
expectMsg(InitializedEvent.SuggestionsRepoInitialized)
}
"recreate corrupted suggestion database file" taggedAs Flaky in withConfig {
config =>
// initialize
withRepos(config) { (sqlDatabase, suggestionsRepo) =>
val component =
new RepoInitialization(
system.dispatcher,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
val init =
for {
_ <- component.init()
version <- suggestionsRepo.getSchemaVersion
} yield version
val version1 = Await.result(init, Timeout)
version1 shouldEqual SchemaVersion.CurrentVersion
}
// corrupt
val bytes: Array[Byte] = Array(1, 2, 3)
Files.delete(config.directories.suggestionsDatabaseFile.toPath)
Files.write(
config.directories.suggestionsDatabaseFile.toPath,
bytes,
StandardOpenOption.CREATE
)
withRepos(config) { (sqlDatabase, suggestionsRepo) =>
sqlDatabase.open()
an[SQLiteException] should be thrownBy Await.result(
suggestionsRepo.getSchemaVersion,
Timeout
)
}
// re-initialize
withRepos(config) { (sqlDatabase, suggestionsRepo) =>
val component =
new RepoInitialization(
system.dispatcher,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
val action =
for {
_ <- component.init()
version <- suggestionsRepo.getSchemaVersion
} yield version
val version2 = Await.result(action, Timeout)
version2 shouldEqual SchemaVersion.CurrentVersion
expectMsg(InitializedEvent.SuggestionsRepoInitialized)
}
}
}
def newConfig(root: ContentRootWithFile): Config = {
@ -225,27 +110,21 @@ class RepoInitializationSpec
}
def withRepos(
config: Config
)(test: (SqlDatabase, SqlSuggestionsRepo) => Any): Unit = {
val sqlDatabase = SqlDatabase(config.directories.suggestionsDatabaseFile)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)
try test(sqlDatabase, suggestionsRepo)
finally {
sqlDatabase.close()
}
@unused config: Config
)(test: InMemorySuggestionsRepo => Any): Unit = {
val suggestionsRepo = new InMemorySuggestionsRepo()
test(suggestionsRepo)
}
def withDb(
test: (
Config,
SqlDatabase,
SqlSuggestionsRepo
InMemorySuggestionsRepo
) => Any
): Unit = {
withConfig { config =>
withRepos(config) { (sqlDatabase, suggestionsRepo) =>
test(config, sqlDatabase, suggestionsRepo)
withRepos(config) { suggestionsRepo =>
test(config, suggestionsRepo)
}
}
}

View File

@ -17,10 +17,9 @@ import org.enso.logger.ReportLogsOnFailure
import org.enso.polyglot.data.{Tree, TypeGraph}
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.{ExportedSymbol, ModuleExports, Suggestion}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo}
import org.enso.searcher.SuggestionsRepo
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.enso.testkit.RetrySpec
import org.enso.text.editing.model.Position
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@ -744,7 +743,7 @@ class SuggestionsHandlerSpec
expectMsg(SearchProtocol.InvalidateSuggestionsDatabaseResult)
}
"search entries by empty search query" taggedAs Retry in withDb {
/* "search entries by empty search query" taggedAs Retry in withDb {
(config, repo, _, _, handler) =>
val (_, inserted) =
Await.result(repo.insertAll(Suggestions.all), Timeout)
@ -914,7 +913,7 @@ class SuggestionsHandlerSpec
Seq(moduleId, typeId, consId, methodId, localId)
)
)
}
}*/
}
private def fieldUpdate(value: String): SearchProtocol.FieldUpdate[String] =
@ -1020,8 +1019,8 @@ class SuggestionsHandlerSpec
testContentRoot.toFile
)
)
val sqlDatabase = SqlDatabase(config.directories.suggestionsDatabaseFile)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)
val suggestionsRepo =
new InMemorySuggestionsRepo()
val suggestionsInit = suggestionsRepo.init
suggestionsInit.onComplete {
@ -1058,9 +1057,8 @@ class SuggestionsHandlerSpec
)
val router = TestProbe("session-router")
val connector = TestProbe("runtime-connector")
val sqlDatabase = SqlDatabase.inmem("testdb")
sqlDatabase.open()
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)
val suggestionsRepo =
new InMemorySuggestionsRepo()
val handler = newInitializedSuggestionsHandler(
config,
router,
@ -1071,7 +1069,6 @@ class SuggestionsHandlerSpec
try test(config, suggestionsRepo, router, connector, handler)
finally {
system.stop(handler)
sqlDatabase.close()
}
}

View File

@ -58,7 +58,7 @@ import org.enso.runtimeversionmanager.test.{
FakeEnvironment,
TestableThreadSafeFileLockManager
}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo}
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.enso.testkit.{EitherValue, WithTemporaryDirectory}
import org.enso.text.Sha3_224VersionCalculator
import org.scalactic.source
@ -162,8 +162,7 @@ abstract class BaseServerTest
val zioRuntime = new ExecutionContextRuntime(testExecutor)
val zioExec = ZioExec(zioRuntime)
val sqlDatabase = SqlDatabase(config.directories.suggestionsDatabaseFile)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)(system.dispatcher)
val suggestionsRepo = new InMemorySuggestionsRepo()(system.dispatcher)
private def initializationComponent =
new SequentialResourcesInitialization(
@ -178,7 +177,6 @@ abstract class BaseServerTest
initThreadPool,
config.directories,
system.eventStream,
sqlDatabase,
suggestionsRepo
)
)

View File

@ -897,18 +897,7 @@ class FileManagerTest
]
},
"name" : ".enso",
"files" : [
{
"type" : "File",
"name" : "suggestions.db",
"path" : {
"rootId" : $testContentRootId,
"segments" : [
".enso"
]
}
}
],
"files" : [],
"directories" : [
]
},

View File

@ -1,5 +1,4 @@
package org.enso.languageserver.websocket.json
import java.util.UUID
import io.circe.literal._
import org.enso.languageserver.websocket.json.{SearchJsonMessages => json}
import org.enso.logger.ReportLogsOnFailure
@ -43,93 +42,6 @@ class SuggestionsHandlerTest
""")
}
"reply to completion request" taggedAs Flaky in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "search/completion",
"id": 0,
"params": {
"file": {
"rootId": $testContentRootId,
"segments": [ "src", "Main.enso" ]
},
"position": {
"line": 0,
"character": 0
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc" : "2.0",
"id" : 0,
"result" : {
"results" : [
],
"currentVersion" : 0
}
}
""")
client.send(json"""
{ "jsonrpc": "2.0",
"method": "search/completion",
"id": 0,
"params": {
"file": {
"rootId": $testContentRootId,
"segments": [ "src", "Foo", "Main.enso" ]
},
"position": {
"line": 0,
"character": 0
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc" : "2.0",
"id" : 0,
"result" : {
"results" : [
],
"currentVersion" : 0
}
}
""")
}
"reply with error when project root not found" taggedAs Flaky in {
val client = getInitialisedWsClient()
client.send(json"""
{ "jsonrpc": "2.0",
"method": "search/completion",
"id": 0,
"params": {
"file": {
"rootId": ${UUID.randomUUID()},
"segments": [ "src", "Main.enso" ]
},
"position": {
"line": 0,
"character": 0
}
}
}
""")
client.expectJson(json"""
{ "jsonrpc" : "2.0",
"id" : 0,
"error" : {
"code" : 1001,
"message" : "Content root not found"
}
}
""")
}
}
}

View File

@ -447,14 +447,14 @@ class VcsManagerTest
"rootId" : $testContentRootId,
"segments" : [
"src",
"Bar.enso"
"Foo.enso"
]
},
{
"rootId" : $testContentRootId,
"segments" : [
"src",
"Foo.enso"
"Bar.enso"
]
}
],

View File

@ -45,6 +45,7 @@ import scala.collection.immutable.ListSet
)
)
)
@SerialVersionUID(9650L)
sealed trait Suggestion extends ToLogString {
def externalId: Option[Suggestion.ExternalID]
@ -54,6 +55,23 @@ sealed trait Suggestion extends ToLogString {
def documentation: Option[String]
def withReexports(reexports: Set[String]): Suggestion
def withReturnType(returnType: String): Suggestion
/** Creates a copy of this suggestion with the optional fields changed, if applicable.
*
* @param optExternalId externalID to modify, if non-empty
* @param optReturnType return type to modify, if non-empty and applicable
* @param optDocumentation documentation to modify, if non-empty
* @param optScope scope to modify, if non-empty and applicable
* @return a copy of this suggestion with modified fields, if applicable, unchanged suggestion otherwise
*/
def update(
optExternalId: Option[Option[Suggestion.ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Suggestion.Scope]
): Suggestion
}
object Suggestion {
@ -242,15 +260,29 @@ object Suggestion {
override def returnType: String =
module
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(module = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
optDocumentation
.map(documentation => this.copy(documentation = documentation))
.getOrElse(this)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
s"Module(module=$module,name=$name,documentation=" +
(if (shouldMask) documentation.map(_ => STUB) else documentation) +
s",reexports=$reexports)"
}
/** A type definition.
@ -276,10 +308,29 @@ object Suggestion {
) extends Suggestion
with ToLogString {
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Type(" +
@ -317,10 +368,29 @@ object Suggestion {
) extends Suggestion
with ToLogString {
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Constructor(" +
@ -389,10 +459,29 @@ object Suggestion {
@JsonIgnore
override def isStatic: Boolean = false
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Getter(" +
@ -433,10 +522,29 @@ object Suggestion {
) extends Method
with ToLogString {
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Method(" +
@ -484,10 +592,29 @@ object Suggestion {
override def name: String =
Kind.Conversion.From
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
copy(reexports = reexports)
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Conversion(" +
@ -521,10 +648,30 @@ object Suggestion {
) extends Suggestion
with ToLogString {
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
this
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
val v3 = optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
optScope.map(scope => v3.copy(scope = scope)).getOrElse(v3)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
"Function(" +
@ -556,10 +703,30 @@ object Suggestion {
documentation: Option[String]
) extends Suggestion {
/** @inheritdoc */
override def withReexports(reexports: Set[String]): Suggestion =
this
override def withReturnType(returnType: String): Suggestion =
copy(returnType = returnType)
override def update(
optExternalId: Option[Option[ExternalID]],
optReturnType: Option[String],
optDocumentation: Option[Option[String]],
optScope: Option[Scope]
): Suggestion = {
val v1 = optExternalId
.map(externalID => this.copy(externalId = externalID))
.getOrElse(this)
val v2 = optReturnType
.map(returnType => v1.copy(returnType = returnType))
.getOrElse(v1)
val v3 = optDocumentation
.map(documentation => v2.copy(documentation = documentation))
.getOrElse(v2)
optScope.map(scope => v3.copy(scope = scope)).getOrElse(v3)
}
/** @inheritdoc */
override def toLogString(shouldMask: Boolean): String =
s"Local(" +

View File

@ -1,6 +1,7 @@
package org.enso.searcher.sql;
import org.enso.polyglot.Suggestion;
import org.enso.searcher.memory.InMemorySuggestionsRepo;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
@ -10,11 +11,11 @@ import scala.collection.immutable.Seq;
import scala.concurrent.Await;
import scala.concurrent.ExecutionContext;
import scala.concurrent.duration.Duration;
import scala.jdk.CollectionConverters;
import scala.jdk.javaapi.CollectionConverters;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -35,13 +36,11 @@ public class SuggestionsRepoBenchmark {
final Seq<Suggestion.Kind> kinds = SuggestionRandom.nextKinds();
final Seq<scala.Tuple2<UUID, String>> updateInput = SuggestionRandom.nextUpdateAllInput();
SqlSuggestionsRepo repo;
InMemorySuggestionsRepo repo;
@Setup
public void setup() throws TimeoutException, InterruptedException {
var sqlDatabase = SqlDatabase.apply(dbfile.toFile(), none());
sqlDatabase.open();
repo = new SqlSuggestionsRepo(sqlDatabase, ExecutionContext.global());
repo = new InMemorySuggestionsRepo(ExecutionContext.global());
if (Files.notExists(dbfile)) {
System.out.println("initializing " + dbfile + " ...");
Await.ready(repo.init(), TIMEOUT);
@ -62,84 +61,16 @@ public class SuggestionsRepoBenchmark {
int insertBatch(int size) throws TimeoutException, InterruptedException {
Suggestion[] stubs =
Stream.generate(SuggestionRandom::nextSuggestion).limit(size).toArray(Suggestion[]::new);
return (int) Await.result(repo.insertBatchJava(stubs), TIMEOUT);
var scalaList = CollectionConverters.asScala(List.of(stubs));
var result = Await.result(repo.insertAll(scalaList.toSeq()), TIMEOUT);
return result._2.size();
}
@SuppressWarnings("unchecked")
static <T> scala.Option<T> none() {
return (scala.Option<T>) scala.None$.MODULE$;
}
@Benchmark
public Object searchBaseline() throws TimeoutException, InterruptedException {
return Await.result(
repo.search(
none(),
CollectionConverters.ListHasAsScala(new ArrayList<String>()).asScala().toSeq(),
none(),
none(),
none(),
none()),
TIMEOUT);
}
@Benchmark
public Object searchByReturnType() throws TimeoutException, InterruptedException {
return Await.result(
repo.search(
none(),
CollectionConverters.ListHasAsScala(new ArrayList<String>()).asScala().toSeq(),
scala.Some.apply("MyType"),
none(),
none(),
none()),
TIMEOUT);
}
@Benchmark
public Object searchBySelfType() throws TimeoutException, InterruptedException {
var selfTypes = new ArrayList<String>();
selfTypes.add("MyType");
return Await.result(
repo.search(
none(),
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
none(),
none(),
none(),
none()),
TIMEOUT);
}
@Benchmark
public Object searchBySelfReturnTypes() throws TimeoutException, InterruptedException {
var selfTypes = new ArrayList<String>();
selfTypes.add("SelfType");
return Await.result(
repo.search(
none(),
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
scala.Some.apply("ReturnType"),
none(),
none(),
none()),
TIMEOUT);
}
@Benchmark
public Object searchByAll() throws TimeoutException, InterruptedException {
var selfTypes = new ArrayList<String>();
selfTypes.add("SelfType");
return Await.result(
repo.search(
none(),
CollectionConverters.ListHasAsScala(selfTypes).asScala().toSeq(),
scala.Some.apply("ReturnType"),
scala.Some.apply(kinds),
none(),
scala.Some.apply(false)),
TIMEOUT);
}
@Benchmark
public Object updateByExternalId() throws TimeoutException, InterruptedException {
return Await.result(repo.updateAll(updateInput), TIMEOUT);

View File

@ -23,27 +23,6 @@ trait SuggestionsRepo[F[_]] {
*/
def getAll: F[(Long, Seq[SuggestionEntry])]
/** Search suggestion by various parameters.
*
* @param module the module name search parameter
* @param selfType the self types to search for, ordered by specificity with
* the most specific type first
* @param returnType the returnType search parameter
* @param kinds the list suggestion kinds to search
* @param position the absolute position in the text
* @param isStatic the static attribute
* @return the current database version and the list of found suggestion ids,
* ranked by specificity
*/
def search(
module: Option[String],
selfType: Seq[String],
returnType: Option[String],
kinds: Option[Seq[Suggestion.Kind]],
position: Option[Suggestion.Position],
isStatic: Option[Boolean]
): F[(Long, Seq[Long])]
/** Select the suggestion by id.
*
* @param id the id of a suggestion

View File

@ -0,0 +1,352 @@
package org.enso.searcher.memory
import org.enso.polyglot.Suggestion
import org.enso.polyglot.Suggestion.ExternalID
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.{
SuggestionAction,
SuggestionsDatabaseAction
}
import org.enso.searcher.data.QueryResult
import org.enso.searcher.sql.SuggestionRowUniqueIndex
import org.enso.searcher.{SuggestionEntry, SuggestionsRepo}
import scala.collection.mutable
import scala.concurrent.{ExecutionContext, Future}
class InMemorySuggestionsRepo(implicit ec: ExecutionContext)
extends SuggestionsRepo[Future] {
private[this] var db: mutable.Map[Long, Suggestion] = null
@volatile private[this] var version: Long = 0
@volatile private[this] var index: Long = 0
/** Initialize the repo. */
override def init: Future[Unit] = {
Future {
if (db == null) {
db = new mutable.HashMap()
version = 0
index = 1
}
}
}
/** Get current version of the repo. */
override def currentVersion: Future[Long] = {
Future {
version
}
}
/** Get all suggestions.
*
* @return the current database version and the list of suggestions
*/
override def getAll: Future[(Long, Seq[SuggestionEntry])] = {
Future {
db.synchronized {
(version, db.toSeq.map(v => SuggestionEntry(v._1, v._2)))
}
}
}
/** Select the suggestion by id.
*
* @param id the id of a suggestion
* @return return the suggestion
*/
override def select(id: Long): Future[Option[Suggestion]] = Future {
db.synchronized {
db.get(id)
}
}
/** Insert the suggestion.
*
* @param suggestion the suggestion to insert
* @return the id of an inserted suggestion
*/
override def insert(suggestion: Suggestion): Future[Option[Long]] = Future {
db.synchronized {
val isDuplicate = db.exists(_._2 == suggestion)
if (!isDuplicate) {
val i = index
index += 1
db.put(i, suggestion)
versionIncrement()
Some(i)
} else {
None
}
}
}
/** Insert a list of suggestions.
*
* @param suggestions the suggestions to insert
* @return the current database version and a list of inserted suggestion ids
*/
override def insertAll(
suggestions: Seq[Suggestion]
): Future[(Long, Seq[Long])] = Future {
val duplicatesBuilder = Vector.newBuilder[(Suggestion, Suggestion)]
val suggestionsMap: mutable.Map[SuggestionRowUniqueIndex, Suggestion] =
mutable.LinkedHashMap()
suggestions.foreach { suggestion =>
val idx = SuggestionRowUniqueIndex(suggestion)
suggestionsMap.put(idx, suggestion).foreach { duplicate =>
duplicatesBuilder.addOne((duplicate, suggestion))
}
}
val duplicates = duplicatesBuilder.result()
if (duplicates.isEmpty) {
db.synchronized {
val result = suggestions.map(s => {
val i = index
index += 1
db.put(i, s)
i
})
versionIncrement()
(version, result)
}
} else {
throw new RuntimeException("Duplicates detected: " + duplicates)
}
}
/** Apply suggestion updates.
*
* @param tree the sequence of suggestion updates
* @return the result of applying the updates
*/
override def applyTree(
tree: Seq[Api.SuggestionUpdate]
): Future[Seq[QueryResult[Api.SuggestionUpdate]]] = Future {
db.synchronized {
val result = tree.map(update =>
update.action match {
case SuggestionAction.Add() =>
// TODO: find duplicates
val i = index
index += 1
db.put(i, update.suggestion)
versionIncrement()
QueryResult(Seq(i), update)
case SuggestionAction.Modify(
externalId,
arguments,
returnType,
documentation,
scope,
reexport
) =>
if (
externalId.nonEmpty || arguments.nonEmpty || returnType.nonEmpty || documentation.nonEmpty || scope.nonEmpty || reexport.nonEmpty
) {
val suggestionInDb = db.find(_._2 == update.suggestion)
suggestionInDb match {
case None =>
QueryResult(Seq(), update)
case Some((suggestionIdx, suggestionInDb)) =>
val updatedSuggestion = suggestionInDb.update(
externalId,
returnType,
documentation,
scope
)
if (updatedSuggestion != suggestionInDb) {
versionIncrement()
}
db.put(suggestionIdx, updatedSuggestion)
QueryResult(Seq(suggestionIdx), update)
}
} else {
QueryResult(Seq(), update)
}
case SuggestionAction.Remove() =>
val sugestionKey = db.find(_._2 == update.suggestion).map(_._1)
sugestionKey.foreach { key =>
db.remove(key)
versionIncrement()
}
QueryResult(sugestionKey.toSeq, update)
}
)
result
}
}
/** Apply the sequence of actions on the database.
*
* @param actions the list of actions
* @return the result of applying the actions
*/
override def applyActions(
actions: Seq[Api.SuggestionsDatabaseAction]
): Future[Seq[QueryResult[Api.SuggestionsDatabaseAction]]] = Future {
db.synchronized {
val result = actions.map {
case act @ SuggestionsDatabaseAction.Clean(module) =>
val suggestions = db.filter(_._2.module == module)
suggestions.foreach { case (id, _) =>
db.remove(id)
}
QueryResult(
suggestions.map(_._1),
act.asInstanceOf[SuggestionsDatabaseAction]
)
}
result
}
}
/** Get the suggestions related to the export updates.
*
* @param actions the list of updates
* @return the suggestions ids associated with the export updates
*/
override def getExportedSymbols(
actions: Seq[Api.ExportsUpdate]
): Future[Seq[QueryResult[Api.ExportsUpdate]]] = Future {
db.synchronized {
actions.map { action =>
val result = action.exports.symbols.toSeq.flatMap { symbol =>
db.collectFirst {
case (id, suggestion)
if suggestion.module == symbol.module &&
suggestion.name == symbol.name && Suggestion.Kind(
suggestion
) == symbol.kind =>
id
}
}
QueryResult(result, action)
}
}
}
/** Remove the suggestion.
*
* @param suggestion the suggestion to remove
* @return the id of removed suggestion
*/
override def remove(suggestion: Suggestion): Future[Option[Long]] = Future {
db.synchronized {
val suggestionKey = db.find(_._2 == suggestion).map(_._1)
suggestionKey.foreach { id =>
db.remove(id)
versionIncrement()
}
suggestionKey
}
}
/** Remove suggestions by module names.
*
* @param modules the list of module names
* @return the current database version and a list of removed suggestion ids
*/
override def removeModules(modules: Seq[String]): Future[(Long, Seq[Long])] =
Future {
db.synchronized {
val suggestions = db.filter {
case (_, suggestion: Suggestion)
if modules.contains(suggestion.module) =>
true
case _ => false
}
suggestions.foreach { case (id, _) =>
db.remove(id)
}
condVersionIncrement(suggestions.nonEmpty)
(version, suggestions.map(_._1).toSeq)
}
}
/** Update the suggestion.
*
* @param suggestion the key suggestion
* @param externalId the external id to update
* @param returnType the return type to update
* @param documentation the documentation string to update
* @param scope the scope to update
*/
override def update(
suggestion: Suggestion,
externalId: Option[Option[ExternalID]],
returnType: Option[String],
documentation: Option[Option[String]],
scope: Option[Suggestion.Scope]
): Future[(Long, Option[Long])] = Future {
db.synchronized {
val suggestionEntry = db.find(_._2 == suggestion)
val result = suggestionEntry.flatMap { case (idx, oldSuggestion) =>
val updated =
oldSuggestion.update(externalId, returnType, documentation, scope)
if (updated != oldSuggestion) {
db.put(idx, updated)
Some(idx)
} else {
None
}
}
condVersionIncrement(result.nonEmpty)
(version, suggestionEntry.map(_._1))
}
}
/** Update a list of suggestions by external id.
*
* @param expressions pairs of external id and a return type
* @return the current database version and a list of updated suggestion ids
*/
override def updateAll(
expressions: Seq[(ExternalID, String)]
): Future[(Long, Seq[Option[Long]])] = Future {
db.synchronized {
val result = expressions.map { case (externalID, expr) =>
db.find(e => externalIDMatches(e._2.externalId, externalID)).map {
case (idx, suggestion) =>
db.put(idx, suggestion.withReturnType(expr))
idx
}
}
condVersionIncrement(result.find(_.nonEmpty).nonEmpty)
(version, result)
}
}
private def versionIncrement(): Unit = {
version += 1
}
private def condVersionIncrement(cond: => Boolean): Unit = {
if (cond) {
version += 1
}
}
private def externalIDMatches(
existing: Option[ExternalID],
externalID: ExternalID
): Boolean = {
existing
.map(e =>
e.getLeastSignificantBits == externalID.getLeastSignificantBits && e.getMostSignificantBits == externalID.getMostSignificantBits
)
.getOrElse(false)
}
/** Cleans the repo resetting the version. */
override def clean: Future[Unit] = Future {
if (db != null) {
db.synchronized {
db.clear()
}
}
}
def close(): Unit = {}
}

View File

@ -0,0 +1,79 @@
package org.enso.searcher.memory
import org.enso.polyglot.Suggestion
import org.enso.searcher.sql.{
NameColumn,
ScopeColumn,
SelfTypeColumn,
SuggestionKind,
SuggestionRow
}
/** An element of unique suggestion index.
*
* @param kind the type of a suggestion
* @param module the module name
* @param name the suggestion name
* @param selfType the self type of a suggestion
* @param scopeStartLine the line of the start position of the scope
* @param scopeStartOffset the offset of the start position of the scope
* @param scopeEndLine the line of the end position of the scope
* @param scopeEndOffset the offset of the end position of the scope
*/
final case class SuggestionRowUniqueIndex(
kind: Suggestion.Kind,
module: String,
name: String,
selfType: String,
scopeStartLine: Int,
scopeStartOffset: Int,
scopeEndLine: Int,
scopeEndOffset: Int
)
object SuggestionRowUniqueIndex {
/** Create an index element from the provided suggestion.
*
* @param suggestion the suggestion
* @return an index element representing the provided suggestion
*/
def apply(suggestion: Suggestion): SuggestionRowUniqueIndex = {
val scope = Suggestion.Scope(suggestion)
val suggestionName = suggestion match {
case conversion: Suggestion.Conversion =>
NameColumn.conversionMethodName(
conversion.selfType,
conversion.returnType
)
case _ => suggestion.name
}
new SuggestionRowUniqueIndex(
Suggestion.Kind(suggestion),
suggestion.module,
suggestionName,
Suggestion.SelfType(suggestion).getOrElse(SelfTypeColumn.EMPTY),
scope.map(_.start.line).getOrElse(ScopeColumn.EMPTY),
scope.map(_.start.character).getOrElse(ScopeColumn.EMPTY),
scope.map(_.end.line).getOrElse(ScopeColumn.EMPTY),
scope.map(_.end.character).getOrElse(ScopeColumn.EMPTY)
)
}
/** Create an index element from the provided suggestion row.
*
* @param row the suggestion row
* @return an index element representing the provided suggestion row
*/
def apply(row: SuggestionRow): SuggestionRowUniqueIndex =
new SuggestionRowUniqueIndex(
SuggestionKind.toSuggestion(row.kind),
row.module,
row.name,
row.selfType,
row.scopeStartLine,
row.scopeStartOffset,
row.scopeEndLine,
row.scopeEndOffset
)
}

View File

@ -1,11 +0,0 @@
package org.enso.searcher.sql
/** An error indicating that the database has invalid schema version.
*
* @param version the database schema version.
*/
class InvalidSchemaVersion(val version: Long)
extends RuntimeException(
s"Database schema version '$version' is different from the application " +
s"schema version '${SchemaVersion.CurrentVersion}'."
)

View File

@ -1,108 +0,0 @@
package org.enso.searcher.sql
import java.io.File
import com.typesafe.config.{Config, ConfigFactory}
import org.enso.searcher.Database
import org.enso.searcher.sqlite.LockingMode
import slick.dbio.DBIO
import slick.jdbc.SQLiteProfile
import scala.concurrent.Future
/** Ths SQL database that runs Slick [[DBIO]] queries resulting in a [[Future]].
*
* @param config the configuration
*/
final class SqlDatabase(config: Option[Config] = None)
extends Database[DBIO, Future] {
private var db: SQLiteProfile.backend.Database = _
/** @inheritdoc */
override def run[A](query: DBIO[A]): Future[A] =
db.run(query)
/** @inheritdoc */
override def open(): Unit =
this.synchronized {
if (db eq null) {
db = SQLiteProfile.backend.Database
.forConfig(
SqlDatabase.configPath,
config.orNull,
classLoader = getClass.getClassLoader
)
}
}
/** @inheritdoc */
override def close(): Unit =
this.synchronized {
if (db ne null) {
db.close()
db = null
}
}
}
object SqlDatabase {
private val configPath: String =
"searcher.db"
/** Create in-memory [[SqlDatabase]] instance.
*
* @return new sql database instance
*/
def inmem(name: String): SqlDatabase =
fromUrl(inmemUrl(name))
/** Create [[SqlDatabase]] instance.
*
* @param filename the database file path
* @param maybeLockingMode the locking mode
* @return new sql database instance
*/
def apply(
filename: File,
maybeLockingMode: Option[LockingMode] = None
): SqlDatabase =
fromUrl(jdbcUrl(filename.toString, maybeLockingMode))
/** Create [[SqlDatabase]] instance.
*
* @param url the database url
* @return new sql database instance
*/
def fromUrl(url: String): SqlDatabase = {
val config = ConfigFactory
.parseString(s"""$configPath.url = "$url"""")
.withFallback(
ConfigFactory.load(getClass.getClassLoader)
)
new SqlDatabase(Some(config))
}
/** Create JDBC URL for in-memory database. */
private def inmemUrl(name: String): String =
s"jdbc:sqlite:file:$name?mode=memory&cache=shared"
/** Create JDBC URL from the file path. */
private def jdbcUrl(
filename: String,
maybeLockingMode: Option[LockingMode]
): String = {
maybeLockingMode match {
case None =>
s"jdbc:sqlite:${escapePath(filename)}"
case Some(lockingMode) =>
s"jdbc:sqlite:file:${escapePath(filename)}?vfs=${lockingMode.name}"
}
}
/** Escape Windows path. */
private def escapePath(path: String): String =
path.replace("\\", "\\\\")
}

View File

@ -1,9 +1,6 @@
package org.enso.searcher.sql
import org.enso.polyglot.Suggestion
import slick.jdbc.SQLiteProfile.api._
import scala.annotation.nowarn
/** A row in the suggestions table.
*
@ -41,18 +38,6 @@ case class SuggestionRow(
documentation: Option[String]
)
/** A row in the suggestions_version table.
*
* @param id the row id
*/
case class SuggestionsVersionRow(id: Option[Long])
/** A row in the schema_version table.
*
* @param id the row id
*/
case class SchemaVersionRow(id: Option[Long])
/** The type of a suggestion. */
object SuggestionKind {
@ -118,77 +103,6 @@ object NameColumn {
}
/** The schema of the suggestions table. */
@nowarn("msg=multiarg infix syntax")
final class SuggestionsTable(tag: Tag)
extends Table[SuggestionRow](tag, "suggestions") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def externalIdLeast = column[Option[Long]]("external_id_least")
def externalIdMost = column[Option[Long]]("external_id_most")
def kind = column[Byte]("kind")
def module = column[String]("module")
def name = column[String]("name")
def selfType = column[String]("self_type")
def returnType = column[String]("return_type")
def parentType = column[Option[String]]("parent_type")
def isStatic = column[Boolean]("is_static")
def scopeStartLine =
column[Int]("scope_start_line", O.Default(ScopeColumn.EMPTY))
def scopeStartOffset =
column[Int]("scope_start_offset", O.Default(ScopeColumn.EMPTY))
def scopeEndLine =
column[Int]("scope_end_line", O.Default(ScopeColumn.EMPTY))
def scopeEndOffset =
column[Int]("scope_end_offset", O.Default(ScopeColumn.EMPTY))
def documentation = column[Option[String]]("documentation")
def * =
(
id.?,
externalIdLeast,
externalIdMost,
kind,
module,
name,
selfType,
returnType,
parentType,
isStatic,
scopeStartLine,
scopeStartOffset,
scopeEndLine,
scopeEndOffset,
documentation
) <>
(SuggestionRow.tupled, SuggestionRow.unapply)
def moduleIdx = index("suggestions_module_idx", module)
def name_idx = index("suggestions_name_idx", name)
def selfTypeIdx = index("suggestions_self_type_idx", selfType)
def returnTypeIdx = index("suggestions_return_type_idx", returnType)
def externalIdIdx =
index("suggestions_external_id_idx", (externalIdLeast, externalIdMost))
// NOTE: unique index should not contain nullable columns because SQLite
// teats NULLs as distinct values.
def uniqueIdx =
index(
"suggestions_unique_idx",
(
kind,
module,
name,
selfType,
scopeStartLine,
scopeStartOffset,
scopeEndLine,
scopeEndOffset
),
unique = true
)
}
/** An element of unique suggestion index.
*
* @param kind the type of a suggestion
@ -257,33 +171,3 @@ object SuggestionRowUniqueIndex {
row.scopeEndOffset
)
}
/** The schema of the suggestions_version table. */
@nowarn("msg=multiarg infix syntax")
final class SuggestionsVersionTable(tag: Tag)
extends Table[SuggestionsVersionRow](tag, "suggestions_version") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def * = id.? <> (SuggestionsVersionRow.apply, SuggestionsVersionRow.unapply)
}
/** The schema of the schema_version table. */
@nowarn("msg=multiarg infix syntax")
final class SchemaVersionTable(tag: Tag)
extends Table[SchemaVersionRow](tag, "schema_version") {
def id = column[Long]("id", O.PrimaryKey)
def * = id.? <> (SchemaVersionRow.apply, SchemaVersionRow.unapply)
}
object Suggestions extends TableQuery(new SuggestionsTable(_))
object SuggestionsVersion extends TableQuery(new SuggestionsVersionTable(_))
object SchemaVersion extends TableQuery(new SchemaVersionTable(_)) {
/** The current schema version. */
val CurrentVersion: Long = 12
}

View File

@ -4,7 +4,7 @@ import org.enso.polyglot.{ExportedSymbol, ModuleExports, Suggestion}
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.searcher.SuggestionEntry
import org.enso.searcher.data.QueryResult
import org.enso.searcher.sql.SqlSuggestionsRepo.UniqueConstraintViolatedError
import org.enso.searcher.memory.InMemorySuggestionsRepo
import org.enso.searcher.sql.equality.SuggestionsEquality
import org.enso.testkit.RetrySpec
import org.scalactic.TripleEqualsSupport
@ -13,7 +13,6 @@ import org.scalatest.wordspec.AnyWordSpec
import java.nio.file.{Files, Path}
import java.util.UUID
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
@ -38,11 +37,8 @@ class SuggestionsRepoTest
tmp
}
def withRepo(test: SqlSuggestionsRepo => Any): Any = {
val tmpdb = Files.createTempFile(tmpdir, "suggestions-repo", ".db")
val sqlDatabase = SqlDatabase(tmpdb.toFile)
sqlDatabase.open()
val repo = new SqlSuggestionsRepo(sqlDatabase)
def withRepo(test: InMemorySuggestionsRepo => Any): Any = {
val repo = new InMemorySuggestionsRepo()
Await.ready(repo.init, Timeout)
try test(repo)
finally {
@ -57,26 +53,13 @@ class SuggestionsRepoTest
Await.result(repo.init, Timeout)
}
"check the schema version when init" taggedAs Retry in withRepo { repo =>
val wrongSchemaVersion = Long.MinValue
val action =
for {
version <- repo.setSchemaVersion(wrongSchemaVersion)
_ <- repo.init
} yield version
val thrown =
the[InvalidSchemaVersion] thrownBy Await.result(action, Timeout)
thrown.version shouldEqual wrongSchemaVersion
}
"insert all suggestions" taggedAs Retry in withRepo { repo =>
val action =
for {
v1 <- repo.currentVersion
(v2, ids) <- repo.insertAll(suggestion.all)
all <- repo.selectAllSuggestions
} yield (ids, all, v1, v2)
all <- repo.getAll
} yield (ids, all._2, v1, v2)
val (ids, entries, v1, v2) = Await.result(action, Timeout)
val expectedEntries = ids.zip(suggestion.all).map(SuggestionEntry.tupled)
@ -102,7 +85,7 @@ class SuggestionsRepoTest
_ <- repo.insertAll(Seq(suggestion.local, suggestion.local))
} yield ()
an[UniqueConstraintViolatedError] should be thrownBy Await.result(
an[RuntimeException] should be thrownBy Await.result(
action,
Timeout
)
@ -708,600 +691,6 @@ class SuggestionsRepoTest
QueryResult(Seq(ids(3)), updates(1))
)
}
"search suggestion by empty query" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, None, None, None)
} yield res._2
val res = Await.result(action, Timeout)
res.isEmpty shouldEqual true
}
"search suggestion by module" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <- repo.search(
Some("local.Test.Main"),
Seq(),
None,
None,
None,
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, id6, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(
id0,
id1,
id2,
id3,
id4,
id5,
id6
).flatten
}
"search suggestion by empty module" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(Some(""), Seq(), None, None, None, None)
} yield (res._2, Seq(id0, id1, id2, id3, id4))
val (res, globals) = Await.result(action, Timeout)
res should contain theSameElementsAs globals.flatten
}
"search suggestion by self type" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq("local.Test.Main"), None, None, None, None)
} yield (id2, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by self type excluding constructors" taggedAs Retry in withRepo {
repo =>
val constructorSelfType =
Suggestion.SelfType(suggestion.constructor).toSeq
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(
None,
constructorSelfType,
None,
None,
None,
None
)
} yield res._2
val res = Await.result(action, Timeout)
res.isEmpty shouldEqual true
}
"search suggestion by return type" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
id3 <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq(),
Some("local.Test.Main.MyType"),
None,
None,
None
)
} yield (id3, id4, res._2)
val (id1, id2, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2).flatten
}
"search suggestion by kind" taggedAs Retry in withRepo { repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
id1 <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, Some(kinds), None, None)
} yield (id1, id4, res._2)
val (id1, id2, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2).flatten
}
"search suggestion by empty kinds" taggedAs Retry in withRepo { repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(None, Seq(), None, Some(Seq()), None, None)
} yield res._2
val res = Await.result(action, Timeout)
res.isEmpty shouldEqual true
}
"search suggestion outside of scope" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(99, 42)),
None
)
} yield (id0, id1, id2, id3, id4, res._2)
val (id0, id1, id2, id3, id4, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id0, id1, id2, id3, id4).flatten
}
"search suggestion by scope begin" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(1, 5)),
None
)
} yield (id0, id1, id2, id3, id4, id5, res._2)
val (id0, id1, id2, id3, id4, id5, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(
id0,
id1,
id2,
id3,
id4,
id5
).flatten
}
"search suggestion by scope end" taggedAs Retry in withRepo { repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(6, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, id6, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(
id0,
id1,
id2,
id3,
id4,
id5,
id6
).flatten
}
"search suggestion inside the function scope" taggedAs Retry in withRepo {
repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(2, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, _, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(
id0,
id1,
id2,
id3,
id4,
id5
).flatten
}
"search suggestion inside the local scope" taggedAs Retry in withRepo {
repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.conversion)
id5 <- repo.insert(suggestion.function)
id6 <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
Some(Suggestion.Position(4, 0)),
None
)
} yield (id0, id1, id2, id3, id4, id5, id6, res._2)
val (id0, id1, id2, id3, id4, id5, id6, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(
id0,
id1,
id2,
id3,
id4,
id5,
id6
).flatten
}
"search suggestion by the static attribute" taggedAs Retry in withRepo {
repo =>
val action = for {
id0 <- repo.insert(suggestion.module)
id1 <- repo.insert(suggestion.tpe)
id2 <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.instanceMethod)
id5 <- repo.insert(suggestion.conversion)
id6 <- repo.insert(suggestion.function)
id7 <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(),
None,
None,
None,
Some(true)
)
} yield (Seq(id0, id1, id2, id3, id5, id6, id7), res._2)
val (ids, res) = Await.result(action, Timeout)
res should contain theSameElementsAs ids.flatten
}
"search suggestion by module and self type" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
id2 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(
Some("local.Test.Main"),
Seq("local.Test.Main"),
None,
None,
None,
None
)
} yield (id2, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by self type and static attribute" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
id3 <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(suggestion.method.selfType),
None,
None,
None,
Some(true)
)
} yield (id3, res._2)
val (id3, res) =
Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id3).flatten
}
"search suggestion by self-type and non-static attribute" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
id4 <- repo.insert(suggestion.instanceMethod)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <-
repo.search(
None,
Seq(suggestion.instanceMethod.selfType),
None,
None,
None,
Some(false)
)
} yield (Seq(id4), res._2)
val (ids, res) = Await.result(action, Timeout)
res should contain theSameElementsAs ids.flatten
}
"search suggestion by return type and kind" taggedAs Retry in withRepo {
repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
None,
None
)
} yield (id4, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by return type and scope" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
id3 <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq(),
Some("local.Test.Main.MyType"),
None,
Some(Suggestion.Position(6, 0)),
None
)
} yield (id3, id4, res._2)
val (id1, id2, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id1, id2).flatten
}
"search suggestion by kind and scope" taggedAs Retry in withRepo { repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
id1 <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq(),
None,
Some(kinds),
Some(Suggestion.Position(99, 1)),
None
)
} yield (id1, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by self and return types" taggedAs Retry in withRepo {
repo =>
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq("local.Test.Main"),
Some("local.Test.Main.MyType"),
None,
None,
None
)
} yield res._2
val res = Await.result(action, Timeout)
res.isEmpty shouldEqual true
}
"search suggestion by module, return type and kind" taggedAs Retry in withRepo {
repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(
Some("local.Test.Main"),
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
None,
None
)
} yield (id4, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by return type, kind and scope" taggedAs Retry in withRepo {
repo =>
val kinds = Seq(Suggestion.Kind.Constructor, Suggestion.Kind.Local)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
id4 <- repo.insert(suggestion.local)
res <- repo.search(
None,
Seq(),
Some("local.Test.Main.MyType"),
Some(kinds),
Some(Suggestion.Position(6, 0)),
None
)
} yield (id4, res._2)
val (id, res) = Await.result(action, Timeout)
res should contain theSameElementsAs Seq(id).flatten
}
"search suggestion by all parameters" taggedAs Retry in withRepo { repo =>
val kinds = Seq(
Suggestion.Kind.Constructor,
Suggestion.Kind.Method,
Suggestion.Kind.Function
)
val action = for {
_ <- repo.insert(suggestion.module)
_ <- repo.insert(suggestion.tpe)
_ <- repo.insert(suggestion.constructor)
_ <- repo.insert(suggestion.method)
_ <- repo.insert(suggestion.conversion)
_ <- repo.insert(suggestion.function)
_ <- repo.insert(suggestion.local)
res <- repo.search(
Some("local.Test.Main"),
Seq("local.Test.Main"),
Some("local.Test.Main.MyType"),
Some(kinds),
Some(Suggestion.Position(42, 0)),
Some(true)
)
} yield res._2
val res = Await.result(action, Timeout)
res.isEmpty shouldEqual true
}
}
object suggestion {

View File

@ -1,25 +0,0 @@
Copyright 2011-2021 Lightbend, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1 @@
this code has waived all copyright and related or neighboring *

View File

@ -1 +0,0 @@
See https://github.com/reactive-streams/reactive-streams-jvm for more information.

View File

@ -1,5 +0,0 @@
Copyright 2008 Taro L. Saito
Copyright 2009 Taro L. Saito
Copyright 2010 Taro L. Saito
copyright notice and this permission notice appear in all copies.
this work for additional information regarding copyright ownership.

View File

@ -1,4 +0,0 @@
Copyright (c) 2007 David Crawshaw <david@zentus.com>
Copyright (c) 2021 Gauthier Roebroeck <gauthier.roebroeck@gmail.com>
Copyright 2007 Taro L. Saito
Copyright 2016 Magnus Reftel

View File

@ -1,2 +0,0 @@
META-INF/maven/org.xerial/sqlite-jdbc/LICENSE
META-INF/maven/org.xerial/sqlite-jdbc/LICENSE.zentus

View File

@ -1,3 +1,3 @@
04F26D14EEE87D86068545C263A1459FF8E8E7AB7E5AB92AD65A35A61E3AD3F2
27B287816C14571C5032AE4D27CC680B74E99DB22BA2134FE24BB8141A53426D
40308641443E112D3668A68FF82D969D26B6D07D48724AFE5119B838FCF36639
119423CD9496FC62BB306497C105ED326E25317490487D577212D1230F6D2D40
0