From 8e074b4c0a246d84f446233b0575ca7ae5b701f9 Mon Sep 17 00:00:00 2001 From: Thanh Le Date: Wed, 8 May 2024 08:24:49 +0700 Subject: [PATCH] Cross compiling core module with scala 3 --- .scalafmt.conf | 16 +++++-- build.sbt | 12 ++++-- modules/core/src/main/scala/ESClient.scala | 42 ++++++++++++------- modules/core/src/main/scala/Queryable.scala | 16 +++---- modules/core/src/main/scala/Range.scala | 8 ++-- modules/core/src/main/scala/forum.scala | 17 ++++---- modules/core/src/main/scala/game.scala | 35 +++++++++------- modules/core/src/main/scala/model.scala | 2 +- modules/core/src/main/scala/package.scala | 19 +++------ modules/core/src/main/scala/study.scala | 19 +++++---- modules/core/src/main/scala/team.scala | 13 +++--- play/app/src/main/scala/AppLoader.scala | 2 +- play/app/src/main/scala/Chronometer.scala | 6 +-- play/app/src/main/scala/QueryParser.scala | 3 +- .../src/main/scala/controllers/WebApi.scala | 25 +++++------ 15 files changed, 133 insertions(+), 102 deletions(-) diff --git a/.scalafmt.conf b/.scalafmt.conf index 6fb57bc6..702f9f99 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,7 +1,17 @@ -version = "3.5.8" +version = "3.8.1" +runner.dialect = scala213 + align.preset = more maxColumn = 110 spaces.inImportCurlyBraces = true -rewrite.rules = [SortImports, RedundantParens, SortModifiers] +rewrite.rules = [SortModifiers] rewrite.redundantBraces.stringInterpolation = true -runner.dialect = scala213 +rewrite.rules = [AvoidInfix] +fileOverride { + "glob:**/build.sbt" { + runner.dialect = scala213 + } + "glob:**/project/**" { + runner.dialect = scala213 + } +} diff --git a/build.sbt b/build.sbt index 64eaec4c..fdc25c43 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,13 @@ +import org.typelevel.scalacoptions.ScalacOption import org.typelevel.scalacoptions.ScalacOptions +lazy val scala213 = "2.13.14" +lazy val scala3 = "3.4.1" +lazy val supportedScalaVersions = List(scala213, scala3) + inThisBuild( Seq( - scalaVersion := "2.13.14", + scalaVersion := scala213, versionScheme := Some("early-semver"), version := "3.0.0-SNAPSHOT", run / fork := true, @@ -21,9 +26,10 @@ lazy val core = project .in(file("modules/core")) .settings( commonSettings, - name := "core", + crossScalaVersions := supportedScalaVersions, + tpolecatScalacOptions ++= Set(ScalacOptions.source3), + name := "lila-search-core", libraryDependencies ++= Seq( - "com.github.ornicar" %% "scalalib" % "7.1.0", "com.sksamuel.elastic4s" %% "elastic4s-client-esjava" % "8.11.5", "joda-time" % "joda-time" % "2.12.7" ) diff --git a/modules/core/src/main/scala/ESClient.scala b/modules/core/src/main/scala/ESClient.scala index 3ad15529..cebe3afd 100644 --- a/modules/core/src/main/scala/ESClient.scala +++ b/modules/core/src/main/scala/ESClient.scala @@ -17,38 +17,44 @@ final class ESClient(client: ElasticClient)(implicit ec: ExecutionContext) { response.fold[Future[A]](Future.failed(new Exception(response.error.reason)))(Future.successful) def search[A](index: Index, query: A, from: From, size: Size)(implicit q: Queryable[A]) = - client execute { - q.searchDef(query)(from, size)(index) - } flatMap toResult map SearchResponse.apply + client + .execute { + q.searchDef(query)(from, size)(index) + } + .flatMap(toResult) + .map(SearchResponse.apply) def count[A](index: Index, query: A)(implicit q: Queryable[A]) = - client execute { - q.countDef(query)(index) - } flatMap toResult map CountResponse.apply + client + .execute { + q.countDef(query)(index) + } + .flatMap(toResult) + .map(CountResponse.apply) def store(index: Index, id: Id, obj: JsonObject) = - client execute { - indexInto(index.name) source obj.json id id.value + client.execute { + indexInto(index.name).source(obj.json).id(id.value) } def storeBulk(index: Index, objs: List[(String, JsonObject)]) = if (objs.isEmpty) funit else - client execute { + client.execute { ElasticDsl.bulk { objs.map { case (id, obj) => - indexInto(index.name) source obj.json id id + indexInto(index.name).source(obj.json).id(id) } } } def deleteOne(index: Index, id: Id) = - client execute { + client.execute { deleteById(index.toES, id.value) } def deleteMany(index: Index, ids: List[Id]) = - client execute { + client.execute { ElasticDsl.bulk { ids.map { id => deleteById(index.toES, id.value) @@ -58,15 +64,19 @@ final class ESClient(client: ElasticClient)(implicit ec: ExecutionContext) { def putMapping(index: Index, fields: Seq[ElasticField]) = dropIndex(index) >> client.execute { - createIndex(index.name).mapping( - properties(fields) source false // all false - ) shards 5 replicas 0 refreshInterval Which.refreshInterval(index) + createIndex(index.name) + .mapping( + properties(fields).source(false) // all false + ) + .shards(5) + .replicas(0) + .refreshInterval(Which.refreshInterval(index)) } def refreshIndex(index: Index) = client .execute { - ElasticDsl refreshIndex index.name + ElasticDsl.refreshIndex(index.name) } .void .recover { case _: Exception => diff --git a/modules/core/src/main/scala/Queryable.scala b/modules/core/src/main/scala/Queryable.scala index 80b3dcc9..1b279c83 100644 --- a/modules/core/src/main/scala/Queryable.scala +++ b/modules/core/src/main/scala/Queryable.scala @@ -11,7 +11,7 @@ trait Queryable[A] { case class ParsedQuery(terms: List[String], filters: Map[String, String]) { - def apply(fk: String): Option[String] = filters get fk + def apply(fk: String): Option[String] = filters.get(fk) } object QueryParser { @@ -23,12 +23,14 @@ object QueryParser { val terms = spaceRegex.split(q.trim.toLowerCase).toList terms.foldLeft(ParsedQuery(Nil, Map.empty)) { case (parsed, term) => - filterKeys.collectFirst { - case fk if term startsWith s"$fk:" => - parsed.copy( - filters = parsed.filters + (fk -> term.drop(fk.size + 1)) - ) - } getOrElse parsed.copy(terms = parsed.terms :+ term) + filterKeys + .collectFirst { + case fk if term.startsWith(s"$fk:") => + parsed.copy( + filters = parsed.filters + (fk -> term.drop(fk.size + 1)) + ) + } + .getOrElse(parsed.copy(terms = parsed.terms :+ term)) } } } diff --git a/modules/core/src/main/scala/Range.scala b/modules/core/src/main/scala/Range.scala index 636a25c6..fbec055c 100644 --- a/modules/core/src/main/scala/Range.scala +++ b/modules/core/src/main/scala/Range.scala @@ -5,13 +5,13 @@ import com.sksamuel.elastic4s.ElasticDsl._ final class Range[A] private (val a: Option[A], val b: Option[A]) { def queries(name: String) = - a.fold(b.toList map { bb => rangeQuery(name) lte bb.toString }) { aa => - b.fold(List(rangeQuery(name) gte aa.toString)) { bb => - List(rangeQuery(name) gte aa.toString lte bb.toString) + a.fold(b.toList.map { bb => rangeQuery(name).lte(bb.toString) }) { aa => + b.fold(List(rangeQuery(name).gte(aa.toString))) { bb => + List(rangeQuery(name).gte(aa.toString).lte(bb.toString)) } } - def map[B](f: A => B) = new Range(a map f, b map f) + def map[B](f: A => B) = new Range(a.map(f), b.map(f)) def nonEmpty = a.nonEmpty || b.nonEmpty } diff --git a/modules/core/src/main/scala/forum.scala b/modules/core/src/main/scala/forum.scala index 2deae3ba..94014e13 100644 --- a/modules/core/src/main/scala/forum.scala +++ b/modules/core/src/main/scala/forum.scala @@ -31,20 +31,23 @@ object ForumQuery { def searchDef(query: Forum)(from: From, size: Size) = index => - search(index.name) query makeQuery(query) sortBy ( - fieldSort(Fields.date) order SortOrder.DESC - ) start from.value size size.value + search(index.name) + .query(makeQuery(query)) + .sortBy( + fieldSort(Fields.date).order(SortOrder.DESC) + ) + .start(from.value) size size.value - def countDef(query: Forum) = index => search(index.name) query makeQuery(query) size 0 + def countDef(query: Forum) = index => search(index.name).query(makeQuery(query)) size 0 private def parsed(text: String) = QueryParser(text, List("user")) private def makeQuery(query: Forum) = boolQuery().must( parsed(query.text).terms.map { term => - multiMatchQuery(term) fields (searchableFields: _*) + multiMatchQuery(term).fields(searchableFields*) } ::: List( - parsed(query.text)("user") map { termQuery(Fields.author, _) }, - !query.troll option termQuery(Fields.troll, false) + parsed(query.text)("user").map { termQuery(Fields.author, _) }, + (!query.troll).option(termQuery(Fields.troll, false)) ).flatten ) } diff --git a/modules/core/src/main/scala/game.scala b/modules/core/src/main/scala/game.scala index ef0e905e..5363c490 100644 --- a/modules/core/src/main/scala/game.scala +++ b/modules/core/src/main/scala/game.scala @@ -57,11 +57,14 @@ object GameQuery { def searchDef(query: Game)(from: From, size: Size) = index => - search(index.name).query( - makeQuery(query) - ) sortBy query.sorting.definition start from.value size size.value timeout timeout + (search(index.name) + .query( + makeQuery(query) + ) + .sortBy(query.sorting.definition) + .start(from.value) size size.value).timeout(timeout) - def countDef(query: Game) = index => search(index.name) query makeQuery(query) size 0 timeout timeout + def countDef(query: Game) = index => (search(index.name).query(makeQuery(query)) size 0).timeout(timeout) private def makeQuery(query: Game) = { @@ -69,29 +72,29 @@ object GameQuery { def usernames = List(user1, user2).flatten def hasAiQueries = - hasAi.toList map { a => + hasAi.toList.map { a => a.fold(existsQuery(Fields.ai), not(existsQuery(Fields.ai))) } - def toQueries(query: Option[_], name: String) = - query.toList map { + def toQueries(query: Option[?], name: String) = + query.toList.map { case s: String => termQuery(name, s.toLowerCase) case x => termQuery(name, x) } List( - usernames map { termQuery(Fields.uids, _) }, + usernames.map { termQuery(Fields.uids, _) }, toQueries(winner, Fields.winner), toQueries(loser, Fields.loser), toQueries(winnerColor, Fields.winnerColor), - turns queries Fields.turns, - averageRating queries Fields.averageRating, - duration queries Fields.duration, - clock.init queries Fields.clockInit, - clock.inc queries Fields.clockInc, - date map Date.formatter.print queries Fields.date, + turns.queries(Fields.turns), + averageRating.queries(Fields.averageRating), + duration.queries(Fields.duration), + clock.init.queries(Fields.clockInit), + clock.inc.queries(Fields.clockInc), + date.map(Date.formatter.print).queries(Fields.date), hasAiQueries, - (hasAi | true).fold(aiLevel queries Fields.ai, Nil), + (hasAi.getOrElse(true)).fold(aiLevel.queries(Fields.ai), Nil), if (perf.nonEmpty) List(termsQuery(Fields.perf, perf)) else Nil, toQueries(source, Fields.source), toQueries(rated, Fields.rated), @@ -113,7 +116,7 @@ case class Sorting(f: String, order: String) { def definition = fieldSort { (Sorting.fieldKeys contains f).fold(f, Sorting.default.f) - } order (order.toLowerCase == "asc").fold(SortOrder.ASC, SortOrder.DESC) + }.order((order.toLowerCase == "asc").fold(SortOrder.ASC, SortOrder.DESC)) } object Sorting { diff --git a/modules/core/src/main/scala/model.scala b/modules/core/src/main/scala/model.scala index b1d0b229..87970e91 100644 --- a/modules/core/src/main/scala/model.scala +++ b/modules/core/src/main/scala/model.scala @@ -13,7 +13,7 @@ case class SearchResponse(hitIds: List[String]) object SearchResponse { def apply(res: ESR): SearchResponse = - SearchResponse(res.hits.hits.toList map (_.id)) + SearchResponse(res.hits.hits.toList.map(_.id)) } case class CountResponse(count: Int) diff --git a/modules/core/src/main/scala/package.scala b/modules/core/src/main/scala/package.scala index 025f2087..405a5c54 100644 --- a/modules/core/src/main/scala/package.scala +++ b/modules/core/src/main/scala/package.scala @@ -1,6 +1,5 @@ package lila -import alleycats.Zero import scala.concurrent.Future import scala.concurrent.ExecutionContext @@ -9,7 +8,7 @@ package object search { object Date { import org.joda.time.format.{ DateTimeFormat, DateTimeFormatter } val format = "yyyy-MM-dd HH:mm:ss" - val formatter: DateTimeFormatter = DateTimeFormat forPattern format + val formatter: DateTimeFormatter = DateTimeFormat.forPattern(format) } // fix scala @@ -17,23 +16,23 @@ package object search { type Fu[+A] = Future[A] type Funit = Fu[Unit] - def fuccess[A](a: A) = Future successful a - def fufail[A <: Throwable, B](a: A): Fu[B] = Future failed a + def fuccess[A](a: A) = Future.successful(a) + def fufail[A <: Throwable, B](a: A): Fu[B] = Future.failed(a) def fufail[A](a: String): Fu[A] = fufail(new Exception(a)) val funit = fuccess(()) implicit final class LilaPimpedFuture[A](fua: Fu[A]) { def >>-(sideEffect: => Unit)(implicit ec: ExecutionContext): Fu[A] = - fua andThen { case _ => + fua.andThen { case _ => sideEffect } - def >>[B](fub: => Fu[B])(implicit ec: ExecutionContext): Fu[B] = fua flatMap (_ => fub) + def >>[B](fub: => Fu[B])(implicit ec: ExecutionContext): Fu[B] = fua.flatMap(_ => fub) def void: Funit = fua.map(_ => ())(ExecutionContext.parasitic) - def inject[B](b: => B)(implicit ec: ExecutionContext): Fu[B] = fua map (_ => b) + def inject[B](b: => B)(implicit ec: ExecutionContext): Fu[B] = fua.map(_ => b) } implicit class LilaPimpedBoolean(self: Boolean) { @@ -43,10 +42,4 @@ package object search { def option[A](a: => A): Option[A] = if (self) Some(a) else None } - implicit class LilaPimpedOption[A](self: Option[A]) { - - def |(a: => A): A = self getOrElse a - - def unary_~(implicit z: Zero[A]): A = self getOrElse z.zero - } } diff --git a/modules/core/src/main/scala/study.scala b/modules/core/src/main/scala/study.scala index 70337da5..6e0d9b60 100644 --- a/modules/core/src/main/scala/study.scala +++ b/modules/core/src/main/scala/study.scala @@ -42,11 +42,12 @@ object StudyQuery { search(index.name) .query(makeQuery(query)) .sortBy( - fieldSort("_score") order SortOrder.DESC, - fieldSort(Fields.likes) order SortOrder.DESC - ) start from.value size size.value + fieldSort("_score").order(SortOrder.DESC), + fieldSort(Fields.likes).order(SortOrder.DESC) + ) + .start(from.value) size size.value - def countDef(query: Study) = index => search(index.name) query makeQuery(query) size 0 + def countDef(query: Study) = index => search(index.name).query(makeQuery(query)) size 0 private def parsed(text: String) = QueryParser(text, List("owner", "member")) @@ -55,12 +56,12 @@ object StudyQuery { if (parsed(query.text).terms.isEmpty) matchAllQuery() else multiMatchQuery( - parsed(query.text).terms mkString " " - ) fields (searchableFields: _*) analyzer "english" matchType "most_fields" + parsed(query.text).terms.mkString(" ") + ).fields(searchableFields*).analyzer("english").matchType("most_fields") must { matcher :: List( - parsed(query.text)("owner") map { termQuery(Fields.owner, _) }, - parsed(query.text)("member") map { member => + parsed(query.text)("owner").map { termQuery(Fields.owner, _) }, + parsed(query.text)("member").map { member => boolQuery() .must(termQuery(Fields.members, member)) .not(termQuery(Fields.owner, member)) @@ -70,7 +71,7 @@ object StudyQuery { Some(selectPublic), query.userId.map(selectUserId) ).flatten - } minimumShouldMatch 1 + }.minimumShouldMatch(1) private val selectPublic = termQuery(Fields.public, true) diff --git a/modules/core/src/main/scala/team.scala b/modules/core/src/main/scala/team.scala index f3b50e1d..d28e788b 100644 --- a/modules/core/src/main/scala/team.scala +++ b/modules/core/src/main/scala/team.scala @@ -26,17 +26,20 @@ object TeamQuery { def searchDef(query: Team)(from: From, size: Size) = index => - search(index.name) query makeQuery(query) sortBy ( - fieldSort(Fields.nbMembers) order SortOrder.DESC - ) start from.value size size.value + search(index.name) + .query(makeQuery(query)) + .sortBy( + fieldSort(Fields.nbMembers).order(SortOrder.DESC) + ) + .start(from.value) size size.value - def countDef(query: Team) = index => search(index.name) query makeQuery(query) size 0 + def countDef(query: Team) = index => search(index.name).query(makeQuery(query)) size 0 private def parsed(query: Team) = QueryParser(query.text, Nil) private def makeQuery(team: Team) = must { parsed(team).terms.map { term => - multiMatchQuery(term) fields (searchableFields: _*) + multiMatchQuery(term).fields(searchableFields*) } } } diff --git a/play/app/src/main/scala/AppLoader.scala b/play/app/src/main/scala/AppLoader.scala index cfff3821..db1c669e 100644 --- a/play/app/src/main/scala/AppLoader.scala +++ b/play/app/src/main/scala/AppLoader.scala @@ -25,7 +25,7 @@ class AppComponents(context: ApplicationLoader.Context) extends BuiltInComponent scala.concurrent.Future { play.api.Logger("search").info("closing now!") c.close() - Thread sleep 1000 + Thread.sleep(1000) } ) diff --git a/play/app/src/main/scala/Chronometer.scala b/play/app/src/main/scala/Chronometer.scala index 42d5780b..76655e60 100644 --- a/play/app/src/main/scala/Chronometer.scala +++ b/play/app/src/main/scala/Chronometer.scala @@ -7,11 +7,11 @@ object Chronometer { def apply[A](name: String)(f: => Fu[A])(implicit ec: ExecutionContext): Fu[A] = { val start = nowMillis // logger debug s"$name - start" - f andThen { + f.andThen { case scala.util.Failure(e: Exception) => - logger warn s"$name - failed in ${nowMillis - start}ms - ${e.getMessage}" + logger.warn(s"$name - failed in ${nowMillis - start}ms - ${e.getMessage}") case scala.util.Failure(e) => throw e // Throwables - case scala.util.Success(_) => logger info s"$name in ${nowMillis - start}ms" + case scala.util.Success(_) => logger.info(s"$name in ${nowMillis - start}ms") } } diff --git a/play/app/src/main/scala/QueryParser.scala b/play/app/src/main/scala/QueryParser.scala index 49a631bb..c324c9df 100644 --- a/play/app/src/main/scala/QueryParser.scala +++ b/play/app/src/main/scala/QueryParser.scala @@ -31,8 +31,7 @@ object JsonParser { implicit def rangeJsonReader[A: Reads: Ordering]: Reads[Range[A]] = ( - (__ \ "a").readNullable[A] and - (__ \ "b").readNullable[A] + (__ \ "a").readNullable[A].and((__ \ "b").readNullable[A]) ) { (a, b) => Range(a, b) } } diff --git a/play/app/src/main/scala/controllers/WebApi.scala b/play/app/src/main/scala/controllers/WebApi.scala index 72731d65..fa3c7308 100644 --- a/play/app/src/main/scala/controllers/WebApi.scala +++ b/play/app/src/main/scala/controllers/WebApi.scala @@ -11,19 +11,19 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: def store(index: String, id: String) = JsObjectBody { obj => - client.store(Index(index), Id(id), JsonObject(Json.stringify(obj))) inject Ok(s"inserted $index/$id") + client.store(Index(index), Id(id), JsonObject(Json.stringify(obj))).inject(Ok(s"inserted $index/$id")) } def deleteById(index: String, id: String) = Action.async { - client.deleteOne(Index(index), Id(id)) inject Ok(s"deleted $index/$id") + client.deleteOne(Index(index), Id(id)).inject(Ok(s"deleted $index/$id")) } def deleteByIds(index: String) = JsObjectBody { obj => (obj \ "ids").asOpt[List[String]] match { case Some(ids) => - client.deleteMany(Index(index), ids map Id) inject Ok(s"deleted ${ids.size} ids from $index") + client.deleteMany(Index(index), ids.map(Id)).inject(Ok(s"deleted ${ids.size} ids from $index")) case _ => fuccess(BadRequest(obj)) } } @@ -35,8 +35,8 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: JsonParser.parse(Index(index))(obj) match { case None => fuccess(NotFound(s"Can't parse query for $index")) case Some(query) => - client.search(Index(index), query, From(from), Size(size)) map { res => - Ok(res.hitIds mkString ",") + client.search(Index(index), query, From(from), Size(size)).map { res => + Ok(res.hitIds.mkString(",")) } } } @@ -46,7 +46,7 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: JsonParser.parse(Index(index))(obj) match { case None => fuccess(NotFound(s"Can't parse query for $index")) case Some(query) => - client.count(Index(index), query) map { res => + client.count(Index(index), query).map { res => Ok(res.count.toString) } } @@ -54,10 +54,10 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: def mapping(index: String) = Action.async { - Which mapping Index(index) match { + Which.mapping(Index(index)) match { case None => fuccess(NotFound(s"No such mapping: $index")) case Some(m) => - client.putMapping(Index(index), m) inject Ok(s"put $index mapping") + client.putMapping(Index(index), m).inject(Ok(s"put $index mapping")) } } @@ -67,7 +67,7 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: (id, JsonObject(Json.stringify(obj))) }.toList Chronometer(s"bulk ${objs.fields.size} $index") { - client.storeBulk(Index(index), jsonObjs) map { _ => + client.storeBulk(Index(index), jsonObjs).map { _ => Ok("thx") } } @@ -75,7 +75,7 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: def refresh(index: String) = JsObjectBody { _ => - client.refreshIndex(Index(index)) map { _ => + client.refreshIndex(Index(index)).map { _ => Ok("thx") } } @@ -87,12 +87,13 @@ class WebApi @Inject() (cc: ControllerComponents, client: ESClient)(implicit ec: .fold( err => fuccess(BadRequest(err.toString)), obj => - f(obj) recover { case e: Exception => + f(obj).recover { case e: Exception => val msg = s"${Json.prettyPrint(obj)}\n\n${e.getMessage}" logger.warn(msg, e) BadRequest(msg) } - ) map (_ as TEXT) + ) + .map(_.as(TEXT)) } private val logger = play.api.Logger("search")