Skip to content

Commit

Permalink
Merge pull request #149 from SwissDataScienceCenter/solr-subquery
Browse files Browse the repository at this point in the history
Extend solr client to support subqueries
  • Loading branch information
eikek authored Jun 5, 2024
2 parents 8d48901 + 5d42e5e commit 50b462c
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ final case class QueryData(
def withLimit(limit: Int): QueryData = copy(limit = limit)
def withOffset(offset: Int): QueryData = copy(offset = offset)

def addSubQuery(field: FieldName, sq: SubQuery): QueryData =
copy(
params = params ++ sq.toParams(field),
fields = fields :+ FieldName(s"${field.name}:[subquery]")
)

object QueryData:

def apply(query: QueryString): QueryData =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

package io.renku.solr.client

import io.bullet.borer.Decoder
import io.bullet.borer.derivation.MapBasedCodecs.deriveDecoder
import io.bullet.borer.derivation.MapBasedCodecs
import io.bullet.borer.{Decoder, Encoder}

final case class ResponseBody[A](
numFound: Long,
Expand All @@ -31,4 +31,5 @@ final case class ResponseBody[A](
copy(docs = docs.map(f))

object ResponseBody:
given [A](using Decoder[A]): Decoder[ResponseBody[A]] = deriveDecoder
given [A](using Decoder[A]): Decoder[ResponseBody[A]] = MapBasedCodecs.deriveDecoder
given [A](using Encoder[A]): Encoder[ResponseBody[A]] = MapBasedCodecs.deriveEncoder
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ object SolrSort:
def nonEmpty: Boolean = !self.isEmpty
def ++(next: SolrSort): SolrSort =
Monoid[SolrSort].combine(self, next)
private[client] def toSolr: String =
self.map { case (f, d) => s"${f.name} ${d.name}" }.mkString(",")

given Monoid[SolrSort] =
Monoid.instance(empty, (a, b) => if (a.isEmpty) b else if (b.isEmpty) a else a ++ b)

given Encoder[SolrSort] = Encoder.forString.contramap(list =>
list.map { case (f, d) => s"${f.name} ${d.name}" }.mkString(",")
)
given Encoder[SolrSort] = Encoder.forString.contramap(_.toSolr)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* 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.
*/

package io.renku.solr.client

import cats.syntax.option.*

import io.renku.solr.client.schema.FieldName

final case class SubQuery(
query: String,
filter: String,
limit: Int,
offset: Int = 0,
fields: Seq[FieldName] = Seq.empty,
sort: SolrSort = SolrSort.empty
):
def withSort(sort: SolrSort): SubQuery = copy(sort = sort)
def withFields(field: FieldName*) = copy(fields = field)
def withFilter(q: String): SubQuery = copy(filter = filter)
def withLimit(limit: Int): SubQuery = copy(limit = limit)
def withOffset(offset: Int): SubQuery = copy(offset = offset)

private[client] def toParams(field: FieldName): Map[String, String] =
def key(s: String): String = s"${field.name}.$s"
List(
(key("q") -> query).some,
Option.when(filter.nonEmpty)(key("fq") -> filter),
Option.when(limit >= 0)(key("limit") -> limit.toString),
Option.when(offset > 0)(key("offset") -> offset.toString),
Option.when(fields.nonEmpty)(key("fl") -> fields.mkString(",")),
Option.when(sort.nonEmpty)(key("sort") -> sort.toSolr)
).collect { case Some(p) => p }.toMap
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.bullet.borer.derivation.MapBasedCodecs
import io.bullet.borer.derivation.key
import io.bullet.borer.{Decoder, Encoder, Reader}
import io.renku.search.GeneratorSyntax.*
import io.renku.solr.client.SolrClientSpec.CourseMember
import io.renku.solr.client.SolrClientSpec.{Course, Room}
import io.renku.solr.client.facet.{Facet, Facets}
import io.renku.solr.client.schema.*
Expand Down Expand Up @@ -175,6 +176,42 @@ class SolrClientSpec
_ = assert(schema.schema.fieldTypes.nonEmpty)
yield ()

test("use subqueries"):
for
client <- IO(solrClient())
course = Course("course1", "TechCourse")
courseMember = CourseMember("course-mem-1", "John Doe", "course1")
_ <- client.upsert(Seq(course))
_ <- client.upsert(Seq(courseMember))

resNormal <- client.query[CourseMember](
QueryData("_type_s:CourseMember", Seq.empty, 10, 0)
)
_ = assertEquals(
resNormal.responseBody.docs.head.copy(version = DocVersion.NotExists),
courseMember
)

resSub <- client.query[CourseMember](
QueryData("_type_s:CourseMember", Seq.empty, 10, 0)
.withFields(FieldName.all)
.addSubQuery(
FieldName("courseFull"),
SubQuery(
query = "{!terms f=id v=$row.course_id_s}",
filter = "{!terms f=_type_s v=Course}",
fields = Seq(FieldName.all),
limit = 2
)
)
)
_ = assertEquals(
resSub.responseBody.docs.head.courseFull.get.docs.head
.copy(version = DocVersion.NotExists),
course
)
yield ()

object SolrClientSpec:
case class Room(id: String, roomName: String, roomDescription: String, roomSeats: Int)
object Room:
Expand Down Expand Up @@ -204,3 +241,15 @@ object SolrClientSpec:
object Course:
given Decoder[Course] = MapBasedCodecs.deriveDecoder
given Encoder[Course] = EncoderSupport.deriveWithDiscriminator[Course]("_type_s")

final case class CourseMember(
id: String,
@key("name_s") name: String,
@key("course_id_s") courseId: String,
courseFull: Option[ResponseBody[Course]] = None,
@key("_version_") version: DocVersion = DocVersion.NotExists
)
object CourseMember:
given Decoder[CourseMember] = MapBasedCodecs.deriveDecoder
given Encoder[CourseMember] =
EncoderSupport.deriveWithDiscriminator[CourseMember]("_type_s")

0 comments on commit 50b462c

Please sign in to comment.