Skip to content

Commit 18ff7e8

Browse files
committed
Switch to DB based implementation
1 parent c836518 commit 18ff7e8

File tree

11 files changed

+137
-127
lines changed

11 files changed

+137
-127
lines changed

server/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies {
4848
implementation 'com.h2database:h2:1.4.200'
4949
implementation 'com.github.fwcd.ktfmt:ktfmt:b5d31d1'
5050
implementation 'com.beust:jcommander:1.78'
51+
implementation("org.xerial:sqlite-jdbc:3.41.2.1")
5152

5253
testImplementation 'org.hamcrest:hamcrest-all:1.3'
5354
testImplementation 'junit:junit:4.11'

server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package org.javacs.kt
33
import org.javacs.kt.classpath.ClassPathEntry
44
import org.javacs.kt.classpath.defaultClassPathResolver
55
import org.javacs.kt.compiler.Compiler
6-
import org.javacs.kt.storage.Storage
76
import org.javacs.kt.util.AsyncExecutor
7+
import org.jetbrains.exposed.sql.Database
88
import java.io.Closeable
99
import java.io.File
1010
import java.nio.file.FileSystems
@@ -20,7 +20,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
2020
private val javaSourcePath = mutableSetOf<Path>()
2121
private val buildScriptClassPath = mutableSetOf<Path>()
2222
val classPath = mutableSetOf<ClassPathEntry>()
23-
var storage: Storage? = null
23+
lateinit var db: Database
2424
val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile()
2525
val javaHome: String? = System.getProperty("java.home", null)
2626

@@ -40,7 +40,7 @@ class CompilerClassPath(private val config: CompilerConfiguration) : Closeable {
4040
updateJavaSourcePath: Boolean = true
4141
): Boolean {
4242
// TODO: Fetch class path and build script class path concurrently (and asynchronously)
43-
val resolver = defaultClassPathResolver(workspaceRoots, storage)
43+
val resolver = defaultClassPathResolver(workspaceRoots, db)
4444
var refreshCompiler = updateJavaSourcePath
4545

4646
if (updateClassPath) {

server/src/main/kotlin/org/javacs/kt/Configuration.kt

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package org.javacs.kt
22

33
import com.google.gson.*
44
import org.eclipse.lsp4j.InitializeParams
5-
import org.javacs.kt.storage.Storage
5+
import org.jetbrains.exposed.sql.Database
66
import java.lang.reflect.Type
77
import java.nio.file.Files
88
import java.nio.file.Path
@@ -44,28 +44,26 @@ public data class ExternalSourcesConfiguration(
4444
)
4545

4646

47-
fun parseServerConfiguration(params: InitializeParams): ServerConfiguration? {
48-
val gson = GsonBuilder().registerTypeHierarchyAdapter(Path::class.java, GsonPathConverter()).create()
49-
50-
var storage: Storage? = null
47+
fun setupServerDatabase(params: InitializeParams): Database {
48+
var db: Database? = null
49+
val dbName = "kls_database"
5150

5251
params.initializationOptions?.let { initializationOptions ->
52+
val gson = GsonBuilder().registerTypeHierarchyAdapter(Path::class.java, GsonPathConverter()).create()
5353
val options = gson.fromJson(initializationOptions as JsonElement, InitializationOptions::class.java)
5454

5555
options.storagePath?.let { storagePath ->
5656
if (Files.isDirectory(storagePath)) {
57-
storage = Storage(storagePath)
57+
db = Database.connect("jdbc:sqlite:$storagePath$dbName.db")
5858
}
5959
}
6060
}
6161

62-
return ServerConfiguration(storage)
62+
return db ?: Database.connect("jdbc:h2:mem:$dbName;DB_CLOSE_DELAY=-1", "org.h2.Driver")
6363
}
6464

6565
data class InitializationOptions(val storagePath: Path?)
6666

67-
data class ServerConfiguration(val storage: Storage?)
68-
6967
class GsonPathConverter : JsonDeserializer<Path?> {
7068

7169
@Throws(JsonParseException::class)
@@ -84,6 +82,5 @@ public data class Configuration(
8482
val completion: CompletionConfiguration = CompletionConfiguration(),
8583
val linting: LintingConfiguration = LintingConfiguration(),
8684
var indexing: IndexingConfiguration = IndexingConfiguration(),
87-
val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(),
88-
var server: ServerConfiguration? = null
85+
val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration()
8986
)

server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.javacs.kt.util.AsyncExecutor
1414
import org.javacs.kt.util.TemporaryDirectory
1515
import org.javacs.kt.util.parseURI
1616
import org.javacs.kt.externalsources.*
17+
import org.javacs.kt.index.SymbolIndex
1718
import java.io.Closeable
1819
import java.nio.file.Paths
1920
import java.util.concurrent.CompletableFuture
@@ -87,9 +88,10 @@ class KotlinLanguageServer : LanguageServer, LanguageClientAware, Closeable {
8788
serverCapabilities.documentRangeFormattingProvider = Either.forLeft(true)
8889
serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS)
8990

90-
config.server = parseServerConfiguration(params)
91+
val db = setupServerDatabase(params)
9192

92-
classPath.storage = config.server?.storage?.getSlice("classpath")
93+
sourcePath.index = SymbolIndex(db)
94+
classPath.db = db
9395

9496
val clientCapabilities = params.capabilities
9597
config.completion.snippets.enabled = clientCapabilities?.textDocument?.completion?.completionItem?.snippetSupport ?: false

server/src/main/kotlin/org/javacs/kt/SourcePath.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class SourcePath(
2929

3030
private val indexAsync = AsyncExecutor()
3131
var indexEnabled: Boolean by indexingConfig::enabled
32-
val index = SymbolIndex()
32+
lateinit var index: SymbolIndex
3333

3434
var beforeCompileCallback: () -> Unit = {}
3535

server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,12 @@ class PositionEntity(id: EntityID<Int>) : IntEntity(id) {
7878
/**
7979
* A global view of all available symbols across all packages.
8080
*/
81-
class SymbolIndex {
82-
private val db = Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver")
83-
81+
class SymbolIndex(private val db: Database) {
8482
var progressFactory: Progress.Factory = Progress.Factory.None
8583

8684
init {
8785
transaction(db) {
88-
SchemaUtils.create(Symbols, Locations, Ranges, Positions)
86+
SchemaUtils.createMissingTablesAndColumns(Symbols, Locations, Ranges, Positions)
8987
}
9088
}
9189

@@ -97,6 +95,9 @@ class SymbolIndex {
9795
progressFactory.create("Indexing").thenApplyAsync { progress ->
9896
try {
9997
transaction(db) {
98+
// Remove everything first.
99+
Symbols.deleteAll()
100+
// Add new ones.
100101
addDeclarations(allDescriptors(module, exclusions))
101102

102103
val finished = System.currentTimeMillis()

shared/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ java {
1414

1515
dependencies {
1616
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
17+
implementation "org.jetbrains.exposed:exposed-core:$exposedVersion"
18+
implementation "org.jetbrains.exposed:exposed-dao:$exposedVersion"
1719
testImplementation 'org.hamcrest:hamcrest-all:1.3'
1820
testImplementation 'junit:junit:4.11'
19-
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2'
2021
}
Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,111 @@
11
package org.javacs.kt.classpath
22

3-
import kotlinx.serialization.Serializable
4-
import org.javacs.kt.storage.SetOfPathsAsStringSerializer
5-
import org.javacs.kt.storage.Storage
3+
import org.jetbrains.exposed.dao.IntEntity
4+
import org.jetbrains.exposed.dao.IntEntityClass
5+
import org.jetbrains.exposed.dao.id.EntityID
6+
import org.jetbrains.exposed.dao.id.IntIdTable
7+
import org.jetbrains.exposed.sql.*
8+
import org.jetbrains.exposed.sql.transactions.transaction
69
import java.nio.file.Path
10+
import java.nio.file.Paths
11+
12+
private const val MAX_PATH_LENGTH = 255
13+
14+
private object ClassPathMetadataCache : IntIdTable() {
15+
val includesSources = bool("includessources")
16+
val buildFileVersion = long("buidlfileversion").nullable()
17+
}
18+
19+
private object ClassPathCacheEntry : IntIdTable() {
20+
val compiledJar = varchar("compiledjar", length = MAX_PATH_LENGTH)
21+
val sourceJar = varchar("sourcejar", length = MAX_PATH_LENGTH).nullable()
22+
}
23+
24+
private object BuildScriptClassPathCacheEntry : IntIdTable() {
25+
val jar = varchar("jar", length = MAX_PATH_LENGTH)
26+
}
27+
28+
class ClassPathMetadataCacheEntity(id: EntityID<Int>) : IntEntity(id) {
29+
companion object : IntEntityClass<ClassPathMetadataCacheEntity>(ClassPathMetadataCache)
30+
31+
var includesSources by ClassPathMetadataCache.includesSources
32+
var buildFileVersion by ClassPathMetadataCache.buildFileVersion
33+
}
34+
35+
class ClassPathCacheEntryEntity(id: EntityID<Int>) : IntEntity(id) {
36+
companion object : IntEntityClass<ClassPathCacheEntryEntity>(ClassPathCacheEntry)
37+
38+
var compiledJar by ClassPathCacheEntry.compiledJar
39+
var sourceJar by ClassPathCacheEntry.sourceJar
40+
}
41+
42+
class BuildScriptClassPathCacheEntryEntity(id: EntityID<Int>) : IntEntity(id) {
43+
companion object : IntEntityClass<BuildScriptClassPathCacheEntryEntity>(BuildScriptClassPathCacheEntry)
44+
45+
var jar by BuildScriptClassPathCacheEntry.jar
46+
}
747

848
/** A classpath resolver that caches another resolver */
9-
internal class CachedClassPathResolver(private val wrapped: ClassPathResolver, private val storage: Storage?) : ClassPathResolver {
49+
internal class CachedClassPathResolver(private val wrapped: ClassPathResolver, private val db: Database) : ClassPathResolver {
1050
override val resolverType: String get() = "Cached + ${wrapped.resolverType}"
1151

12-
private var cachedClassPath: ClasspathCache? = storage?.getObject("cachedClasspath")
13-
private var cachedBuildScriptClassPath: Set<Path>? = storage?.getObject("cachedBuildScriptClassPath", SetOfPathsAsStringSerializer)
52+
private var cachedClassPathEntries: Set<ClassPathEntry>
53+
get() = transaction(db) { ClassPathCacheEntryEntity.all() }.map {
54+
ClassPathEntry(
55+
compiledJar = Paths.get(it.compiledJar),
56+
sourceJar = if (it.sourceJar != null) Paths.get(it.sourceJar) else null
57+
)
58+
}.toSet()
59+
set(newEntries) = transaction(db) {
60+
ClassPathCacheEntry.deleteAll()
61+
newEntries.map {
62+
ClassPathCacheEntryEntity.new {
63+
compiledJar = it.compiledJar.toString()
64+
sourceJar = it.sourceJar.toString()
65+
}
66+
}
67+
}
68+
69+
private var cachedBuildScriptClassPathEntries: Set<Path>
70+
get() = transaction(db) { BuildScriptClassPathCacheEntryEntity.all() }.map { Paths.get(it.jar) }.toSet()
71+
set(newEntries) = transaction(db) {
72+
BuildScriptClassPathCacheEntry.deleteAll()
73+
newEntries.map { BuildScriptClassPathCacheEntryEntity.new { jar = it.toString() } }
74+
}
75+
76+
private var cachedClassPathMetadata
77+
get() = transaction(db) { ClassPathMetadataCacheEntity.all().map {
78+
ClasspathMetadata(
79+
includesSources = it.includesSources,
80+
buildFileVersion = it.buildFileVersion
81+
)
82+
}.firstOrNull() }
83+
set(newClassPathMetadata) = transaction(db) {
84+
ClassPathMetadataCache.deleteAll()
85+
val newClassPathMetadataRow = newClassPathMetadata ?: ClasspathMetadata()
86+
ClassPathMetadataCacheEntity.new {
87+
includesSources = newClassPathMetadataRow.includesSources
88+
buildFileVersion = newClassPathMetadataRow.buildFileVersion
89+
}
90+
}
91+
92+
init {
93+
transaction(db) {
94+
SchemaUtils.createMissingTablesAndColumns(ClassPathMetadataCache, ClassPathCacheEntry, BuildScriptClassPathCacheEntry)
95+
}
96+
}
1497

1598
override val classpath: Set<ClassPathEntry> get() {
16-
cachedClassPath?.let { if (!dependenciesChanged()) return it.classpathEntries }
99+
cachedClassPathEntries.let { if (!dependenciesChanged()) return it }
17100

18101
val newClasspath = wrapped.classpath
19-
updateClasspathCache(ClasspathCache(newClasspath, false))
102+
updateClasspathCache(newClasspath, false)
20103

21104
return newClasspath
22105
}
23106

24107
override val buildScriptClasspath: Set<Path> get() {
25-
cachedBuildScriptClassPath?.let { if (!dependenciesChanged()) return it }
108+
if (!dependenciesChanged()) return cachedBuildScriptClassPathEntries
26109

27110
val newBuildScriptClasspath = wrapped.buildScriptClasspath
28111

@@ -31,35 +114,39 @@ internal class CachedClassPathResolver(private val wrapped: ClassPathResolver, p
31114
}
32115

33116
override val classpathWithSources: Set<ClassPathEntry> get() {
34-
cachedClassPath?.let { if (!dependenciesChanged() && it.includesSources) return it.classpathEntries }
117+
cachedClassPathMetadata?.let { if (!dependenciesChanged() && it.includesSources) return cachedClassPathEntries }
35118

36119
val newClasspath = wrapped.classpathWithSources
37-
updateClasspathCache(ClasspathCache(newClasspath, true))
120+
updateClasspathCache(newClasspath, true)
38121

39122
return newClasspath
40123
}
41124

42125
override val currentBuildFileVersion: Long get() = wrapped.currentBuildFileVersion
43126

44-
private fun updateClasspathCache(newClasspathCache: ClasspathCache) {
45-
storage?.setObject("cachedClasspath", newClasspathCache)
46-
storage?.setObject("cachedBuildFileVersion", currentBuildFileVersion)
47-
cachedClassPath = newClasspathCache
127+
private fun updateClasspathCache(newClasspathEntries: Set<ClassPathEntry>, includesSources: Boolean) {
128+
transaction(db) {
129+
cachedClassPathEntries = newClasspathEntries
130+
cachedClassPathMetadata = cachedClassPathMetadata?.copy(
131+
includesSources = includesSources,
132+
buildFileVersion = currentBuildFileVersion
133+
) ?: ClasspathMetadata()
134+
}
48135
}
49136

50137
private fun updateBuildScriptClasspathCache(newClasspath: Set<Path>) {
51-
storage?.setObject("cachedBuildScriptClassPath", newClasspath, SetOfPathsAsStringSerializer)
52-
storage?.setObject("cachedBuildFileVersion", currentBuildFileVersion)
53-
cachedBuildScriptClassPath = newClasspath
138+
transaction(db) {
139+
cachedBuildScriptClassPathEntries = newClasspath
140+
cachedClassPathMetadata = cachedClassPathMetadata?.copy(buildFileVersion = currentBuildFileVersion) ?: ClasspathMetadata()
141+
}
54142
}
55143

56144
private fun dependenciesChanged(): Boolean {
57-
return storage?.getObject<Long>("cachedBuildFileVersion") ?: 0 < wrapped.currentBuildFileVersion
145+
return (cachedClassPathMetadata?.buildFileVersion ?: 0) < wrapped.currentBuildFileVersion
58146
}
59147
}
60148

61-
@Serializable
62-
private data class ClasspathCache(
63-
val classpathEntries: Set<ClassPathEntry>,
64-
val includesSources: Boolean
149+
private data class ClasspathMetadata(
150+
val includesSources: Boolean = false,
151+
val buildFileVersion: Long? = null
65152
)
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
package org.javacs.kt.classpath
22

3-
import kotlinx.serialization.Serializable
4-
import org.javacs.kt.storage.PathAsStringSerializer
53
import java.nio.file.Path
64

7-
@Serializable
85
data class ClassPathEntry(
9-
@Serializable(with = PathAsStringSerializer::class)
106
val compiledJar: Path,
11-
@Serializable(with = PathAsStringSerializer::class)
127
val sourceJar: Path? = null
138
)

shared/src/main/kotlin/org/javacs/kt/classpath/DefaultClassPathResolver.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package org.javacs.kt.classpath
22

33
import org.javacs.kt.LOG
4-
import org.javacs.kt.storage.Storage
4+
import org.jetbrains.exposed.sql.Database
55
import java.nio.file.Path
66
import java.nio.file.PathMatcher
77
import java.nio.file.FileSystems
88

9-
fun defaultClassPathResolver(workspaceRoots: Collection<Path>, storage: Storage? = null): ClassPathResolver =
9+
fun defaultClassPathResolver(workspaceRoots: Collection<Path>, db: Database): ClassPathResolver =
1010
CachedClassPathResolver(
1111
WithStdlibResolver(
1212
ShellClassPathResolver.global(workspaceRoots.firstOrNull())
1313
.or(workspaceRoots.asSequence().flatMap { workspaceResolvers(it) }.joined)
1414
).or(BackupClassPathResolver),
15-
storage
15+
db
1616
)
1717

1818
/** Searches the workspace for all files that could provide classpath info. */

0 commit comments

Comments
 (0)