Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Bugfixes #876 and #914 for squeryl-record (in queries and date query …

…improvements)

Closes #876
Closes #914

Mostly fixed by using new squeryl version 0.9.4-RC6 and by fixing implicit conversions.
Test cases added.
  • Loading branch information...
commit 20ae05ba6e8a7b4923683006a09287e81eec29bc 1 parent d3d8f52
Michael Gottschalk authored
17 persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordMetaDataFactory.scala
View
@@ -15,7 +15,7 @@ package net.liftweb
package squerylrecord
import common.{Box, Full}
-import record.{BaseField, MetaRecord, Record, TypedField}
+import record.{BaseField, MetaRecord, Record, TypedField, OwnedField}
import record.field._
import org.squeryl.internals.{FieldMetaData, PosoMetaData, FieldMetaDataFactory}
@@ -152,5 +152,20 @@ class RecordMetaDataFactory extends FieldMetaDataFactory {
() => metaRecord.createRecord.asInstanceOf[AnyRef]
}
+
+ /**
+ * There needs to be a special handling for squeryl-record when single fields are selected.
+ *
+ * The problem was that record fields reference the record itself and thus Squeryl was of the
+ * opinion that the whole record should be returned, as well as the selected field.
+ * It is described in detail in this bug report:
+ * https://www.assembla.com/spaces/liftweb/tickets/876-record-squeryl-selecting-unspecified-columns-in-generated-sql
+ *
+ * By overriding this function, the reference to the record is excluded from
+ * the reference finding algorithm in Squeryl.
+ */
+ override def hideFromYieldInspection(o: AnyRef, f: Field): Boolean = {
+ o.isInstanceOf[OwnedField[_]] && isRecord(f.getType)
+ }
}
40 persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala
View
@@ -14,11 +14,12 @@
package net.liftweb
package squerylrecord
-import record.{ MandatoryTypedField, OptionalTypedField, TypedField }
+import record.{ MandatoryTypedField, OptionalTypedField, TypedField, Record}
+import record.field.{EnumNameField, OptionalEnumNameField, EnumField, OptionalEnumField}
-import org.squeryl.{ PrimitiveTypeMode, Schema }
+import org.squeryl.{ PrimitiveTypeMode, Schema, Query }
import org.squeryl.dsl.{ BooleanExpression, DateExpression, EnumExpression, NumericalExpression, StringExpression, NonNumericalExpression }
-import org.squeryl.dsl.ast.{ SelectElementReference, SelectElement, ConstantExpressionNode }
+import org.squeryl.dsl.ast.{ SelectElementReference, SelectElement, ConstantExpressionNode, RightHandSideOfIn }
import org.squeryl.internals.{ AttributeValidOnNonNumericalColumn, AttributeValidOnNumericalColumn, FieldReferenceLinker, OutMapper }
import java.util.{ Calendar, Date }
@@ -125,12 +126,31 @@ trait RecordTypeMode extends PrimitiveTypeMode {
}
}
+ /** Needed for inner selects. The cast is possible here because the type is not
+ * used in the in query. Only the AST of the query is needed. */
+ //implicit def queryStringField2QueryString[T <: TypedField[String]](q: Query[T]): Query[String] = q.asInstanceOf[Query[String]]
+
/** Needed for outer joins. */
implicit def optionDateField2OptionDate(f: Option[TypedField[Calendar]]) = fieldReference match {
case Some(e) => new SelectElementReference[Timestamp](e)(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
- case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull) with BooleanExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
+ case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
}
+
+ /** Needed for inner queries on date fields */
+ //implicit def dateField2Timestamp(f: MandatoryTypedField[Calendar]) = new java.sql.Timestamp(f.is.getTime.getTime)
+ //implicit def optionalDateField2Timestamp(f: OptionalTypedField[Calendar]): Option[java.sql.Timestamp] = f.is.map(d => new java.sql.Timestamp(d.getTime.getTime))
+ implicit def calendarFieldQuery2RightHandSideOfIn[F <: TypedField[Calendar]](q: org.squeryl.Query[F]) = new RightHandSideOfIn[Timestamp](q.ast)
+ /**
+ * Needed for queries on constant calendar values.
+ */
+ implicit def calendarToTimestampExpression(c: Calendar) = dateToTimestampExpression(c.getTime)
+
+ /**
+ * Neeed for queries on constant date values.
+ */
+ implicit def dateToTimestampExpression(d: java.util.Date) = new ConstantExpressionNode[Timestamp](new java.sql.Timestamp(d.getTime)) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
+
/** Conversion of mandatory Enum fields to Squeryl Expressions. */
implicit def enum2EnumExpr[EnumType <: Enumeration](f: MandatoryTypedField[EnumType#Value]) = fieldReference match {
case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value]
@@ -148,6 +168,16 @@ trait RecordTypeMode extends PrimitiveTypeMode {
case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value]
case None => new ConstantExpressionNode[Enumeration#Value](getValue(f).orNull) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value]
}
+
+ implicit def enumFieldQuery2RightHandSideOfIn[EnumType <: Enumeration, T <: Record[T]](q: org.squeryl.Query[EnumNameField[T, EnumType]]) = new RightHandSideOfIn[Enumeration#Value](q.ast)
+
+
+ /** Needed for inner queries on certain non-numerical fields: */
+ /*implicit def mandatoryTypedField2Value[T](f: MandatoryTypedField[T]): T = f.is
+ implicit def optionalTypedField2Value[T](f: OptionalTypedField[T]): Option[T] = f.is*/
+
+ implicit def typedFieldQuery2RightHandSideOfIn[T, F <: TypedField[T]](q: org.squeryl.Query[F]) = new RightHandSideOfIn[T](q.ast)
+
/**
* Helper method for converting mandatory numerical fields to Squeryl Expressions.
@@ -211,4 +241,4 @@ trait SquerylRecordNonNumericalExpression[T] { this: NonNumericalExpression[T] =
def defineAs(columnAttributes: AttributeValidOnNonNumericalColumn*)(implicit restrictUsageWithinSchema: Schema) = {
is(columnAttributes: _*)(restrictUsageWithinSchema)
}
-}
+}
84 persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala
View
@@ -18,6 +18,7 @@ import org.specs.Specification
import record.{BaseField, Record}
import RecordTypeMode._
import MySchema.{TestData => td, _}
+import java.util.Calendar
/**
@@ -199,6 +200,89 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") {
loadedCompanies.size must beGreaterThanOrEqualTo(1)
}
}
+
+ forExample("support associate with one-to-many relations") >> {
+ transactionWithRollback {
+ //td.c3.employees.associate(td.e2)
+ //td.e2.company.id must_== td.c3.id
+ }
+ }
+
+ forExample("support many to many relations") >> {
+ transactionWithRollback {
+ td.e1.rooms must haveSize(2)
+ }
+ }
+
+ forExample("support date/time queries") >> {
+ transaction {
+ val c1 = from(companies)(c =>
+ where (c.created <= Calendar.getInstance)
+ select(c))
+ c1.size must beGreaterThan(1)
+
+ val c2 = from(companies)(c =>
+ where (c.created <= Calendar.getInstance.getTime)
+ select(c))
+ c2.size must beGreaterThan(1)
+ }
+ }
+
+ forExample("support inner queries") >> {
+ import record.field._
+
+ transaction {
+ // Should work with the ID function (returns a long):
+ val companyId: Long = from(companies)(c => where(c.id in
+ from(companies)(c2 => where(c2.id === td.c1.id) select(c2.id)))
+ select(c.id)).single
+ companyId must_== td.c1.id
+
+ // It should also be possible to select the ID field directly:
+ val companyIdField: LongField[Company] = from(companies)(c => where(c.idField in
+ from(companies)(c2 => where(c2.id === td.c1.id) select(c2.idField)))
+ select(c.idField)).single
+ companyIdField.is must_== td.c1.id
+
+ // Strings should also be selectable in inner queries
+ val companyIdByName: Long = from(companies)(c => where(c.name in
+ from(companies)(c2 => where(c2.name === td.c1.name) select(c2.name)))
+ select(c.id)).single
+ companyIdByName must_== td.c1.id
+
+ // ...And DateTime-Fields:
+ val companyIdByCreated: DateTimeField[Company] = from(companies)(c => where(c.created in
+ from(companies)(c2 => where(c2.id === td.c1.id) select(c2.created)))
+ select(c.created)).single
+ companyIdByCreated.is must_== td.c1.created.is
+
+ // Decimal Fields:
+ val empSalary: DecimalField[Employee] = from(employees)(e => where (e.salary in
+ from(employees)(e2 => where(e2.id === td.e1.id) select(e2.salary)))
+ select(e.salary)).single
+ empSalary.is must_== td.e1.salary.is
+
+ // Email fields:
+ val empEmail: EmailField[Employee] = from(employees)(e => where (e.email in
+ from(employees)(e2 => where(e2.id === td.e1.id) select(e2.email)))
+ select(e.email)).single
+ empSalary.is must_== td.e1.salary.is
+
+ // Boolean fields:
+ val empAdmin: BooleanField[Employee] = from(employees)(e => where (e.admin in
+ from(employees)(e2 => where(e2.id === td.e1.id) select(e2.admin)))
+ select(e.admin)).single
+ empAdmin.is must_== td.e1.admin.is
+
+ // Enum fields:
+ val empRole: EnumNameField[_, _] = from(employees)(e => where (e.role in
+ from(employees)(e2 => where(e2.id === td.e1.id) select(e2.role)))
+ select(e.role)).single
+ empRole.is must_== td.e1.role.is
+
+ }
+ }
+
}
class TransactionRollbackException extends RuntimeException
Please sign in to comment.
Something went wrong with that request. Please try again.