/
JdbcTypesComponent.scala
386 lines (349 loc) · 17.9 KB
/
JdbcTypesComponent.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
package scala.slick.driver
import java.sql.{Blob, Clob, Date, Time, Timestamp}
import java.util.UUID
import scala.slick.SlickException
import scala.slick.ast._
import scala.slick.jdbc.{PositionedParameters, PositionedResult}
import scala.slick.profile.RelationalTypesComponent
import scala.reflect.ClassTag
trait JdbcTypesComponent extends RelationalTypesComponent { driver: JdbcDriver =>
/** A JdbcType object represents a Scala type that can be
* used as a column type in the database. Implicit JdbcTypes
* for the standard types of a profile are provided by the drivers. */
trait JdbcType[T] extends TypedType[T] { self =>
/** The constant from java.sql.Types that is used for setting parameters
* of the type to NULL. */
def sqlType: Int
/** The default name for the SQL type that is used for column declarations. */
def sqlTypeName: String
/** Set a parameter of the type. */
def setValue(v: T, p: PositionedParameters): Unit
/** Set an Option parameter of the type. */
def setOption(v: Option[T], p: PositionedParameters): Unit
/** Get a result column of the type. */
def nextValue(r: PositionedResult): T
/** Update a column of the type in a mutable result set. */
def updateValue(v: T, r: PositionedResult): Unit
def nextValueOrElse(d: =>T, r: PositionedResult) = { val v = nextValue(r); if(r.rs.wasNull) d else v }
def nextOption(r: PositionedResult): Option[T] = { val v = nextValue(r); if(r.rs.wasNull) None else Some(v) }
def updateOption(v: Option[T], r: PositionedResult): Unit = v match {
case Some(s) => updateValue(s, r)
case None => r.updateNull()
}
/** Convert a value to a SQL literal.
* This should throw a `SlickException` if `hasLiteralForm` is false. */
def valueToSQLLiteral(value: T): String
def nullable = false
/** Indicates whether values of this type have a literal representation in
* SQL statements.
* This must return false if `valueToSQLLiteral` throws a SlickException.
* QueryBuilder (and driver-specific subclasses thereof) uses this method
* to treat LiteralNodes as volatile (i.e. using bind variables) as needed. */
def hasLiteralForm: Boolean
override def optionType: OptionTypedType[T] with JdbcType[Option[T]] = new OptionTypedType[T] with JdbcType[Option[T]] {
val elementType = self
def sqlType = self.sqlType
override def sqlTypeName = self.sqlTypeName
def scalaType = new ScalaOptionType[T](self.scalaType)
def setValue(v: Option[T], p: PositionedParameters) = self.setOption(v, p)
def setOption(v: Option[Option[T]], p: PositionedParameters) = self.setOption(v.getOrElse(None), p)
def nextValue(r: PositionedResult) = self.nextOption(r)
def updateValue(v: Option[T], r: PositionedResult) = self.updateOption(v, r)
override def valueToSQLLiteral(value: Option[T]): String = value.map(self.valueToSQLLiteral).getOrElse("null")
override def nullable = true
override def toString = s"Option[$self]"
def hasLiteralForm = self.hasLiteralForm
def mapChildren(f: Type => Type): OptionTypedType[T] with JdbcType[Option[T]] = {
val e2 = f(elementType)
if(e2 eq elementType) this
else e2.asInstanceOf[JdbcType[T]].optionType
}
}
override def toString = {
def cln = getClass.getName
val pos = cln.lastIndexOf("$JdbcTypes$")
val s = if(pos >= 0) cln.substring(pos+11) else cln
val s2 = if(s.endsWith("JdbcType")) s.substring(0, s.length-8) else s
s2 + "/" + sqlTypeName
}
}
abstract class MappedJdbcType[T, U](implicit tmd: JdbcType[U], tag: ClassTag[T]) extends JdbcType[T] {
def map(t: T): U
def comap(u: U): T
def newSqlType: Option[Int] = None
def newSqlTypeName: Option[String] = None
def newValueToSQLLiteral(value: T): Option[String] = None
def newNullable: Option[Boolean] = None
def newHasLiteralForm: Option[Boolean] = None
def sqlType = newSqlType.getOrElse(tmd.sqlType)
override def sqlTypeName = newSqlTypeName.getOrElse(tmd.sqlTypeName)
def setValue(v: T, p: PositionedParameters) = tmd.setValue(map(v), p)
def setOption(v: Option[T], p: PositionedParameters) = tmd.setOption(v.map(map _), p)
def nextValue(r: PositionedResult) = comap(tmd.nextValue(r))
override def nextValueOrElse(d: =>T, r: PositionedResult) = { val v = tmd.nextValue(r); if(r.rs.wasNull) d else comap(v) }
override def nextOption(r: PositionedResult): Option[T] = { val v = tmd.nextValue(r); if(r.rs.wasNull) None else Some(comap(v)) }
def updateValue(v: T, r: PositionedResult) = tmd.updateValue(map(v), r)
override def valueToSQLLiteral(value: T) = newValueToSQLLiteral(value).getOrElse(tmd.valueToSQLLiteral(map(value)))
override def nullable = newNullable.getOrElse(tmd.nullable)
def hasLiteralForm = newHasLiteralForm.getOrElse(tmd.hasLiteralForm)
def scalaType = ScalaBaseType[T]
}
object MappedJdbcType extends MappedColumnTypeFactory {
def base[T : ClassTag, U : BaseColumnType](tmap: T => U, tcomap: U => T): BaseColumnType[T] =
new MappedJdbcType[T, U] with BaseTypedType[T] {
def map(t: T) = tmap(t)
def comap(u: U) = tcomap(u)
}
}
type TypeInfo = JdbcType[Any /* it's really _ but we'd have to cast it to Any anyway */]
def typeInfoFor(t: Type): TypeInfo = ((t.structural match {
case tmd: JdbcType[_] => tmd
case ScalaBaseType.booleanType => columnTypes.booleanJdbcType
case ScalaBaseType.bigDecimalType => columnTypes.bigDecimalJdbcType
case ScalaBaseType.byteType => columnTypes.byteJdbcType
case ScalaBaseType.charType => columnTypes.charJdbcType
case ScalaBaseType.doubleType => columnTypes.doubleJdbcType
case ScalaBaseType.floatType => columnTypes.floatJdbcType
case ScalaBaseType.intType => columnTypes.intJdbcType
case ScalaBaseType.longType => columnTypes.longJdbcType
case ScalaBaseType.nullType => columnTypes.nullJdbcType
case ScalaBaseType.shortType => columnTypes.shortJdbcType
case ScalaBaseType.stringType => columnTypes.stringJdbcType
case o: OptionType => typeInfoFor(o.elementType).optionType
case t => throw new SlickException("JdbcProfile has no TypeInfo for type "+t)
}): JdbcType[_]).asInstanceOf[JdbcType[Any]]
def defaultSqlTypeName(tmd: JdbcType[_]): String = tmd.sqlType match {
case java.sql.Types.VARCHAR => "VARCHAR(254)"
case java.sql.Types.DECIMAL => "DECIMAL(21,2)"
case t => JdbcTypesComponent.typeNames.getOrElse(t,
throw new SlickException("No SQL type name found in java.sql.Types for code "+t))
}
abstract class DriverJdbcType[T : ClassTag] extends JdbcType[T] with BaseTypedType[T] {
def scalaType = ScalaBaseType[T]
def sqlTypeName: String = driver.defaultSqlTypeName(this)
def valueToSQLLiteral(value: T) =
if(hasLiteralForm) value.toString
else throw new SlickException(sqlTypeName + " does not have a literal representation")
def hasLiteralForm = true
}
class JdbcTypes {
val booleanJdbcType = new BooleanJdbcType
val blobJdbcType = new BlobJdbcType
val byteJdbcType = new ByteJdbcType
val byteArrayJdbcType = new ByteArrayJdbcType
val charJdbcType = new CharJdbcType
val clobJdbcType = new ClobJdbcType
val dateJdbcType = new DateJdbcType
val doubleJdbcType = new DoubleJdbcType
val floatJdbcType = new FloatJdbcType
val intJdbcType = new IntJdbcType
val longJdbcType = new LongJdbcType
val shortJdbcType = new ShortJdbcType
val stringJdbcType = new StringJdbcType
val timeJdbcType = new TimeJdbcType
val timestampJdbcType = new TimestampJdbcType
val uuidJdbcType = new UUIDJdbcType
val bigDecimalJdbcType = new BigDecimalJdbcType
val nullJdbcType = new NullJdbcType
class BooleanJdbcType extends DriverJdbcType[Boolean] {
def sqlType = java.sql.Types.BOOLEAN
def setValue(v: Boolean, p: PositionedParameters) = p.setBoolean(v)
def setOption(v: Option[Boolean], p: PositionedParameters) = p.setBooleanOption(v)
def nextValue(r: PositionedResult) = r.nextBoolean
def updateValue(v: Boolean, r: PositionedResult) = r.updateBoolean(v)
}
class BlobJdbcType extends DriverJdbcType[Blob] {
def sqlType = java.sql.Types.BLOB
def setValue(v: Blob, p: PositionedParameters) = p.setBlob(v)
def setOption(v: Option[Blob], p: PositionedParameters) = p.setBlobOption(v)
def nextValue(r: PositionedResult) = r.nextBlob
def updateValue(v: Blob, r: PositionedResult) = r.updateBlob(v)
override def hasLiteralForm = false
}
class ByteJdbcType extends DriverJdbcType[Byte] with NumericTypedType {
def sqlType = java.sql.Types.TINYINT
def setValue(v: Byte, p: PositionedParameters) = p.setByte(v)
def setOption(v: Option[Byte], p: PositionedParameters) = p.setByteOption(v)
def nextValue(r: PositionedResult) = r.nextByte
def updateValue(v: Byte, r: PositionedResult) = r.updateByte(v)
}
class ByteArrayJdbcType extends DriverJdbcType[Array[Byte]] {
def sqlType = java.sql.Types.BLOB
def setValue(v: Array[Byte], p: PositionedParameters) = p.setBytes(v)
def setOption(v: Option[Array[Byte]], p: PositionedParameters) = p.setBytesOption(v)
def nextValue(r: PositionedResult) = r.nextBytes
def updateValue(v: Array[Byte], r: PositionedResult) = r.updateBytes(v)
override def hasLiteralForm = false
}
class ClobJdbcType extends DriverJdbcType[Clob] {
def sqlType = java.sql.Types.CLOB
def setValue(v: Clob, p: PositionedParameters) = p.setClob(v)
def setOption(v: Option[Clob], p: PositionedParameters) = p.setClobOption(v)
def nextValue(r: PositionedResult) = r.nextClob
def updateValue(v: Clob, r: PositionedResult) = r.updateClob(v)
override def hasLiteralForm = false
}
class CharJdbcType extends DriverJdbcType[Char] {
def sqlType = java.sql.Types.CHAR
override def sqlTypeName = "CHAR(1)"
def setValue(v: Char, p: PositionedParameters) = stringJdbcType.setValue(String.valueOf(v), p)
def setOption(v: Option[Char], p: PositionedParameters) = stringJdbcType.setOption(v.map(String.valueOf), p)
def nextValue(r: PositionedResult) = {
val s = stringJdbcType.nextValue(r)
if(s == null || s.isEmpty) ' ' else s.charAt(0)
}
def updateValue(v: Char, r: PositionedResult) = stringJdbcType.updateValue(String.valueOf(v), r)
override def valueToSQLLiteral(v: Char) = stringJdbcType.valueToSQLLiteral(String.valueOf(v))
}
class DateJdbcType extends DriverJdbcType[Date] {
def sqlType = java.sql.Types.DATE
def setValue(v: Date, p: PositionedParameters) = p.setDate(v)
def setOption(v: Option[Date], p: PositionedParameters) = p.setDateOption(v)
def nextValue(r: PositionedResult) = r.nextDate
def updateValue(v: Date, r: PositionedResult) = r.updateDate(v)
override def valueToSQLLiteral(value: Date) = "{d '"+value.toString+"'}"
}
class DoubleJdbcType extends DriverJdbcType[Double] with NumericTypedType {
def sqlType = java.sql.Types.DOUBLE
def setValue(v: Double, p: PositionedParameters) = p.setDouble(v)
def setOption(v: Option[Double], p: PositionedParameters) = p.setDoubleOption(v)
def nextValue(r: PositionedResult) = r.nextDouble
def updateValue(v: Double, r: PositionedResult) = r.updateDouble(v)
}
class FloatJdbcType extends DriverJdbcType[Float] with NumericTypedType {
def sqlType = java.sql.Types.FLOAT
def setValue(v: Float, p: PositionedParameters) = p.setFloat(v)
def setOption(v: Option[Float], p: PositionedParameters) = p.setFloatOption(v)
def nextValue(r: PositionedResult) = r.nextFloat
def updateValue(v: Float, r: PositionedResult) = r.updateFloat(v)
}
class IntJdbcType extends DriverJdbcType[Int] with NumericTypedType {
def sqlType = java.sql.Types.INTEGER
def setValue(v: Int, p: PositionedParameters) = p.setInt(v)
def setOption(v: Option[Int], p: PositionedParameters) = p.setIntOption(v)
def nextValue(r: PositionedResult) = r.nextInt
def updateValue(v: Int, r: PositionedResult) = r.updateInt(v)
}
class LongJdbcType extends DriverJdbcType[Long] with NumericTypedType {
def sqlType = java.sql.Types.BIGINT
def setValue(v: Long, p: PositionedParameters) = p.setLong(v)
def setOption(v: Option[Long], p: PositionedParameters) = p.setLongOption(v)
def nextValue(r: PositionedResult) = r.nextLong
def updateValue(v: Long, r: PositionedResult) = r.updateLong(v)
}
class ShortJdbcType extends DriverJdbcType[Short] with NumericTypedType {
def sqlType = java.sql.Types.SMALLINT
def setValue(v: Short, p: PositionedParameters) = p.setShort(v)
def setOption(v: Option[Short], p: PositionedParameters) = p.setShortOption(v)
def nextValue(r: PositionedResult) = r.nextShort
def updateValue(v: Short, r: PositionedResult) = r.updateShort(v)
}
class StringJdbcType extends DriverJdbcType[String] {
def sqlType = java.sql.Types.VARCHAR
def setValue(v: String, p: PositionedParameters) = p.setString(v)
def setOption(v: Option[String], p: PositionedParameters) = p.setStringOption(v)
def nextValue(r: PositionedResult) = r.nextString
def updateValue(v: String, r: PositionedResult) = r.updateString(v)
override def valueToSQLLiteral(value: String) = if(value eq null) "NULL" else {
val sb = new StringBuilder
sb append '\''
for(c <- value) c match {
case '\'' => sb append "''"
case _ => sb append c
}
sb append '\''
sb.toString
}
}
class TimeJdbcType extends DriverJdbcType[Time] {
def sqlType = java.sql.Types.TIME
def setValue(v: Time, p: PositionedParameters) = p.setTime(v)
def setOption(v: Option[Time], p: PositionedParameters) = p.setTimeOption(v)
def nextValue(r: PositionedResult) = r.nextTime
def updateValue(v: Time, r: PositionedResult) = r.updateTime(v)
override def valueToSQLLiteral(value: Time) = "{t '"+value.toString+"'}"
}
class TimestampJdbcType extends DriverJdbcType[Timestamp] {
def sqlType = java.sql.Types.TIMESTAMP
def setValue(v: Timestamp, p: PositionedParameters) = p.setTimestamp(v)
def setOption(v: Option[Timestamp], p: PositionedParameters) = p.setTimestampOption(v)
def nextValue(r: PositionedResult) = r.nextTimestamp
def updateValue(v: Timestamp, r: PositionedResult) = r.updateTimestamp(v)
override def valueToSQLLiteral(value: Timestamp) = "{ts '"+value.toString+"'}"
}
class UUIDJdbcType extends DriverJdbcType[UUID] {
def sqlType = java.sql.Types.OTHER
def setValue(v: UUID, p: PositionedParameters) = p.setBytes(toBytes(v))
def setOption(v: Option[UUID], p: PositionedParameters) =
if(v == None) p.setNull(sqlType) else p.setBytes(toBytes(v.get))
def nextValue(r: PositionedResult) = fromBytes(r.nextBytes())
def updateValue(v: UUID, r: PositionedResult) = r.updateBytes(toBytes(v))
override def hasLiteralForm = false
def toBytes(uuid: UUID) = if(uuid eq null) null else {
val msb = uuid.getMostSignificantBits
val lsb = uuid.getLeastSignificantBits
val buff = new Array[Byte](16)
var i = 0
while(i < 8) {
buff(i) = ((msb >> (8 * (7 - i))) & 255).toByte;
buff(8 + i) = ((lsb >> (8 * (7 - i))) & 255).toByte;
i += 1
}
buff
}
def fromBytes(data: Array[Byte]) = if(data eq null) null else {
var msb = 0L
var lsb = 0L
var i = 0
while(i < 8) {
msb = (msb << 8) | (data(i) & 0xff)
i += 1
}
while(i < 16) {
lsb = (lsb << 8) | (data(i) & 0xff)
i += 1
}
new UUID(msb, lsb)
}
}
class BigDecimalJdbcType extends DriverJdbcType[BigDecimal] with NumericTypedType {
def sqlType = java.sql.Types.DECIMAL
def setValue(v: BigDecimal, p: PositionedParameters) = p.setBigDecimal(v)
def setOption(v: Option[BigDecimal], p: PositionedParameters) = p.setBigDecimalOption(v)
def nextValue(r: PositionedResult) = r.nextBigDecimal
def updateValue(v: BigDecimal, r: PositionedResult) = r.updateBigDecimal(v)
}
class NullJdbcType extends DriverJdbcType[Null] {
def sqlType = java.sql.Types.NULL
def setValue(v: Null, p: PositionedParameters) = p.setString(null)
def setOption(v: Option[Null], p: PositionedParameters) = p.setString(null)
def nextValue(r: PositionedResult) = { r.nextString; null }
def updateValue(v: Null, r: PositionedResult) = r.updateNull()
override def valueToSQLLiteral(value: Null) = "NULL"
}
}
trait ImplicitColumnTypes extends super.ImplicitColumnTypes {
implicit def booleanColumnType = columnTypes.booleanJdbcType
implicit def blobColumnType = columnTypes.blobJdbcType
implicit def byteColumnType = columnTypes.byteJdbcType
implicit def byteArrayColumnType = columnTypes.byteArrayJdbcType
implicit def charColumnType = columnTypes.charJdbcType
implicit def clobColumnType = columnTypes.clobJdbcType
implicit def dateColumnType = columnTypes.dateJdbcType
implicit def doubleColumnType = columnTypes.doubleJdbcType
implicit def floatColumnType = columnTypes.floatJdbcType
implicit def intColumnType = columnTypes.intJdbcType
implicit def longColumnType = columnTypes.longJdbcType
implicit def shortColumnType = columnTypes.shortJdbcType
implicit def stringColumnType = columnTypes.stringJdbcType
implicit def timeColumnType = columnTypes.timeJdbcType
implicit def timestampColumnType = columnTypes.timestampJdbcType
implicit def uuidColumnType = columnTypes.uuidJdbcType
implicit def bigDecimalColumnType = columnTypes.bigDecimalJdbcType
}
}
object JdbcTypesComponent {
private[slick] lazy val typeNames = Map() ++
(for(f <- classOf[java.sql.Types].getFields)
yield f.get(null).asInstanceOf[Int] -> f.getName)
}