Skip to content

Commit

Permalink
Updated Filter to use fieldName instead of field for persistence
Browse files Browse the repository at this point in the history
  • Loading branch information
darkfrog26 committed Sep 3, 2024
1 parent cb7a692 commit ebed784
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 56 deletions.
14 changes: 7 additions & 7 deletions core/src/main/scala/lightdb/Field.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@ sealed class Field[Doc <: Document[Doc], V](val name: String,

def getJson(doc: Doc): Json = get(doc).json

override def is(value: V): Filter[Doc] = Filter.Equals(this, value)
override def is(value: V): Filter[Doc] = Filter.Equals(name, value)

override def !==(value: V): Filter[Doc] = Filter.NotEquals(this, value)
override def !==(value: V): Filter[Doc] = Filter.NotEquals(name, value)

override protected def rangeLong(from: Option[Long], to: Option[Long]): Filter[Doc] =
Filter.RangeLong(this.asInstanceOf[Field[Doc, Long]], from, to)
Filter.RangeLong(name, from, to)

override protected def rangeDouble(from: Option[Double], to: Option[Double]): Filter[Doc] =
Filter.RangeDouble(this.asInstanceOf[Field[Doc, Double]], from, to)
Filter.RangeDouble(name, from, to)

override def IN(values: Seq[V]): Filter[Doc] = {
Field.MaxIn.foreach { max =>
if (values.size > max) throw new RuntimeException(s"Attempting to specify ${values.size} values for IN clause in $name, but maximum is ${Field.MaxIn}.")
}
Filter.In(this, values)
Filter.In(name, values)
}

override def parsed(query: String, allowLeadingWildcard: Boolean): Filter[Doc] =
Filter.Parsed(this, query, allowLeadingWildcard)
Filter.Parsed(name, query, allowLeadingWildcard)

override def words(s: String, matchStartsWith: Boolean, matchEndsWith: Boolean): Filter[Doc] = {
val words = s.split("\\s+").map { w =>
Expand All @@ -62,7 +62,7 @@ sealed class Field[Doc <: Document[Doc], V](val name: String,
def opt: Field[Doc, Option[V]] = new Field[Doc, Option[V]](name, doc => Option(get(doc)), () => implicitly[RW[Option[V]]], indexed)

override def distance(from: GeoPoint, radius: Distance): Filter[Doc] =
Filter.Distance(this.asInstanceOf[Field[Doc, Option[GeoPoint]]], from, radius)
Filter.Distance(name, from, radius)

override def toString: String = s"Field(name = $name)"
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/lightdb/Query.scala
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ case class Query[Doc <: Document[Doc], Model <: DocumentModel[Doc]](collection:
(implicit transaction: Transaction[Doc]): SearchResults[Doc, V] = {
val storeMode = collection.store.storeMode
if (Query.Validation || (Query.WarnFilteringWithoutIndex && storeMode == StoreMode.All)) {
val notIndexed = filter.toList.flatMap(_.fields).filter(!_.indexed)
val notIndexed = filter.toList.flatMap(_.fields(collection.model)).filter(!_.indexed)
storeMode match {
case StoreMode.Indexes => if (notIndexed.nonEmpty) {
throw NonIndexedFieldException(query, notIndexed)
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/lightdb/doc/DocumentModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ trait DocumentModel[Doc <: Document[Doc]] {

def fields: List[Field[Doc, _]] = _fields

def fieldByName[F](name: String): Field[Doc, F] = _fields
.find(_.name == name)
.getOrElse(throw new NullPointerException(s"$name field not found"))
.asInstanceOf[Field[Doc, F]]

lazy val builder: FilterBuilder[Doc, this.type] = new FilterBuilder(this, 1, Nil)

object field {
Expand Down
59 changes: 39 additions & 20 deletions core/src/main/scala/lightdb/filter/Filter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,74 @@ package lightdb.filter

import fabric.Json
import lightdb.Field
import lightdb.doc.Document
import lightdb.doc.{Document, DocumentModel}
import lightdb.spatial.GeoPoint

sealed trait Filter[Doc <: Document[Doc]] {
def fields: List[Field[Doc, _]]
def fieldNames: List[String]

def fields(model: DocumentModel[Doc]): List[Field[Doc, _]] =
fieldNames.map(model.fieldByName)
}

object Filter {
def and[Doc <: Document[Doc]](filters: Filter[Doc]*): Filter[Doc] = filters.tail
.foldLeft(filters.head)((combined, filter) => combined && filter)

case class Equals[Doc <: Document[Doc], F](field: Field[Doc, F], value: F) extends Filter[Doc] {
def getJson: Json = field.rw.read(value)
case class Equals[Doc <: Document[Doc], F](fieldName: String, value: F) extends Filter[Doc] {
def getJson(model: DocumentModel[Doc]): Json = model.fieldByName[F](fieldName).rw.read(value)
def field(model: DocumentModel[Doc]): Field[Doc, F] = model.fieldByName(fieldName)

override lazy val fields: List[Field[Doc, _]] = List(field)
override lazy val fieldNames: List[String] = List(fieldName)
}
object Equals {
def apply[Doc <: Document[Doc], F](field: Field[Doc, F], value: F): Equals[Doc, F] = Equals(field.name, value)
}

case class NotEquals[Doc <: Document[Doc], F](field: Field[Doc, F], value: F) extends Filter[Doc] {
def getJson: Json = field.rw.read(value)
case class NotEquals[Doc <: Document[Doc], F](fieldName: String, value: F) extends Filter[Doc] {
def getJson(model: DocumentModel[Doc]): Json = model.fieldByName[F](fieldName).rw.read(value)
def field(model: DocumentModel[Doc]): Field[Doc, F] = model.fieldByName(fieldName)

override lazy val fields: List[Field[Doc, _]] = List(field)
override lazy val fieldNames: List[String] = List(fieldName)
}
object NotEquals {
def apply[Doc <: Document[Doc], F](field: Field[Doc, F], value: F): NotEquals[Doc, F] = NotEquals(field.name, value)
}

case class In[Doc <: Document[Doc], F](field: Field[Doc, F], values: Seq[F]) extends Filter[Doc] {
def getJson: List[Json] = values.toList.map(field.rw.read)
case class In[Doc <: Document[Doc], F](fieldName: String, values: Seq[F]) extends Filter[Doc] {
def getJson(model: DocumentModel[Doc]): List[Json] = values.toList.map(model.fieldByName[F](fieldName).rw.read)
def field(model: DocumentModel[Doc]): Field[Doc, F] = model.fieldByName(fieldName)

override lazy val fields: List[Field[Doc, _]] = List(field)
override lazy val fieldNames: List[String] = List(fieldName)
}
object In {
def apply[Doc <: Document[Doc], F](field: Field[Doc, F], values: Seq[F]): In[Doc, F] = In(field.name, values)
}

case class RangeLong[Doc <: Document[Doc]](field: Field[Doc, Long], from: Option[Long], to: Option[Long]) extends Filter[Doc] {
override lazy val fields: List[Field[Doc, _]] = List(field)
case class RangeLong[Doc <: Document[Doc]](fieldName: String, from: Option[Long], to: Option[Long]) extends Filter[Doc] {
def field(model: DocumentModel[Doc]): Field[Doc, Long] = model.fieldByName(fieldName)
override lazy val fieldNames: List[String] = List(fieldName)
}

case class RangeDouble[Doc <: Document[Doc]](field: Field[Doc, Double], from: Option[Double], to: Option[Double]) extends Filter[Doc] {
override lazy val fields: List[Field[Doc, _]] = List(field)
case class RangeDouble[Doc <: Document[Doc]](fieldName: String, from: Option[Double], to: Option[Double]) extends Filter[Doc] {
def field(model: DocumentModel[Doc]): Field[Doc, Double] = model.fieldByName(fieldName)
override lazy val fieldNames: List[String] = List(fieldName)
}

case class Parsed[Doc <: Document[Doc], F](field: Field[Doc, F], query: String, allowLeadingWildcard: Boolean) extends Filter[Doc] {
override lazy val fields: List[Field[Doc, _]] = List(field)
case class Parsed[Doc <: Document[Doc], F](fieldName: String, query: String, allowLeadingWildcard: Boolean) extends Filter[Doc] {
def field(model: DocumentModel[Doc]): Field[Doc, F] = model.fieldByName(fieldName)
override lazy val fieldNames: List[String] = List(fieldName)
}

case class Distance[Doc <: Document[Doc]](field: Field[Doc, Option[GeoPoint]], from: GeoPoint, radius: lightdb.distance.Distance) extends Filter[Doc] {
override lazy val fields: List[Field[Doc, _]] = List(field)
case class Distance[Doc <: Document[Doc]](fieldName: String, from: GeoPoint, radius: lightdb.distance.Distance) extends Filter[Doc] {
def field(model: DocumentModel[Doc]): Field[Doc, GeoPoint] = model.fieldByName(fieldName)
override lazy val fieldNames: List[String] = List(fieldName)
}

case class Multi[Doc <: Document[Doc]](minShould: Int, filters: List[FilterClause[Doc]] = Nil) extends Filter[Doc] {
def conditional(filter: Filter[Doc], condition: Condition, boost: Option[Double] = None): Multi[Doc] =
copy(filters = filters ::: List(FilterClause(filter, condition, boost)))

override def fields: List[Field[Doc, _]] = filters.flatMap(_.filter.fields)
override def fieldNames: List[String] = filters.flatMap(_.filter.fieldNames)
}
}
18 changes: 9 additions & 9 deletions lucene/src/main/scala/lightdb/lucene/LuceneStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -236,29 +236,29 @@ class LuceneStore[Doc <: Document[Doc], Model <: DocumentModel[Doc]](directory:

private def filter2Lucene(filter: Option[Filter[Doc]]): LuceneQuery = filter match {
case Some(f) => f match {
case f: Filter.Equals[Doc, _] => exactQuery(f.field, f.getJson)
case f: Filter.Equals[Doc, _] => exactQuery(f.field(collection.model), f.getJson(collection.model))
case f: Filter.NotEquals[Doc, _] =>
val b = new BooleanQuery.Builder
b.add(new MatchAllDocsQuery, BooleanClause.Occur.MUST)
b.add(exactQuery(f.field, f.getJson), BooleanClause.Occur.MUST_NOT)
b.add(exactQuery(f.field(collection.model), f.getJson(collection.model)), BooleanClause.Occur.MUST_NOT)
b.build()
case f: Filter.In[Doc, _] =>
val queries = f.getJson.map(json => exactQuery(f.field, json))
val queries = f.getJson(collection.model).map(json => exactQuery(f.field(collection.model), json))
val b = new BooleanQuery.Builder
b.setMinimumNumberShouldMatch(1)
queries.foreach { q =>
b.add(q, BooleanClause.Occur.SHOULD)
}
b.build()
case Filter.RangeLong(index, from, to) => LongField.newRangeQuery(index.name, from.getOrElse(Long.MinValue), to.getOrElse(Long.MaxValue))
case Filter.RangeDouble(index, from, to) => DoubleField.newRangeQuery(index.name, from.getOrElse(Double.MinValue), to.getOrElse(Double.MaxValue))
case Filter.Parsed(index, query, allowLeadingWildcard) =>
val parser = new QueryParser(index.name, this.index.analyzer)
case Filter.RangeLong(fieldName, from, to) => LongField.newRangeQuery(fieldName, from.getOrElse(Long.MinValue), to.getOrElse(Long.MaxValue))
case Filter.RangeDouble(fieldName, from, to) => DoubleField.newRangeQuery(fieldName, from.getOrElse(Double.MinValue), to.getOrElse(Double.MaxValue))
case Filter.Parsed(fieldName, query, allowLeadingWildcard) =>
val parser = new QueryParser(fieldName, this.index.analyzer)
parser.setAllowLeadingWildcard(allowLeadingWildcard)
parser.setSplitOnWhitespace(true)
parser.parse(query)
case Filter.Distance(index, from, radius) =>
LatLonPoint.newDistanceQuery(index.name, from.latitude, from.longitude, radius.toMeters)
case Filter.Distance(fieldName, from, radius) =>
LatLonPoint.newDistanceQuery(fieldName, from.latitude, from.longitude, radius.toMeters)
case Filter.Multi(minShould, clauses) =>
val b = new BooleanQuery.Builder
val hasShould = clauses.exists(c => c.condition == Condition.Should || c.condition == Condition.Filter)
Expand Down
36 changes: 18 additions & 18 deletions sql/src/main/scala/lightdb/sql/SQLStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -481,35 +481,35 @@ abstract class SQLStore[Doc <: Document[Doc], Model <: DocumentModel[Doc]] exten
throw new UnsupportedOperationException("Distance filtering not supported in SQL!")

private def filter2Part(f: Filter[Doc]): SQLPart = f match {
case f: Filter.Equals[Doc, _] if f.field.isArr =>
val values = f.getJson.asVector
case f: Filter.Equals[Doc, _] if f.field(collection.model).isArr =>
val values = f.getJson(collection.model).asVector
val parts = values.map { json =>
val jsonString = JsonFormatter.Compact(json)
SQLPart(s"${f.field.name} LIKE ?", List(SQLArg.StringArg(s"%$jsonString%")))
SQLPart(s"${f.fieldName} LIKE ?", List(SQLArg.StringArg(s"%$jsonString%")))
}
SQLPart.merge(parts: _*)
case f: Filter.Equals[Doc, _] if f.value == null | f.value == None => SQLPart(s"${f.field.name} IS NULL")
case f: Filter.Equals[Doc, _] => SQLPart(s"${f.field.name} = ?", List(SQLArg.FieldArg(f.field, f.value)))
case f: Filter.NotEquals[Doc, _] if f.field.isArr =>
val values = f.getJson.asVector
case f: Filter.Equals[Doc, _] if f.value == null | f.value == None => SQLPart(s"${f.fieldName} IS NULL")
case f: Filter.Equals[Doc, _] => SQLPart(s"${f.fieldName} = ?", List(SQLArg.FieldArg(f.field(collection.model), f.value)))
case f: Filter.NotEquals[Doc, _] if f.field(collection.model).isArr =>
val values = f.getJson(collection.model).asVector
val parts = values.map { json =>
val jsonString = JsonFormatter.Compact(json)
SQLPart(s"${f.field.name} NOT LIKE ?", List(SQLArg.StringArg(s"%$jsonString%")))
SQLPart(s"${f.fieldName} NOT LIKE ?", List(SQLArg.StringArg(s"%$jsonString%")))
}
SQLPart.merge(parts: _*)
case f: Filter.NotEquals[Doc, _] if f.value == null | f.value == None => SQLPart(s"${f.field.name} IS NOT NULL")
case f: Filter.NotEquals[Doc, _] => SQLPart(s"${f.field.name} != ?", List(SQLArg.FieldArg(f.field, f.value)))
case f: Filter.In[Doc, _] => SQLPart(s"${f.field.name} IN (${f.values.map(_ => "?").mkString(", ")})", f.values.toList.map(v => SQLArg.FieldArg(f.field, v)))
case f: Filter.NotEquals[Doc, _] if f.value == null | f.value == None => SQLPart(s"${f.fieldName} IS NOT NULL")
case f: Filter.NotEquals[Doc, _] => SQLPart(s"${f.fieldName} != ?", List(SQLArg.FieldArg(f.field(collection.model), f.value)))
case f: Filter.In[Doc, _] => SQLPart(s"${f.fieldName} IN (${f.values.map(_ => "?").mkString(", ")})", f.values.toList.map(v => SQLArg.FieldArg(f.field(collection.model), v)))
case f: Filter.RangeLong[Doc] => (f.from, f.to) match {
case (Some(from), Some(to)) => SQLPart(s"${f.field.name} BETWEEN ? AND ?", List(SQLArg.LongArg(from), SQLArg.LongArg(to)))
case (None, Some(to)) => SQLPart(s"${f.field.name} <= ?", List(SQLArg.LongArg(to)))
case (Some(from), None) => SQLPart(s"${f.field.name} >= ?", List(SQLArg.LongArg(from)))
case (Some(from), Some(to)) => SQLPart(s"${f.fieldName} BETWEEN ? AND ?", List(SQLArg.LongArg(from), SQLArg.LongArg(to)))
case (None, Some(to)) => SQLPart(s"${f.fieldName} <= ?", List(SQLArg.LongArg(to)))
case (Some(from), None) => SQLPart(s"${f.fieldName} >= ?", List(SQLArg.LongArg(from)))
case _ => throw new UnsupportedOperationException(s"Invalid: $f")
}
case f: Filter.RangeDouble[Doc] => (f.from, f.to) match {
case (Some(from), Some(to)) => SQLPart(s"${f.field.name} BETWEEN ? AND ?", List(SQLArg.DoubleArg(from), SQLArg.DoubleArg(to)))
case (None, Some(to)) => SQLPart(s"${f.field.name} <= ?", List(SQLArg.DoubleArg(to)))
case (Some(from), None) => SQLPart(s"${f.field.name} >= ?", List(SQLArg.DoubleArg(from)))
case (Some(from), Some(to)) => SQLPart(s"${f.fieldName} BETWEEN ? AND ?", List(SQLArg.DoubleArg(from), SQLArg.DoubleArg(to)))
case (None, Some(to)) => SQLPart(s"${f.fieldName} <= ?", List(SQLArg.DoubleArg(to)))
case (Some(from), None) => SQLPart(s"${f.fieldName} >= ?", List(SQLArg.DoubleArg(from)))
case _ => throw new UnsupportedOperationException(s"Invalid: $f")
}
case f: Filter.Parsed[Doc, _] =>
Expand All @@ -523,7 +523,7 @@ abstract class SQLStore[Doc <: Document[Doc], Model <: DocumentModel[Doc]] exten
}
s
}.toList
SQLPart(parts.map(_ => s"${f.field.name} LIKE ?").mkString(" AND "), parts.map(s => SQLArg.StringArg(s)))
SQLPart(parts.map(_ => s"${f.fieldName} LIKE ?").mkString(" AND "), parts.map(s => SQLArg.StringArg(s)))
case f: Filter.Distance[Doc] => distanceFilter(f)
case f: Filter.Multi[Doc] =>
val (shoulds, others) = f.filters.partition(f => f.condition == Condition.Filter || f.condition == Condition.Should)
Expand Down
2 changes: 1 addition & 1 deletion sqlite/src/main/scala/lightdb/sql/SQLiteStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class SQLiteStore[Doc <: Document[Doc], Model <: DocumentModel[Doc]](val connect
}

override protected def distanceFilter(f: Filter.Distance[Doc]): SQLPart =
SQLPart(s"ST_Distance(${f.field.name}, GeomFromText(?, 4326), true) <= ?", List(SQLArg.GeoPointArg(f.from), SQLArg.DoubleArg(f.radius.m)))
SQLPart(s"ST_Distance(${f.fieldName}, GeomFromText(?, 4326), true) <= ?", List(SQLArg.GeoPointArg(f.from), SQLArg.DoubleArg(f.radius.m)))

override protected def addColumn(field: Field[Doc, _])(implicit transaction: Transaction[Doc]): Unit = {
if (field.rw.definition.className.contains("lightdb.spatial.GeoPoint")) {
Expand Down

0 comments on commit ebed784

Please sign in to comment.