diff --git a/core/src/main/scala/lightdb/Field.scala b/core/src/main/scala/lightdb/Field.scala index 31879186..b6a05916 100644 --- a/core/src/main/scala/lightdb/Field.scala +++ b/core/src/main/scala/lightdb/Field.scala @@ -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 => @@ -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)" } diff --git a/core/src/main/scala/lightdb/Query.scala b/core/src/main/scala/lightdb/Query.scala index d46cb5e6..389038c0 100644 --- a/core/src/main/scala/lightdb/Query.scala +++ b/core/src/main/scala/lightdb/Query.scala @@ -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) diff --git a/core/src/main/scala/lightdb/doc/DocumentModel.scala b/core/src/main/scala/lightdb/doc/DocumentModel.scala index 9fd332fa..a45a50a5 100644 --- a/core/src/main/scala/lightdb/doc/DocumentModel.scala +++ b/core/src/main/scala/lightdb/doc/DocumentModel.scala @@ -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 { diff --git a/core/src/main/scala/lightdb/filter/Filter.scala b/core/src/main/scala/lightdb/filter/Filter.scala index d8a73ed8..222653b7 100644 --- a/core/src/main/scala/lightdb/filter/Filter.scala +++ b/core/src/main/scala/lightdb/filter/Filter.scala @@ -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) } } \ No newline at end of file diff --git a/lucene/src/main/scala/lightdb/lucene/LuceneStore.scala b/lucene/src/main/scala/lightdb/lucene/LuceneStore.scala index 5bb4d645..8ab77037 100644 --- a/lucene/src/main/scala/lightdb/lucene/LuceneStore.scala +++ b/lucene/src/main/scala/lightdb/lucene/LuceneStore.scala @@ -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) diff --git a/sql/src/main/scala/lightdb/sql/SQLStore.scala b/sql/src/main/scala/lightdb/sql/SQLStore.scala index 0835b885..b00f2468 100644 --- a/sql/src/main/scala/lightdb/sql/SQLStore.scala +++ b/sql/src/main/scala/lightdb/sql/SQLStore.scala @@ -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, _] => @@ -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) diff --git a/sqlite/src/main/scala/lightdb/sql/SQLiteStore.scala b/sqlite/src/main/scala/lightdb/sql/SQLiteStore.scala index c84daad5..623d3250 100644 --- a/sqlite/src/main/scala/lightdb/sql/SQLiteStore.scala +++ b/sqlite/src/main/scala/lightdb/sql/SQLiteStore.scala @@ -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")) {