Skip to content

Commit 6a1ef56

Browse files
authored
Refactor in-memory storage to external storage through SQLite (#42)
Decouple data load from app startup in preparation for remote sourcing
1 parent 3243d4e commit 6a1ef56

24 files changed

+646
-212
lines changed

README.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ The following is exhaustive list of valid properties
132132
| Which port to use. Relevant only if enable-web is `#t`
133133
| 8080
134134

135+
| db-path
136+
| Path to DB file used by SQLite
137+
| "db"
138+
135139
| spec-index
136140
| Index of definitions to load (see Identifiers definitions section)
137141
| "./types/index.scm"

build.sbt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ libraryDependencies += ("org.apache.solr" % "solr-core" % solrVersion)
1212
.exclude("org.apache.logging.log4j", "log4j-slf4j-impl")
1313
.exclude("org.apache.logging.log4j", "log4j-core")
1414

15-
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.14.1"
16-
17-
val logbackVersion = "1.2.6"
15+
val logbackVersion = "1.4.7"
1816
libraryDependencies += "ch.qos.logback" % "logback-core" % logbackVersion
1917
libraryDependencies += "ch.qos.logback" % "logback-classic" % logbackVersion
2018

@@ -26,8 +24,12 @@ libraryDependencies ++= Seq(
2624
)
2725

2826
libraryDependencies += "io.circe" %% "circe-generic" % "0.14.3"
29-
27+
libraryDependencies += "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.14.1"
28+
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.41.2.1"
29+
libraryDependencies += "com.zaxxer" % "HikariCP" % "5.0.1"
3030
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % "test"
31+
libraryDependencies += "org.tpolecat" %% "doobie-core" % "1.0.0-RC1"
32+
libraryDependencies += "org.flywaydb" % "flyway-core" % "9.17.0"
3133

3234
scalacOptions ++= Seq("-Xmax-inlines", "1000")
3335

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
create table filterset(
2+
id integer primary key,
3+
code text,
4+
name text
5+
);
6+
7+
create table filterset_library(
8+
id integer primary key,
9+
filterset_id integer,
10+
name text,
11+
12+
foreign key(filterset_id) references filterset(id)
13+
);
14+
15+
create table index_entry(
16+
id integer primary key,
17+
group_id integer,
18+
lib text,
19+
name text,
20+
signature text,
21+
description text,
22+
23+
foreign key(group_id) references index_entry(id)
24+
);
25+
26+
create table index_entry_tag(
27+
id integer primary key,
28+
index_entry_id integer,
29+
name text,
30+
31+
foreign key(index_entry_id) references index_entry(id)
32+
);
33+
34+
create table index_entry_subsignature(
35+
id integer primary key,
36+
index_entry_id integer,
37+
name text,
38+
signature text,
39+
40+
foreign key(index_entry_id) references index_entry(id)
41+
);

src/main/scala/scmindex/Config.scala

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package scmindex
22

3+
import scmindex.core.*
34
import java.nio.charset.StandardCharsets
45
import java.nio.file.{Files, Path}
56
import cats.effect.IO
67
import org.slf4j.LoggerFactory
78

89
case class Config(
910
port: Int,
11+
dbPath: String,
1012
specIndex: String,
1113
solrEmbed: Boolean,
1214
solrHome: String,
@@ -43,25 +45,31 @@ object Config {
4345
for {
4446
map <- Sexpr.alistToMap(sexpr)
4547
port <- getInt(map, "port", 8080)
48+
dbPath <- getString(map, "db-path", "db")
4649
specIndex <- getString(map, "spec-index", "types/index.scm")
4750
solrEmbed <- getBool(map, "solr-embed", true)
4851
solrHome <- getString(map, "solr-home", "./solrhome")
4952
solrUrl <- getString(map, "solr-url", "http://localhost:8983/solr")
5053
solrCore <- getString(map, "solr-core", "scmindex")
51-
filtersetIndex <- getString(map, "filterset-idnex", "filters/index.scm")
52-
} yield Config(port, specIndex, solrEmbed, solrHome, solrUrl, solrCore, filtersetIndex)
54+
filtersetIndex <- getString(map, "filterset-index", "filters/index.scm")
55+
} yield Config(port, dbPath, specIndex, solrEmbed, solrHome, solrUrl, solrCore, filtersetIndex)
5356
}
5457

55-
given SignatureLoader[Config] with {
58+
given Importer[Config] with {
5659
extension (config: Config) {
5760
def loadIndex() = config.loadLibrary(config.specIndex)
58-
5961
def loadLibrary(file: String) = IO {
6062
val path = Path.of(file)
6163
val content = Files.readString(path, StandardCharsets.UTF_8)
6264
log.info(s"Loading library ${file}")
6365
SexprParser.read(content)
6466
}
67+
def loadFiltersetIndex() = config.loadLibrary(config.filtersetIndex)
68+
def loadFilterset(file: String) = IO {
69+
val path = Path.of(file)
70+
val content = Files.readString(path, StandardCharsets.UTF_8)
71+
SexprParser.read(content)
72+
}
6573
}
6674
}
6775

src/main/scala/scmindex/Indexer.scala

Lines changed: 0 additions & 17 deletions
This file was deleted.

src/main/scala/scmindex/Main.scala

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,38 @@ import java.nio.charset.StandardCharsets
44
import java.nio.file.{Files, Path}
55
import cats.data.EitherT
66
import cats.effect.{ExitCode, IO, IOApp}
7-
import scmindex.SolrIndexer.given
7+
import scmindex.persistance.*
8+
import scmindex.persistance.SolrIndexer.given
9+
import scmindex.core.*
10+
import scmindex.web.WebController
11+
import scmindex.Config.given
812

913
object Main extends IOApp {
1014

1115
def run(args: List[String]): IO[ExitCode] = {
16+
1217
val configFile = args match {
1318
case Nil => "./config/configuration.scm"
1419
case x :: _ => x
1520
}
1621

17-
{for {
22+
val modelEitherT = for {
1823
configSexpr <- EitherT(SexprParser.readFromFile(configFile))
1924
cfg <- EitherT.fromEither(Config.readFromSexpr(configSexpr))
20-
entriesList <- EitherT(SCMIndexEntry.loadSignatures(cfg))
21-
entries = entriesList.toVector
22-
filtersetsSexpr <- EitherT(SexprParser.readFromFile(cfg.filtersetIndex))
23-
filtersetsReader = (str: String) => SexprParser.readFromFile(str)
24-
filtersets <- EitherT(Filterset.loadFiltersets(filtersetsSexpr, filtersetsReader))
25-
solrIndexer = if (cfg.solrEmbed) SolrIndexer.createEmbeddedSolrIndexer(cfg.solrHome, cfg.solrCore) else SolrIndexer.createRemoteSolrIndexer(cfg.solrUrl)
26-
_ <- EitherT(solrIndexer.index(entries).map {_ => Right(())})
27-
model = Model(solrIndexer, cfg, filtersets, entries)
28-
} yield model}.value.flatMap {
29-
case Right(model) => WebController.runServer(model)
25+
solrIndexer =
26+
if (cfg.solrEmbed)
27+
SolrIndexer.createEmbeddedSolrIndexer(cfg.solrHome, cfg.solrCore)
28+
else
29+
SolrIndexer.createRemoteSolrIndexer(cfg.solrUrl)
30+
storage <- EitherT.liftF(SqliteStorage.create(cfg.dbPath))
31+
_ <- EitherT.liftF(storage.init())
32+
service = SCMIndexService(solrIndexer, storage)
33+
_ <- EitherT(SCMIndexService.runImport(service, cfg))
34+
exitCode <- EitherT.liftF(WebController.runServer(service, cfg.port))
35+
} yield exitCode
36+
37+
modelEitherT.value.flatMap {
38+
case Right(exitCode) => IO(exitCode)
3039
case Left(err) => IO(err.printStackTrace()).as(ExitCode(1))
3140
}
3241
}

src/main/scala/scmindex/Model.scala

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/main/scala/scmindex/Filterset.scala renamed to src/main/scala/scmindex/core/Filterset.scala

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package scmindex
1+
package scmindex.core
22

33
import cats.data.EitherT
44
import cats.effect.IO
@@ -8,24 +8,26 @@ case class Filterset(code: String, name: String, libs: List[String])
88

99
object Filterset {
1010

11-
def loadFiltersets(index: Sexpr, reader: (String) => IO[Either[Exception, Sexpr]]): IO[Either[Exception, List[Filterset]]] = {
12-
Sexpr.sexprToProperList(index).map { lst =>
13-
lst
14-
.map({parseFilterset(_, reader)})
15-
.sequence
16-
.map { f =>
17-
f.partitionMap(identity) match {
18-
case (Nil, rights) => Right(rights)
19-
case (first :: _, _) => Left(Exception("Failed to parse filtersets", first))
11+
def loadFiltersets[T: Importer](importer: T): IO[Either[Exception, List[Filterset]]] = {
12+
val eitherT = for {
13+
index <- EitherT(importer.loadFiltersetIndex())
14+
indexAsList <- EitherT.fromEither(Sexpr.sexprToProperList(index))
15+
filtersets <- EitherT({
16+
indexAsList
17+
.map({parseFilterset(_, importer.loadFilterset)})
18+
.sequence
19+
.map { f =>
20+
f.partitionMap(identity) match {
21+
case (Nil, rights) => Right(rights)
22+
case (first :: _, _) => Left(Exception("Failed to parse filtersets", first))
23+
}
2024
}
21-
}
22-
} match {
23-
case Right(v) => v
24-
case Left(err) => IO(Left(err))
25-
}
25+
})
26+
} yield filtersets
27+
eitherT.value
2628
}
2729

28-
private def parseFilterset(sexpr: Sexpr, reader: (String) => IO[Either[Exception, Sexpr]]): IO[Either[Exception, Filterset]] = {
30+
def parseFilterset(sexpr: Sexpr, reader: (String) => IO[Either[Exception, Sexpr]]): IO[Either[Exception, Filterset]] = {
2931
def getStringField(map: Map[String, Sexpr], field: String): Either[Exception, String] = {
3032
map.get(field) match {
3133
case Some(SexprString(value)) => Right(value)
@@ -44,7 +46,7 @@ object Filterset {
4446
res.value
4547
}
4648

47-
private def parseFiltersetLibs(sexpr: Sexpr): Either[Exception, List[String]] = {
49+
def parseFiltersetLibs(sexpr: Sexpr): Either[Exception, List[String]] = {
4850
Sexpr.sexprToProperList(sexpr)
4951
.flatMap { terms =>
5052
val entries = terms.map {
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
package scmindex
1+
package scmindex.core
22

33
import cats.effect.IO
44

5-
trait SignatureLoader[A] {
5+
trait Importer[A] {
66

77
extension (a :A) {
88
def loadIndex(): IO[Either[Exception, Sexpr]]
99
def loadLibrary(file: String): IO[Either[Exception, Sexpr]]
10+
def loadFiltersetIndex(): IO[Either[Exception, Sexpr]]
11+
def loadFilterset(src: String): IO[Either[Exception, Sexpr]]
1012
}
1113

1214
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package scmindex.core
2+
3+
import cats.effect.IO
4+
5+
case class FacetValue(value: String, count: Int)
6+
case class IndexerResponse[R](total: Int, libs: List[FacetValue], params: List[FacetValue], returns: List[FacetValue], tags: List[FacetValue], items: List[R])
7+
8+
trait Indexer[A, ID] {
9+
extension (a: A) {
10+
def index(entries: List[(ID, SCMIndexEntry)]): IO[Unit]
11+
def query(query: String, lib: List[String], param: List[String], returns: List[String], tags: List[String], pageSize: Int, offset: Int): IO[IndexerResponse[ID]]
12+
def get(lib: String, name: String): IO[Option[ID]]
13+
def listFacetOptions(lib: List[String], facetName: String): IO[List[String]]
14+
}
15+
}

0 commit comments

Comments
 (0)