Skip to content
This repository
Browse code

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
@@ -15,7 +15,7 @@ package net.liftweb
15 15 package squerylrecord
16 16
17 17 import common.{Box, Full}
18   -import record.{BaseField, MetaRecord, Record, TypedField}
  18 +import record.{BaseField, MetaRecord, Record, TypedField, OwnedField}
19 19 import record.field._
20 20
21 21 import org.squeryl.internals.{FieldMetaData, PosoMetaData, FieldMetaDataFactory}
@@ -152,5 +152,20 @@ class RecordMetaDataFactory extends FieldMetaDataFactory {
152 152
153 153 () => metaRecord.createRecord.asInstanceOf[AnyRef]
154 154 }
  155 +
  156 + /**
  157 + * There needs to be a special handling for squeryl-record when single fields are selected.
  158 + *
  159 + * The problem was that record fields reference the record itself and thus Squeryl was of the
  160 + * opinion that the whole record should be returned, as well as the selected field.
  161 + * It is described in detail in this bug report:
  162 + * https://www.assembla.com/spaces/liftweb/tickets/876-record-squeryl-selecting-unspecified-columns-in-generated-sql
  163 + *
  164 + * By overriding this function, the reference to the record is excluded from
  165 + * the reference finding algorithm in Squeryl.
  166 + */
  167 + override def hideFromYieldInspection(o: AnyRef, f: Field): Boolean = {
  168 + o.isInstanceOf[OwnedField[_]] && isRecord(f.getType)
  169 + }
155 170
156 171 }
40 persistence/squeryl-record/src/main/scala/net/liftweb/squerylrecord/RecordTypeMode.scala
@@ -14,11 +14,12 @@
14 14 package net.liftweb
15 15 package squerylrecord
16 16
17   -import record.{ MandatoryTypedField, OptionalTypedField, TypedField }
  17 +import record.{ MandatoryTypedField, OptionalTypedField, TypedField, Record}
  18 +import record.field.{EnumNameField, OptionalEnumNameField, EnumField, OptionalEnumField}
18 19
19   -import org.squeryl.{ PrimitiveTypeMode, Schema }
  20 +import org.squeryl.{ PrimitiveTypeMode, Schema, Query }
20 21 import org.squeryl.dsl.{ BooleanExpression, DateExpression, EnumExpression, NumericalExpression, StringExpression, NonNumericalExpression }
21   -import org.squeryl.dsl.ast.{ SelectElementReference, SelectElement, ConstantExpressionNode }
  22 +import org.squeryl.dsl.ast.{ SelectElementReference, SelectElement, ConstantExpressionNode, RightHandSideOfIn }
22 23 import org.squeryl.internals.{ AttributeValidOnNonNumericalColumn, AttributeValidOnNumericalColumn, FieldReferenceLinker, OutMapper }
23 24
24 25 import java.util.{ Calendar, Date }
@@ -125,12 +126,31 @@ trait RecordTypeMode extends PrimitiveTypeMode {
125 126 }
126 127 }
127 128
  129 + /** Needed for inner selects. The cast is possible here because the type is not
  130 + * used in the in query. Only the AST of the query is needed. */
  131 + //implicit def queryStringField2QueryString[T <: TypedField[String]](q: Query[T]): Query[String] = q.asInstanceOf[Query[String]]
  132 +
128 133 /** Needed for outer joins. */
129 134 implicit def optionDateField2OptionDate(f: Option[TypedField[Calendar]]) = fieldReference match {
130 135 case Some(e) => new SelectElementReference[Timestamp](e)(createOutMapperTimestampType) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
131   - case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull) with BooleanExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
  136 + case None => new ConstantExpressionNode[Timestamp](getValue(f).map(field => new Timestamp(field.getTimeInMillis)).orNull) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
132 137 }
  138 +
  139 + /** Needed for inner queries on date fields */
  140 + //implicit def dateField2Timestamp(f: MandatoryTypedField[Calendar]) = new java.sql.Timestamp(f.is.getTime.getTime)
  141 + //implicit def optionalDateField2Timestamp(f: OptionalTypedField[Calendar]): Option[java.sql.Timestamp] = f.is.map(d => new java.sql.Timestamp(d.getTime.getTime))
  142 + implicit def calendarFieldQuery2RightHandSideOfIn[F <: TypedField[Calendar]](q: org.squeryl.Query[F]) = new RightHandSideOfIn[Timestamp](q.ast)
133 143
  144 + /**
  145 + * Needed for queries on constant calendar values.
  146 + */
  147 + implicit def calendarToTimestampExpression(c: Calendar) = dateToTimestampExpression(c.getTime)
  148 +
  149 + /**
  150 + * Neeed for queries on constant date values.
  151 + */
  152 + implicit def dateToTimestampExpression(d: java.util.Date) = new ConstantExpressionNode[Timestamp](new java.sql.Timestamp(d.getTime)) with DateExpression[Timestamp] with SquerylRecordNonNumericalExpression[Timestamp]
  153 +
134 154 /** Conversion of mandatory Enum fields to Squeryl Expressions. */
135 155 implicit def enum2EnumExpr[EnumType <: Enumeration](f: MandatoryTypedField[EnumType#Value]) = fieldReference match {
136 156 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 {
148 168 case Some(e) => new SelectElementReference[Enumeration#Value](e)(e.createEnumerationMapper) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value]
149 169 case None => new ConstantExpressionNode[Enumeration#Value](getValue(f).orNull) with EnumExpression[Enumeration#Value] with SquerylRecordNonNumericalExpression[Enumeration#Value]
150 170 }
  171 +
  172 + implicit def enumFieldQuery2RightHandSideOfIn[EnumType <: Enumeration, T <: Record[T]](q: org.squeryl.Query[EnumNameField[T, EnumType]]) = new RightHandSideOfIn[Enumeration#Value](q.ast)
  173 +
  174 +
  175 + /** Needed for inner queries on certain non-numerical fields: */
  176 + /*implicit def mandatoryTypedField2Value[T](f: MandatoryTypedField[T]): T = f.is
  177 + implicit def optionalTypedField2Value[T](f: OptionalTypedField[T]): Option[T] = f.is*/
  178 +
  179 + implicit def typedFieldQuery2RightHandSideOfIn[T, F <: TypedField[T]](q: org.squeryl.Query[F]) = new RightHandSideOfIn[T](q.ast)
  180 +
151 181
152 182 /**
153 183 * Helper method for converting mandatory numerical fields to Squeryl Expressions.
@@ -211,4 +241,4 @@ trait SquerylRecordNonNumericalExpression[T] { this: NonNumericalExpression[T] =
211 241 def defineAs(columnAttributes: AttributeValidOnNonNumericalColumn*)(implicit restrictUsageWithinSchema: Schema) = {
212 242 is(columnAttributes: _*)(restrictUsageWithinSchema)
213 243 }
214   -}
  244 +}
84 persistence/squeryl-record/src/test/scala/net/liftweb/squerylrecord/SquerylRecordSpec.scala
@@ -18,6 +18,7 @@ import org.specs.Specification
18 18 import record.{BaseField, Record}
19 19 import RecordTypeMode._
20 20 import MySchema.{TestData => td, _}
  21 +import java.util.Calendar
21 22
22 23
23 24 /**
@@ -199,6 +200,89 @@ object SquerylRecordSpec extends Specification("SquerylRecord Specification") {
199 200 loadedCompanies.size must beGreaterThanOrEqualTo(1)
200 201 }
201 202 }
  203 +
  204 + forExample("support associate with one-to-many relations") >> {
  205 + transactionWithRollback {
  206 + //td.c3.employees.associate(td.e2)
  207 + //td.e2.company.id must_== td.c3.id
  208 + }
  209 + }
  210 +
  211 + forExample("support many to many relations") >> {
  212 + transactionWithRollback {
  213 + td.e1.rooms must haveSize(2)
  214 + }
  215 + }
  216 +
  217 + forExample("support date/time queries") >> {
  218 + transaction {
  219 + val c1 = from(companies)(c =>
  220 + where (c.created <= Calendar.getInstance)
  221 + select(c))
  222 + c1.size must beGreaterThan(1)
  223 +
  224 + val c2 = from(companies)(c =>
  225 + where (c.created <= Calendar.getInstance.getTime)
  226 + select(c))
  227 + c2.size must beGreaterThan(1)
  228 + }
  229 + }
  230 +
  231 + forExample("support inner queries") >> {
  232 + import record.field._
  233 +
  234 + transaction {
  235 + // Should work with the ID function (returns a long):
  236 + val companyId: Long = from(companies)(c => where(c.id in
  237 + from(companies)(c2 => where(c2.id === td.c1.id) select(c2.id)))
  238 + select(c.id)).single
  239 + companyId must_== td.c1.id
  240 +
  241 + // It should also be possible to select the ID field directly:
  242 + val companyIdField: LongField[Company] = from(companies)(c => where(c.idField in
  243 + from(companies)(c2 => where(c2.id === td.c1.id) select(c2.idField)))
  244 + select(c.idField)).single
  245 + companyIdField.is must_== td.c1.id
  246 +
  247 + // Strings should also be selectable in inner queries
  248 + val companyIdByName: Long = from(companies)(c => where(c.name in
  249 + from(companies)(c2 => where(c2.name === td.c1.name) select(c2.name)))
  250 + select(c.id)).single
  251 + companyIdByName must_== td.c1.id
  252 +
  253 + // ...And DateTime-Fields:
  254 + val companyIdByCreated: DateTimeField[Company] = from(companies)(c => where(c.created in
  255 + from(companies)(c2 => where(c2.id === td.c1.id) select(c2.created)))
  256 + select(c.created)).single
  257 + companyIdByCreated.is must_== td.c1.created.is
  258 +
  259 + // Decimal Fields:
  260 + val empSalary: DecimalField[Employee] = from(employees)(e => where (e.salary in
  261 + from(employees)(e2 => where(e2.id === td.e1.id) select(e2.salary)))
  262 + select(e.salary)).single
  263 + empSalary.is must_== td.e1.salary.is
  264 +
  265 + // Email fields:
  266 + val empEmail: EmailField[Employee] = from(employees)(e => where (e.email in
  267 + from(employees)(e2 => where(e2.id === td.e1.id) select(e2.email)))
  268 + select(e.email)).single
  269 + empSalary.is must_== td.e1.salary.is
  270 +
  271 + // Boolean fields:
  272 + val empAdmin: BooleanField[Employee] = from(employees)(e => where (e.admin in
  273 + from(employees)(e2 => where(e2.id === td.e1.id) select(e2.admin)))
  274 + select(e.admin)).single
  275 + empAdmin.is must_== td.e1.admin.is
  276 +
  277 + // Enum fields:
  278 + val empRole: EnumNameField[_, _] = from(employees)(e => where (e.role in
  279 + from(employees)(e2 => where(e2.id === td.e1.id) select(e2.role)))
  280 + select(e.role)).single
  281 + empRole.is must_== td.e1.role.is
  282 +
  283 + }
  284 + }
  285 +
202 286 }
203 287
204 288 class TransactionRollbackException extends RuntimeException

0 comments on commit 20ae05b

Please sign in to comment.
Something went wrong with that request. Please try again.