Skip to content

Commit dd470f1

Browse files
committed
Merge pull request squeryl#157 from awkay/arrays
Adds basic support for Arrays
2 parents 865b717 + 63a354b commit dd470f1

File tree

9 files changed

+224
-13
lines changed

9 files changed

+224
-13
lines changed

org.squeryl.tests.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ h2.connectionString=jdbc:h2:~/test
33
h2.user=sa
44
h2.password=
55

6-
#postgresql.connectionString=jdbc:postgresql://localhost:5432/squeryl
7-
#postgresql.user=squeryl
8-
#postgresql.password=squeryl
6+
postgresql.connectionString=jdbc:postgresql://localhost:5432/test
7+
postgresql.user=test
8+
postgresql.password=test
99

1010
#derby.connectionString=jdbc:derby:memory:test;create=true
1111
#derby.user=app

src/main/scala/org/squeryl/PrimitiveTypeMode.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ trait PrimitiveTypeMode extends QueryDsl with FieldMapper {
3838
implicit val optionDateTEF = PrimitiveTypeSupport.optionDateTEF
3939
implicit val timestampTEF = PrimitiveTypeSupport.timestampTEF
4040
implicit val optionTimestampTEF = PrimitiveTypeSupport.optionTimestampTEF
41+
implicit val doubleArrayTEF = PrimitiveTypeSupport.doubleArrayTEF
42+
implicit val intArrayTEF = PrimitiveTypeSupport.intArrayTEF
43+
implicit val longArrayTEF = PrimitiveTypeSupport.longArrayTEF
4144

4245
// =========================== Numerical Integral ===========================
4346
implicit val byteTEF = PrimitiveTypeSupport.byteTEF
@@ -97,5 +100,9 @@ trait PrimitiveTypeMode extends QueryDsl with FieldMapper {
97100

98101
implicit def bigDecimalToTE(f: BigDecimal) = bigDecimalTEF.create(f)
99102
implicit def optionBigDecimalToTE(f: Option[BigDecimal]) = optionBigDecimalTEF.create(f)
103+
104+
implicit def doubleArrayToTE(f : Array[Double]) = doubleArrayTEF.create(f)
105+
implicit def intArrayToTE(f : Array[Int]) = intArrayTEF.create(f)
106+
implicit def longArrayToTE(f : Array[Long]) = longArrayTEF.create(f)
100107

101108
}

src/main/scala/org/squeryl/adapters/PostgreSqlAdapter.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ class PostgreSqlAdapter extends DatabaseAdapter {
3939
override def bigDecimalTypeDeclaration = "numeric"
4040
override def bigDecimalTypeDeclaration(precision:Int, scale:Int) = "numeric(" + precision + "," + scale + ")"
4141
override def binaryTypeDeclaration = "bytea"
42-
override def uuidTypeDeclaration = "uuid"
43-
42+
override def uuidTypeDeclaration = "uuid"
43+
44+
45+
override def jdbcIntArrayCreationType = "int4"
46+
override def jdbcLongArrayCreationType = "int8"
47+
override def jdbcDoubleArrayCreationType = "float8"
48+
4449
override def foreignKeyConstraintName(foreignKeyTable: Table[_], idWithinSchema: Int) =
4550
foreignKeyTable.name + "FK" + idWithinSchema
4651

src/main/scala/org/squeryl/dsl/TypedExpression.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,16 @@ sealed trait TString extends TOptionString with TNonOption
6464
sealed trait TDate extends TOptionDate with TNonOption
6565
sealed trait TTimestamp extends TOptionTimestamp with TNonOption
6666
sealed trait TByteArray extends TOptionByteArray with TNonOption
67+
sealed trait TIntArray extends TOptionIntArray with TNonOption
68+
sealed trait TLongArray extends TOptionLongArray with TNonOption
69+
sealed trait TDoubleArray extends TOptionDoubleArray with TNonOption
6770
sealed trait TOptionString
6871
sealed trait TOptionDate
6972
sealed trait TOptionTimestamp
7073
sealed trait TOptionByteArray
74+
sealed trait TOptionIntArray
75+
sealed trait TOptionLongArray
76+
sealed trait TOptionDoubleArray
7177
sealed trait TBoolean extends TOptionBoolean with TNonOption
7278
sealed trait TOptionBoolean
7379
sealed trait TUUID extends TOptionUUID with TNonOption
@@ -255,6 +261,10 @@ trait JdbcMapper[P,A] {
255261
def map(rs: ResultSet, i: Int): A = convertFromJdbc(extractNativeJdbcValue(rs, i))
256262
}
257263

264+
trait ArrayJdbcMapper[P,A] extends JdbcMapper[P,A] {
265+
self: TypedExpressionFactory[A,_] =>
266+
def nativeJdbcType = sample.asInstanceOf[AnyRef].getClass
267+
}
258268

259269
trait PrimitiveJdbcMapper[A] extends JdbcMapper[A,A] {
260270
self: TypedExpressionFactory[A,_] =>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.squeryl.internals
2+
3+
import java.sql.ResultSet
4+
import org.squeryl.Session
5+
import org.squeryl.dsl.TypedExpressionFactory
6+
import org.squeryl.dsl.ArrayJdbcMapper
7+
8+
abstract class ArrayTEF[P, TE] extends TypedExpressionFactory[Array[P], TE] with ArrayJdbcMapper[java.sql.Array, Array[P]] {
9+
// must define "sample" that includes an element. e.g. Array[Int](0)
10+
def sample : Array[P]
11+
def toWrappedJDBCType(element: P) : java.lang.Object
12+
def fromWrappedJDBCType(element: Array[java.lang.Object]) : Array[P]
13+
val defaultColumnLength = 1
14+
def extractNativeJdbcValue(rs: ResultSet, i: Int) = rs.getArray(i)
15+
def convertToJdbc(v: Array[P]): java.sql.Array = {
16+
val content: Array[java.lang.Object] = v.map(toWrappedJDBCType(_))
17+
val s = Session.currentSession
18+
val con = s.connection
19+
var rv: java.sql.Array = null
20+
try {
21+
val typ = s.databaseAdapter.arrayCreationType(sample(0).getClass)
22+
rv = con.createArrayOf(typ, content)
23+
} catch {
24+
case e: Exception => s.log("Cannot create JDBC array: " + e.getMessage)
25+
}
26+
rv
27+
}
28+
def convertFromJdbc(v: java.sql.Array): Array[P] = {
29+
val s = Session.currentSession
30+
var rv : Array[P] = sample.take(0)
31+
try {
32+
val obj = v.getArray();
33+
rv = fromWrappedJDBCType(obj.asInstanceOf[Array[java.lang.Object]])
34+
} catch {
35+
case e: Exception => s.log("Cannot obtain array from JDBC: " + e.getMessage)
36+
}
37+
rv
38+
}
39+
}

src/main/scala/org/squeryl/internals/DatabaseAdapter.scala

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,25 @@ trait DatabaseAdapter {
180180
def bigDecimalTypeDeclaration(precision:Int, scale:Int) = "decimal(" + precision + "," + scale + ")"
181181
def timestampTypeDeclaration = "timestamp"
182182
def binaryTypeDeclaration = "binary"
183-
def uuidTypeDeclaration = "char(36)"
183+
def uuidTypeDeclaration = "char(36)"
184+
def intArrayTypeDeclaration = intTypeDeclaration + "[]"
185+
def longArrayTypeDeclaration = longTypeDeclaration + "[]"
186+
def doubleArrayTypeDeclaration = doubleTypeDeclaration + "[]"
187+
188+
def jdbcIntArrayCreationType = intTypeDeclaration
189+
def jdbcLongArrayCreationType = longTypeDeclaration
190+
def jdbcDoubleArrayCreationType = doubleTypeDeclaration
191+
192+
final def arrayCreationType(ptype : Class[_]) : String = {
193+
val rv = ptype.getName() match {
194+
case "java.lang.Integer" => jdbcIntArrayCreationType
195+
case "java.lang.Double" => jdbcDoubleArrayCreationType
196+
case "java.lang.Long" => jdbcLongArrayCreationType
197+
case _ => ""
198+
}
199+
rv
200+
}
201+
184202
/*
185203
private val _declarationHandler = new FieldTypeHandler[String] {
186204
@@ -796,9 +814,15 @@ trait DatabaseAdapter {
796814
binaryTypeDeclaration
797815
else if(classOf[BigDecimal].isAssignableFrom(c))
798816
bigDecimalTypeDeclaration
817+
else if(classOf[scala.Array[Int]].isAssignableFrom(c))
818+
intArrayTypeDeclaration
819+
else if(classOf[scala.Array[Long]].isAssignableFrom(c))
820+
longArrayTypeDeclaration
821+
else if(classOf[scala.Array[Double]].isAssignableFrom(c))
822+
doubleArrayTypeDeclaration
799823
else
800-
Utils.throwError("unsupported type " + ar.getClass.getCanonicalName)
801-
824+
Utils.throwError("unsupported type " + ar.getClass.getCanonicalName)
825+
802826
decl
803827
}
804828

src/main/scala/org/squeryl/internals/FieldMapper.scala

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ import java.sql.Timestamp
2222
import java.util.Date
2323
import java.util.UUID
2424
import org.squeryl.dsl._
25-
25+
import java.util.regex.Pattern
26+
import org.squeryl.Session
27+
import org.squeryl.dsl.ArrayJdbcMapper
2628

2729
trait FieldMapper {
2830
outer =>
@@ -106,6 +108,29 @@ trait FieldMapper {
106108
val deOptionizer = binaryTEF
107109
}
108110

111+
val intArrayTEF = new ArrayTEF[Int, TIntArray] {
112+
val sample = Array(0)
113+
def toWrappedJDBCType(element: Int) : java.lang.Object = new java.lang.Integer(element)
114+
def fromWrappedJDBCType(elements: Array[java.lang.Object]) : Array[Int] = elements.map(i => i.asInstanceOf[java.lang.Integer].toInt)
115+
}
116+
117+
val longArrayTEF = new ArrayTEF[Long, TLongArray] {
118+
val sample = Array(0L)
119+
def toWrappedJDBCType(element: Long) : java.lang.Object = new java.lang.Long(element)
120+
def fromWrappedJDBCType(elements: Array[java.lang.Object]) : Array[Long] = elements.map(i => i.asInstanceOf[java.lang.Long].toLong)
121+
}
122+
123+
val doubleArrayTEF = new ArrayTEF[Double, TDoubleArray] {
124+
val sample : Array[Double] = Array(0.0)
125+
def toWrappedJDBCType(element: Double) : java.lang.Object = new java.lang.Double(element)
126+
def fromWrappedJDBCType(elements: Array[java.lang.Object]) : Array[Double] = elements.map(i => i.asInstanceOf[java.lang.Double].toDouble)
127+
}
128+
129+
// FIXME: The type soup on this was beyond my patience for now...I think we'll need an ArrayDeOptionizer
130+
//val optionIntArrayTEF = new TypedExpressionFactory[Option[Array[Int]],TOptionIntArray] with DeOptionizer[Array[Int], Array[Int], TIntArray, Option[Array[Int]], TOptionIntArray] {
131+
//val deOptionizer = intArrayTEF
132+
//}
133+
109134
def enumValueTEF[A >: Enumeration#Value <: Enumeration#Value](ev: Enumeration#Value) =
110135
new JdbcMapper[Int,A] with TypedExpressionFactory[A,TEnumValue[A]] {
111136

@@ -222,6 +247,9 @@ trait FieldMapper {
222247
register(timestampTEF)
223248
register(dateTEF)
224249
register(uuidTEF)
250+
register(intArrayTEF)
251+
register(longArrayTEF)
252+
register(doubleArrayTEF)
225253

226254
val re = enumValueTEF(DummyEnum.DummyEnumerationValue)
227255

@@ -323,6 +351,20 @@ trait FieldMapper {
323351
Utils.throwError("field type "+ z.clasz + " already registered, handled by " + m.getClass.getCanonicalName)
324352
}
325353

354+
private [squeryl] def register[S,J](m: ArrayJdbcMapper[S,J]) {
355+
val f = m.thisTypedExpressionFactory
356+
val z = new FieldAttributesBasedOnType(
357+
makeMapper(m),
358+
m.defaultColumnLength,
359+
f.sample,
360+
m.nativeJdbcType)
361+
362+
val wasThere = registry.put(z.clasz, z)
363+
364+
if(wasThere != None)
365+
Utils.throwError("field type "+ z.clasz + " already registered, handled by " + m.getClass.getCanonicalName)
366+
}
367+
326368
private def register[A](pm: PrimitiveJdbcMapper[A]) {
327369
val f = pm.thisTypedExpressionFactory
328370
val z = new FieldAttributesBasedOnType(
@@ -333,8 +375,8 @@ trait FieldMapper {
333375

334376
registry.put(c, z)
335377
}
336-
337-
private def lookup(c: Class[_]): Option[FieldAttributesBasedOnType[_]] =
378+
379+
private def lookup(c: Class[_]): Option[FieldAttributesBasedOnType[_]] = {
338380
if(!c.isPrimitive)
339381
registry.get(c)
340382
else c.getName match {
@@ -346,4 +388,5 @@ trait FieldMapper {
346388
case "double" => lookup(classOf[java.lang.Double])
347389
case "void" => None
348390
}
391+
}
349392
}

src/test/scala/org/squeryl/postgres/PostgresTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ package org.squeryl.postgres
22

33

44
import org.squeryl.test._
5-
65
import org.squeryl.framework.DBConnector
76
import org.squeryl.adapters.PostgreSqlAdapter
8-
97
import org.squeryl.Session
8+
import org.squeryl.test.arrays.PrimitiveArrayTest
109

1110
trait Postgresql_Connection extends DBConnector{
1211
def connectToDb() : Option[() => Session] = {
@@ -28,6 +27,7 @@ trait Postgresql_Connection extends DBConnector{
2827
}
2928
}
3029

30+
class Postgresql_ArrayTests extends PrimitiveArrayTest with Postgresql_Connection
3131
class Postgresql_UuidTests extends UuidTests with Postgresql_Connection
3232
class Postgresql_NestedLeftOuterJoinTest extends NestedLeftOuterJoinTest with Postgresql_Connection
3333
class Postgresql_SchoolDbMutableRelations extends mutablerelations.SchoolDb2MetableRelations with Postgresql_Connection
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package org.squeryl.test.arrays
2+
3+
import _root_.org.squeryl.framework._
4+
5+
abstract class PrimitiveArrayTest extends SchemaTester with RunTestsInsideTransaction {
6+
7+
import _root_.org.squeryl.PrimitiveTypeMode._
8+
9+
val schema = PrimitiveArraySchema
10+
11+
import PrimitiveArraySchema._
12+
13+
test("can insert and query integer, double, and long array values in database") {
14+
transaction {
15+
schema.drop
16+
schema.create
17+
swimmers.insert(new Swimmer(1, Array(10.55, 12.99, 15.32), Array(100, 110, 20), Array(9876543210L, 123456789L)))
18+
}
19+
20+
val query = from(swimmers)((s) => select(s))
21+
val res = transaction { query.toList }
22+
23+
res.size should equal(1)
24+
res(0).lap_times.size should equal(3)
25+
res(0).lap_times(0) should equal(10.55)
26+
res(0).lap_times(1) should equal(12.99)
27+
res(0).lap_times(2) should equal(15.32)
28+
29+
res(0).scores.size should equal(3)
30+
res(0).scores(0) should equal(100)
31+
res(0).scores(1) should equal(110)
32+
res(0).scores(2) should equal(20)
33+
34+
res(0).orgids.size should equal(2)
35+
res(0).orgids(0) should equal(9876543210L)
36+
res(0).orgids(1) should equal(123456789L)
37+
}
38+
test("can update integer, double, and long array values in database") {
39+
transaction {
40+
schema.drop
41+
schema.create
42+
swimmers.insert(new Swimmer(1, Array(10.55, 12.99, 15.32), Array(100, 110, 20), Array(9876543210L, 123456789L)))
43+
}
44+
45+
val query = from(swimmers)((s) => select(s))
46+
val res = transaction { query.toList }
47+
48+
res.size should equal(1)
49+
res(0).lap_times.size should equal(3)
50+
res(0).scores.size should equal(3)
51+
res(0).orgids.size should equal(2)
52+
53+
transaction {
54+
update(swimmers)(s =>
55+
where(s.id === 1)
56+
set (s.lap_times := Array(11.69), s.scores := Array(1, 2, 3, 4, 5), s.orgids := Array(13L)))
57+
}
58+
59+
val query2 = from(swimmers)((s) => select(s))
60+
val res2 = transaction { query.toList }
61+
62+
res2.size should equal(1)
63+
res2(0).lap_times.size should equal(1)
64+
res2(0).scores.size should equal(5)
65+
res2(0).orgids.size should equal(1)
66+
67+
res2(0).lap_times(0) should equal(11.69)
68+
res2(0).scores(2) should equal(3)
69+
res2(0).orgids(0) should equal(13L)
70+
}
71+
}
72+
73+
import _root_.org.squeryl.Schema
74+
import _root_.org.squeryl.PrimitiveTypeMode._
75+
76+
object PrimitiveArraySchema extends Schema {
77+
78+
val swimmers = table[Swimmer]("swimmer")
79+
80+
override def drop = super.drop
81+
}
82+
83+
class Swimmer(val id: Int, val lap_times: Array[Double], val scores: Array[Int], val orgids: Array[Long])

0 commit comments

Comments
 (0)